Automatically Push Your Oura Ring Data to Google Sheets with Python

samcha
4 min readDec 26, 2022
https://github.com/samchaaa/oura_to_sheets

Repo here: https://github.com/samchaaa/oura_to_sheets

Here are some quick code snippets to get you started pulling your Oura ring data, transforming it in python, and pushing to Google Sheets.

This assumes familiarity with python, Google Sheets, and Google Cloud. If not, there are links for further instructions.

First the setup and code, then commentary.

The code

This code uses the oura-ring wrapper for Oura API requests, and gspread for Google Sheets.

Here are all the steps to setting up the system:
1. git clone https://github.com/samchaaa/oura_to_sheets

2. Get your Oura API personal access token, paste in oura_key.txt

3. File > Make a copy, this Google Sheet, copy the sheet id to config.py

4. Set up Google Cloud service account, download .json key and paste in svc_acct.json

5. Share the Google Sheet with the service account

All of this info is kept in config.py, which is pulled into the python script or notebook, whichever you want to use.

For further instructions on setting up gspread and the Google service account, see the gspread docs for the best explanation.

Running the code

python update_sheets.py to run the update.

There are two ways to run the code: the notebook (notebook.ipynb), or the python script (update_sheets.py). Both are the same end result.

The notebook notebook.ipynb is available for digging into the data, creating visualizations, and developing new transformations of the different Oura endpoints. At the end, it still pushes to Google Sheets.

The script update_sheets.py is for quick updates. This is also useful if you want to schedule and run this task in the cloud — for example, using PythonAnywhere (shameless affiliate link).

Commentary

How the sheet works

The script pushes data to the “data” tab on the sheet:

These columns then get pulled into the main sheet (“202212”) via VLOOKUP:
=ifna(vlookup(A2, data!A:C, 2, false), “-”)

Note the VLOOKUP is dependent on the formats of the date in “202212” col A:A and “data” col A:A. If you change either one, make sure either they both match or the code is updated accordingly.

Explanation of transformations

The Oura API does a pretty good job of cleaning and consolidating your data. Most top-level metrics (by day) you may probably just pull right into the sheet.

That being said, if you are doing shift work or traveling, there are a few tweaks you may make to clean up your data.

Converting timezone (heart rate data)

# set timezone to local
hr = client.get_heart_rate(ydy, tdy)
hr = pd.DataFrame(hr).set_index('timestamp').drop(columns=['source'])
hr.index = [pd.to_datetime(_.replace('+00:00', '')) for _ in hr.index]
hr.index = hr.index.tz_localize('UTC')
hr.index = hr.index.tz_convert('Asia/Seoul')
# hr.plot(figsize=(13,5));

By default the heart rate data is in UTC time. (I don’t know if this is from the Oura API, or the oura-ring wrapper.) This simply transforms into a timezone of your choice.

Aggregating sleep by day

If you have fragmented sleep, or nap around the clock, this is useful for getting your total sleep by day.

# filter sleep to what we need and engineer duration
s = [
{
x: _[x] for x in _
if x in [
'day',
'bedtime_end',
'bedtime_start',
'total_sleep_duration',
]
} for _ in sleep
]
for _ in s:
_['dur'] = (
pd.to_datetime(_['bedtime_end']) -
pd.to_datetime(_['bedtime_start'])
)
# convert seconds to hours
total_sleep = pd.DataFrame(s)[
['day', 'total_sleep_duration']
].groupby('day').sum() / 60 / 60

This simply uses pd.to_datetime() to convert whatever datetime format into a python datetime object. By default, it is in seconds. Then we groupby on date, sum up all your sleep sessions, and convert into hours.

Converting date to excel date

This makes it easier to have the VLOOKUP formula in the sheet work.

def excel_date(date1):
"""
converts datetime date to excel int date
https://stackoverflow.com/a/9574948
"""
temp = datetime.datetime(1899, 12, 30) # Note, not 31st Dec but 30th!
delta = date1 - temp
return float(delta.days) + (float(delta.seconds) / 86400)

This just converts your python datetime date to an excel-style integer date.

Further steps

Here are some other steps I’d take to streamline these updates or expand further.

Put it in Google Apps Script

If you want to cut out a few steps from this process, you could pull the data directly into Google Apps Script and transform there. I just find python easier.

Automate the app update

One issue with the Oura update flow is that you need to open the app in order to push your data to from the ring, to the phone, to the cloud.

Here are some ways to automate this push, from users on r/ouraring.

Engineer other metrics

There are tons of other endpoints to use, via the oura-ring API.

get_daily_activity(): aggregate calories burnt

get_workouts(): get in-depth heartrate, HRV, and calories from recorded workouts

get_tags(): get tag frequencies

get_sleep_periods(): aggregate time in REM, deep sleep, sleep latency, etc.

Put it in the cloud

PythonAnywhere (shameless affiliate link)

Feedback and ideas for improvement welcome! Find me via GitHub: https://github.com/samchaaa/

--

--

samcha

Python, trading, data viz. Get access to samchaaa++ for ready-to-implement algorithms and quantitative studies: https://samchaaa.substack.com/