1.x updates

This commit is contained in:
2026-05-19 08:34:22 -04:00
parent 3f3fce62d3
commit 9d91ac8ebc
53 changed files with 4541 additions and 2111 deletions

View File

@@ -4,41 +4,32 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# 04 Are you running more efficiently?\n",
"# 04 \u2014 Are you running more efficiently?\n",
"\n",
"Heart rate and speed as a function of distance, year over year.\n",
"\n",
"## Method\n",
"\n",
"**Efficiency metric:** *meters per heartbeat* = `distance_m / (duration_min × avg_HR)`.\n",
"**Efficiency metric:** *meters per heartbeat* = `distance_m / (duration_min \u00d7 avg_HR)`.\n",
"Higher = more forward motion produced per beat = more aerobically efficient.\n",
"\n",
"**Controlling for confounders:**\n",
"- Distance: longer runs naturally drift HR up and pace down we bucket by distance.\n",
"- Effort type: hard intervals are not comparable to easy aerobic runs we report an *all runs* view and an *easy runs only* (HR < 155) view.\n",
"- Distance: longer runs naturally drift HR up and pace down \u2192 we bucket by distance.\n",
"- Effort type: hard intervals are not comparable to easy aerobic runs \u2192 we report an *all runs* view and an *easy runs only* (HR < 155) view.\n",
"- Activity type: filtered to `running` (excludes trail, cycling, etc.)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"322 runs with HR + pace, 2022-2026\n"
]
}
],
"outputs": [],
"source": [
"import sys\n",
"sys.path.insert(0, '..')\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from analysis import open_conn, load_activities\n",
"from openrun import open_conn, load_activities\n",
"\n",
"pd.options.display.float_format = '{:.2f}'.format\n",
"conn = open_conn()\n",
@@ -178,10 +169,10 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Yearly summary (runs 5 km)\n",
"## Yearly summary (runs \u2265 5 km)\n",
"\n",
"If `m/beat` is *increasing* year over year more efficient.\n",
"If it's *decreasing* with HR roughly constant losing fitness *or* something is off (sensor, conditions)."
"If `m/beat` is *increasing* year over year \u2192 more efficient.\n",
"If it's *decreasing* with HR roughly constant \u2192 losing fitness *or* something is off (sensor, conditions)."
]
},
{
@@ -301,7 +292,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Meters per heartbeat the headline efficiency view\n",
"## Meters per heartbeat \u2014 the headline efficiency view\n",
"\n",
"One line per year. Each line shows the median efficiency at each distance bucket."
]
@@ -371,7 +362,7 @@
"\n",
"pace_pv.plot(ax=ax2, marker='o')\n",
"ax2.set_title('Median pace by distance')\n",
"ax2.set_ylabel('min/km (faster )'); ax2.grid(alpha=0.3); ax2.legend(title='Year')\n",
"ax2.set_ylabel('min/km (faster \u2193)'); ax2.grid(alpha=0.3); ax2.legend(title='Year')\n",
"plt.tight_layout()"
]
},
@@ -379,7 +370,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pace vs HR scatter every run, colored by year\n",
"## Pace vs HR scatter \u2014 every run, colored by year\n",
"\n",
"If you're getting more efficient, points should drift **right and up** (faster pace at same HR) over time."
]
@@ -407,7 +398,7 @@
" alpha=0.5, label=str(yr))\n",
"ax.invert_yaxis()\n",
"ax.set_xlabel('Avg HR (bpm)')\n",
"ax.set_ylabel('Pace (min/km, faster )')\n",
"ax.set_ylabel('Pace (min/km, faster \u2191)')\n",
"ax.set_title('Every run, sized by distance, colored by year')\n",
"ax.legend(title='Year')\n",
"ax.grid(alpha=0.3)\n",
@@ -418,9 +409,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Easy runs only (HR < 155, 5km)\n",
"## Easy runs only (HR < 155, \u22655km)\n",
"\n",
"This is the cleanest comparison aerobic baseline pace at a given heart-rate ceiling.\n",
"This is the cleanest comparison \u2014 aerobic baseline pace at a given heart-rate ceiling.\n",
"Hard sessions vary a lot; easy runs are more reproducible."
]
},
@@ -563,7 +554,7 @@
"ax.scatter(easy['start_time_local'], easy['mpb'], alpha=0.4, s=easy['distance_km']*4, label='Run (sized by km)')\n",
"monthly.plot(ax=ax, color='C3', lw=2, marker='o', label='Monthly median')\n",
"ax.set_ylabel('Meters per heartbeat')\n",
"ax.set_title(f'Easy-run efficiency over time (HR < {HR_CEIL}, 5km)')\n",
"ax.set_title(f'Easy-run efficiency over time (HR < {HR_CEIL}, \u22655km)')\n",
"ax.legend()\n",
"ax.grid(alpha=0.3)\n",
"plt.tight_layout()"
@@ -573,7 +564,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Curve fits HR and pace as a function of distance, per year\n",
"## Curve fits \u2014 HR and pace as a function of distance, per year\n",
"\n",
"Smooth curves instead of bucketed bars, so you can see the shape clearly."
]
@@ -615,7 +606,7 @@
"\n",
"ax2.set_xscale('log')\n",
"ax2.invert_yaxis()\n",
"ax2.set_xlabel('Distance (km, log)'); ax2.set_ylabel('Pace (min/km, faster )')\n",
"ax2.set_xlabel('Distance (km, log)'); ax2.set_ylabel('Pace (min/km, faster \u2191)')\n",
"ax2.set_title('Pace vs distance, per year'); ax2.legend(title='Year'); ax2.grid(alpha=0.3)\n",
"plt.tight_layout()"
]
@@ -771,4 +762,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
}
}