Files
openrun/examples/notebooks/01_overview.ipynb

669 lines
133 KiB
Plaintext
Raw Normal View History

2026-05-18 12:53:24 -04:00
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
2026-06-12 06:25:45 -04:00
"# 01 — Overview\n",
2026-05-18 12:53:24 -04:00
"\n",
"Sanity-check the synced data: what's there, how complete it is, and what you've been up to recently.\n",
"\n",
"**Kernel:** pick the `.venv` from `garmin/` (top-right of this notebook)."
]
},
{
"cell_type": "code",
2026-06-12 06:25:45 -04:00
"execution_count": 1,
2026-05-18 12:53:24 -04:00
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"sys.path.insert(0, '..') # so we can import analysis.py from the project root\n",
"\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
2026-05-19 08:34:22 -04:00
"from openrun import open_conn, load_activities, load_wellness\n",
2026-05-18 12:53:24 -04:00
"\n",
"pd.options.display.float_format = '{:.2f}'.format\n",
"conn = open_conn()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data coverage"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>table</th>\n",
" <th>rows</th>\n",
" <th>from</th>\n",
" <th>to</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>activities</td>\n",
2026-06-12 06:25:45 -04:00
" <td>378</td>\n",
" <td>1658945478000.0</td>\n",
2026-05-18 12:53:24 -04:00
" <td>2026-05-10 10:43:58</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>activity_fit_files</td>\n",
2026-06-12 06:25:45 -04:00
" <td>349</td>\n",
2026-05-18 12:53:24 -04:00
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>activity_splits</td>\n",
" <td>2285</td>\n",
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
2026-06-12 06:25:45 -04:00
" <td>activity_time_in_zone</td>\n",
" <td>345</td>\n",
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_body_battery</td>\n",
" <td>365</td>\n",
" <td>2025-05-13</td>\n",
" <td>2026-05-12</td>\n",
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>5</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_hrv</td>\n",
" <td>363</td>\n",
" <td>2025-05-13</td>\n",
" <td>2026-05-12</td>\n",
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>6</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_intensity_minutes</td>\n",
" <td>365</td>\n",
" <td>2025-05-13</td>\n",
" <td>2026-05-12</td>\n",
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>7</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_resting_hr</td>\n",
" <td>363</td>\n",
" <td>2025-05-13</td>\n",
" <td>2026-05-11</td>\n",
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>8</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_sleep</td>\n",
2026-06-12 06:25:45 -04:00
" <td>1462</td>\n",
" <td>2022-04-08</td>\n",
" <td>2026-05-17</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>9</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_steps</td>\n",
2026-06-12 06:25:45 -04:00
" <td>1497</td>\n",
" <td>2022-04-08</td>\n",
" <td>2026-05-17</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>10</th>\n",
2026-05-18 12:53:24 -04:00
" <td>daily_stress</td>\n",
" <td>365</td>\n",
" <td>2025-05-13</td>\n",
" <td>2026-05-12</td>\n",
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>11</th>\n",
" <td>manual_activities</td>\n",
" <td>0</td>\n",
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>12</th>\n",
" <td>race_plan</td>\n",
" <td>0</td>\n",
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>13</th>\n",
" <td>sqlite_sequence</td>\n",
" <td>0</td>\n",
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>14</th>\n",
2026-05-18 12:53:24 -04:00
" <td>sync_state</td>\n",
2026-06-12 06:25:45 -04:00
" <td>3</td>\n",
2026-05-18 12:53:24 -04:00
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
2026-06-12 06:25:45 -04:00
" table rows from to\n",
"0 activities 378 1658945478000.0 2026-05-10 10:43:58\n",
"1 activity_fit_files 349 \n",
"2 activity_splits 2285 \n",
"3 activity_time_in_zone 345 \n",
"4 daily_body_battery 365 2025-05-13 2026-05-12\n",
"5 daily_hrv 363 2025-05-13 2026-05-12\n",
"6 daily_intensity_minutes 365 2025-05-13 2026-05-12\n",
"7 daily_resting_hr 363 2025-05-13 2026-05-11\n",
"8 daily_sleep 1462 2022-04-08 2026-05-17\n",
"9 daily_steps 1497 2022-04-08 2026-05-17\n",
"10 daily_stress 365 2025-05-13 2026-05-12\n",
"11 manual_activities 0 \n",
"12 race_plan 0 \n",
"13 sqlite_sequence 0 \n",
"14 sync_state 3 "
2026-05-18 12:53:24 -04:00
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"summary = []\n",
"for (table,) in conn.execute(\"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name\"):\n",
" n = conn.execute(f'SELECT COUNT(*) FROM \"{table}\"').fetchone()[0]\n",
" date_col = 'start_time_local' if table == 'activities' else ('calendar_date' if table.startswith('daily_') else None)\n",
" if date_col and n:\n",
" mn, mx = conn.execute(f'SELECT MIN({date_col}), MAX({date_col}) FROM {table}').fetchone()\n",
" else:\n",
" mn = mx = ''\n",
" summary.append({'table': table, 'rows': n, 'from': mn, 'to': mx})\n",
"pd.DataFrame(summary)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Activity types"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>count</th>\n",
" </tr>\n",
" <tr>\n",
" <th>activity_type</th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>running</th>\n",
2026-06-12 06:25:45 -04:00
" <td>335</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>trail_running</th>\n",
" <td>15</td>\n",
" </tr>\n",
" <tr>\n",
" <th>cycling</th>\n",
2026-06-12 06:25:45 -04:00
" <td>9</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
2026-06-12 06:25:45 -04:00
" <th>transition_v2</th>\n",
" <td>7</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>open_water_swimming</th>\n",
2026-06-12 06:25:45 -04:00
" <td>6</td>\n",
" </tr>\n",
" <tr>\n",
" <th>multi_sport</th>\n",
" <td>4</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>assistance</th>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>mountain_biking</th>\n",
" <td>1</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" count\n",
"activity_type \n",
2026-06-12 06:25:45 -04:00
"running 335\n",
2026-05-18 12:53:24 -04:00
"trail_running 15\n",
2026-06-12 06:25:45 -04:00
"cycling 9\n",
"transition_v2 7\n",
"open_water_swimming 6\n",
2026-05-18 12:53:24 -04:00
"multi_sport 4\n",
"assistance 1\n",
"mountain_biking 1"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"activities = load_activities(conn)\n",
"activities['activity_type'].value_counts().to_frame('count')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Last 10 activities"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>start_time_local</th>\n",
" <th>activity_type</th>\n",
" <th>activity_name</th>\n",
" <th>distance_km</th>\n",
" <th>duration_min</th>\n",
" <th>pace_min_per_km</th>\n",
" <th>avg_hr</th>\n",
" <th>training_load</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
2026-05-18 12:53:24 -04:00
" <td>running</td>\n",
" <td>Milford Running</td>\n",
2026-06-12 06:25:45 -04:00
" <td>12.94</td>\n",
" <td>105.89</td>\n",
" <td>8.09</td>\n",
" <td>163.00</td>\n",
" <td>101.28</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>transition_v2</td>\n",
" <td>Transition</td>\n",
" <td>24.03</td>\n",
" <td>75.39</td>\n",
" <td>3.01</td>\n",
" <td>116.00</td>\n",
" <td>90.98</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>open_water_swimming</td>\n",
" <td>Open Water Swimming</td>\n",
" <td>0.52</td>\n",
" <td>23.45</td>\n",
" <td>NaN</td>\n",
" <td>137.00</td>\n",
" <td>55.14</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
2026-05-18 12:53:24 -04:00
" <td>running</td>\n",
2026-06-12 06:25:45 -04:00
" <td>Running</td>\n",
" <td>4.88</td>\n",
" <td>25.64</td>\n",
" <td>5.17</td>\n",
" <td>169.00</td>\n",
" <td>134.45</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>transition_v2</td>\n",
" <td>Transition 2</td>\n",
" <td>3.89</td>\n",
" <td>715.77</td>\n",
" <td>NaN</td>\n",
" <td>107.00</td>\n",
" <td>10.10</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>cycling</td>\n",
" <td>Cycling</td>\n",
" <td>18.98</td>\n",
" <td>56.46</td>\n",
" <td>NaN</td>\n",
" <td>100.00</td>\n",
" <td>10.10</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>transition_v2</td>\n",
" <td>Transition 1</td>\n",
" <td>4.44</td>\n",
" <td>2117.33</td>\n",
" <td>NaN</td>\n",
" <td>87.00</td>\n",
" <td>8.48</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>open_water_swimming</td>\n",
" <td>Open Water Swimming</td>\n",
" <td>0.80</td>\n",
" <td>32.80</td>\n",
" <td>NaN</td>\n",
" <td>106.00</td>\n",
" <td>8.48</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
2026-05-18 12:53:24 -04:00
" <td>running</td>\n",
2026-06-12 06:25:45 -04:00
" <td>Running</td>\n",
" <td>5.09</td>\n",
" <td>25.35</td>\n",
" <td>4.97</td>\n",
" <td>162.00</td>\n",
" <td>173.71</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
2026-06-12 06:25:45 -04:00
" <td>NaT</td>\n",
" <td>transition_v2</td>\n",
" <td>Transition 2</td>\n",
" <td>0.77</td>\n",
" <td>137.95</td>\n",
" <td>NaN</td>\n",
" <td>118.00</td>\n",
" <td>5.90</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
2026-06-12 06:25:45 -04:00
" start_time_local activity_type activity_name distance_km \\\n",
"0 NaT running Milford Running 12.94 \n",
"1 NaT transition_v2 Transition 24.03 \n",
"2 NaT open_water_swimming Open Water Swimming 0.52 \n",
"3 NaT running Running 4.88 \n",
"4 NaT transition_v2 Transition 2 3.89 \n",
"5 NaT cycling Cycling 18.98 \n",
"6 NaT transition_v2 Transition 1 4.44 \n",
"7 NaT open_water_swimming Open Water Swimming 0.80 \n",
"8 NaT running Running 5.09 \n",
"9 NaT transition_v2 Transition 2 0.77 \n",
2026-05-18 12:53:24 -04:00
"\n",
" duration_min pace_min_per_km avg_hr training_load \n",
2026-06-12 06:25:45 -04:00
"0 105.89 8.09 163.00 101.28 \n",
"1 75.39 3.01 116.00 90.98 \n",
"2 23.45 NaN 137.00 55.14 \n",
"3 25.64 5.17 169.00 134.45 \n",
"4 715.77 NaN 107.00 10.10 \n",
"5 56.46 NaN 100.00 10.10 \n",
"6 2117.33 NaN 87.00 8.48 \n",
"7 32.80 NaN 106.00 8.48 \n",
"8 25.35 4.97 162.00 173.71 \n",
"9 137.95 NaN 118.00 5.90 "
2026-05-18 12:53:24 -04:00
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cols = ['start_time_local', 'activity_type', 'activity_name', 'distance_km', 'duration_min', 'pace_min_per_km', 'avg_hr', 'training_load']\n",
"activities[cols].tail(10).iloc[::-1].reset_index(drop=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20e72ec0",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Monthly running mileage, year-over-year"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
2026-06-12 06:25:45 -04:00
"image/png": "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
2026-05-18 12:53:24 -04:00
"text/plain": [
"<Figure size 1100x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"runs = activities[activities['activity_type'].str.contains('running', case=False, na=False)].copy()\n",
"monthly = (runs.assign(month_no=runs['start_time_local'].dt.month)\n",
" .groupby(['year', 'month_no'])['distance_km'].sum()\n",
" .unstack('year'))\n",
"\n",
"fig, ax = plt.subplots(figsize=(11, 4))\n",
"monthly.plot(ax=ax, marker='o')\n",
"ax.set_xticks(range(1, 13))\n",
"ax.set_xticklabels(['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'])\n",
"ax.set_ylabel('km')\n",
"ax.set_xlabel('')\n",
2026-06-12 06:25:45 -04:00
"ax.set_title('Monthly running km — year over year')\n",
2026-05-18 12:53:24 -04:00
"ax.legend(title='Year')\n",
"ax.grid(alpha=0.3)\n",
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Wellness completeness (last 90 days)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>% missing</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>total_steps</th>\n",
" <td>0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>sleep_score</th>\n",
2026-06-12 06:25:45 -04:00
" <td>100.00</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>avg_stress</th>\n",
2026-06-12 06:25:45 -04:00
" <td>6.67</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>hrv_last_night</th>\n",
2026-06-12 06:25:45 -04:00
" <td>13.33</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>resting_hr</th>\n",
2026-06-12 06:25:45 -04:00
" <td>7.78</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" <tr>\n",
" <th>bb_charged</th>\n",
2026-06-12 06:25:45 -04:00
" <td>7.78</td>\n",
2026-05-18 12:53:24 -04:00
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" % missing\n",
"total_steps 0.00\n",
2026-06-12 06:25:45 -04:00
"sleep_score 100.00\n",
"avg_stress 6.67\n",
"hrv_last_night 13.33\n",
"resting_hr 7.78\n",
"bb_charged 7.78"
2026-05-18 12:53:24 -04:00
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"w = load_wellness(conn).tail(90)\n",
"missing = w[['total_steps','sleep_score','avg_stress','hrv_last_night','resting_hr','bb_charged']].isna().mean().to_frame('% missing') * 100\n",
"missing"
]
}
],
"metadata": {
"kernelspec": {
2026-06-12 06:25:45 -04:00
"display_name": "garmin (3.13.13)",
2026-05-18 12:53:24 -04:00
"language": "python",
2026-06-12 06:25:45 -04:00
"name": "python3"
2026-05-18 12:53:24 -04:00
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
2026-06-12 06:25:45 -04:00
}