Finding Optimal Bedtime Windows using Oura Data
In this post, I demonstrate how Oura ring data can be used to identify the optimal bed time window that results in a higher quality sleep.
Some sleep researchs studies suggest that going to bed and waking up at certain times can result in a more optimal sleep. These individual bedtimes windows depend on person’s chronotype and circadian rhythms. To find my personal optimal lights-off/lights-on time, I turn to my Oura data. This analysis is based on data collected during ~650 nights in 2019 and 2020.
More Bedtime =/= Better Sleep
First, let’s see if simply staying longer in bed leads to a higher sleep quality. Thankfully, in addition to time in bed (“duration”), my Oura ring tracks all stages of sleep: light sleep, REM and deep. Similar to total sleep efficiency, I convert the time in each of the sleep stages into proportions of the total sleep time. I am also primarily interested in restorative stages (REM and deep):
sleepbedtime<-ouradata %>% select(duration,light,rem,deep) %>% drop_na %>% mutate(totsleep=(light+rem+deep)) %>% mutate(vars(light,rem,deep),list(~round(100*(./totsleep),1))) %>% mutate(restsleep=rem+deep)
The correlations partially confirms our assumptions: more time in bed does not necessarily result in a better sleep - at least not all stages.
Finding Optimal Bedtime Windows
Original Oura data has bedtime_start and bedtime_end in %Y-%m-%dT%H:%M:%S format so I have to go through some manipulations to convert those into time slots:
mutate(bedtime_end=substr(bedtime_end,1,19)) %>% mutate(bedstart=as.numeric(format(strptime(bedtime_start,"%Y-%m-%dT%H:%M:%S"),'%H.%M'))) %>% mutate(bedend=as.numeric(format(strptime(bedtime_end,"%Y-%m-%dT%H:%M:%S"),'%H.%M'))) %>% mutate(start = 1*(bedstart>12 & bedstart<22.3) + 2*(bedstart>=22.3 & bedstart <23)+ 3*(bedstart >=23) + 3*(bedstart<12)) %>% mutate(bedtime_start=c("<10:30PM","10:30-11:00PM",">11:00PM")[start]) %>% mutate(bedend=as.numeric(format(strptime(bedtime_end,"%Y-%m-%dT%H:%M:%S"),'%H.%M'))) %>% mutate(end = 1*(bedend<6.3) + 2*(bedend>=6.3 & bedend <7)+ 3*(bedend >=7 & bedend <7.3) + 4*(bedend>=7.3)) %>% mutate(bedtime_end=c("<6:30AM","6:30-7:00AM","7:00-7:30AM",">7:30AM")[end])
I had to play with various breakpoints before I arrived at the more or less decent version with enough records within each bucket:
I then used heatmap to visualize the mean differences in restorative sleep across timeslot combinations. Please note that I am not running any statistical significance tests. But I did include the sample sizes (in parentheses).
According to this heatmap, the best way to get the most out of my sleep is to go to bed before 10:30 pm and get up between 6:30 and 7:00 am. Interestingly, I can get almost the same proportion of restorative sleep if I go to bed after 11 pm and wake up between 6:30 and 7:00 am. What is also interesting is that all optimal values are clustered within the 6:30AM-7:30Am timeslot, and thus are driven mostly by the time of waking up, and not the time of going to bed.
The two potential issues I see with this analysis are:
- my choice of the metric (efficiency of REM + Deep sleep) may not necessarily be ideal or even correct
- the sample sizes are too small to make a definitive conclusion; I may need to start going to bed earlier more often.
I will certainly have to replicate this analysis once I get at least six months more of data.