Loading Oura Ring Data in R

Wed, Jul 15, 2020 3-minute read 485 words

In this post, I describe how I access my Oura ring data in R, and what data points I am interested in.

I have been using Oura ring for over a year now. The ring’s app has amazing interface, but as a data scientist, I am always interested in getting my hands on the actual raw data. With Oura, you can either download it from the site (go to My Account and scroll to Export data), or via API. This post focuses on using downloadable data; I have not had a chance yet to work with API.

Reading Oura Data in R

The data is currently available only in JSON format so first we explore its structure:

library(jsonlite)
rawoura<-fromJSON('./DATAIN/oura_2020-07-07T13-46-10.json')
lapply(rawoura,names)
> lapply(rawoura,names)
$activity
 [1] "average_met"             
 [2] "cal_active"              
 [3] "cal_total"               
 [4] "class_5min"              
 [5] "daily_movement"          
 [6] "day_end"                 
 [7] "day_start"               
 [8] "high"                    
 [9] "inactive"                
[10] "inactivity_alerts"       
[11] "low"                     
[12] "medium"                  
[13] "met_1min"                
[14] "met_min_high"            
[15] "met_min_inactive"        
[16] "met_min_low"             
[17] "met_min_medium"          
[18] "non_wear"                
[19] "rest"                    
[20] "score"                   
[21] "score_meet_daily_targets"
[22] "score_move_every_hour"   
[23] "score_recovery_time"     
[24] "score_stay_active"       
[25] "score_training_frequency"
[26] "score_training_volume"   
[27] "steps"                   
[28] "summary_date"            
[29] "target_calories"         
[30] "target_km"               
[31] "target_miles"            
[32] "timezone"                
[33] "to_target_km"            
[34] "to_target_miles"         
[35] "total"                   

$readiness
 [1] "period_id"             
 [2] "score"                 
 [3] "score_activity_balance"
 [4] "score_previous_day"    
 [5] "score_previous_night"  
 [6] "score_recovery_index"  
 [7] "score_resting_hr"      
 [8] "score_sleep_balance"   
 [9] "score_temperature"     
[10] "summary_date"          
[11] "score_hrv_balance"     

$restful_periods
 [1] "bedtime_end"   
 [2] "bedtime_start" 
 [3] "breath_average"
 [4] "duration"      
 [5] "hr_average"    
 [6] "hr_lowest"     
 [7] "period_id"     
 [8] "summary_date"  
 [9] "timezone"      
[10] "rmssd"         

$sleep
 [1] "awake"                      
 [2] "bedtime_end"                
 [3] "bedtime_end_delta"          
 [4] "bedtime_start"              
 [5] "bedtime_start_delta"        
 [6] "breath_average"             
 [7] "deep"                       
 [8] "duration"                   
 [9] "efficiency"                 
[10] "hr_5min"                    
[11] "hr_average"                 
[12] "hr_lowest"                  
[13] "hypnogram_5min"             
[14] "is_longest"                 
[15] "light"                      
[16] "midpoint_at_delta"          
[17] "midpoint_time"              
[18] "onset_latency"              
[19] "period_id"                  
[20] "rem"                        
[21] "restless"                   
[22] "rmssd"                      
[23] "rmssd_5min"                 
[24] "score"                      
[25] "score_alignment"            
[26] "score_deep"                 
[27] "score_disturbances"         
[28] "score_efficiency"           
[29] "score_latency"              
[30] "score_rem"                  
[31] "score_total"                
[32] "summary_date"               
[33] "temperature_delta"          
[34] "temperature_deviation"      
[35] "timezone"                   
[36] "total"                      
[37] "temperature_trend_deviation"

> 

I am only interested in Activity, Sleep and maybe Readiness lists, and only in specific data points.

activity<-flatten(data.frame(rawoura$activity))
activdat<-activity %>% select(summary_date,cal_active,cal_total,inactive,daily_movement,low,medium,high,steps,rest,score) %>% 
  rename(activescore=score)

The “min_1met” column is very interesting: it contains metabolic burn for each of the 1,440 minutes on each day. This allows me to get metabolic burns for mornings, afternoons and evenings:

minmets<-activity %>% select(summary_date,met_1min) %>% unnest(cols=c(met_1min)) %>% 
  group_by(summary_date) %>% mutate(min=seq_along(met_1min)) %>% 
  mutate(daypart=as.factor(ifelse(min <360, "AM",ifelse(min>1020,"EV", "DA")))) %>% 
  group_by(summary_date,daypart) %>% mutate(mets=round(sum(met_1min,na.rm=T),0)) %>% 
  ungroup() %>% distinct(summary_date,daypart,mets)

From the Sleep list, I only need several data points:

sleep<-flatten(data.frame(rawoura$sleep))
sleepdat<-sleep %>% 
  select(summary_date,bedtime_start,bedtime_end,duration,onset_latency,light,rem,deep,breath_average,
         hr_average,temperature_delta,score) %>% 
  rename(sleepscore=score) 

I am somewhat cautious about Oura’s readiness scores, but read them anyway:

readiness<-flatten(data.frame(rawoura$readiness))
readidat<-readiness %>% select(summary_date,score,score_previous_day,score_previous_night,score_resting_hr,score_sleep_balance,score_recovery_index) %>%rename(readiscore=score)

The combined data is available on GitHub as a part of the public release. The R code for retrieving Oura data is also available on GitHub here.

And just for fun, a coupe of charts of my metabolic activity and sleep before and during the COVID19 quarantine:

Oura ring metabolic burn by daypart

It looks like I am getting more Deep and REM sleep during the quarantine phase:

Oura ring sleep