{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a3f8a788",
   "metadata": {},
   "source": [
    "# An activity-list CSV: weekly training volume\n",
    "\n",
    "Exports of workouts (Strava's, Garmin's, Polar's, COROS's, once FIT or TCX files are turned into rows) reduce to one row per activity. This notebook sums minutes and distance per week and activity type and charts the weekly volume.\n",
    "\n",
    "**The input here is synthetic** (`activities-synthetic.csv` from [https://aifitnessapi.com/own-your-data](https://aifitnessapi.com/own-your-data#notebooks)), generated from a fixed seed. Its column names are **ours, not any vendor's**. Set `CSV` and `COLUMNS` for your own file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "be8f25a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os, urllib.request\n",
    "\n",
    "def local_or_download(path, url):\n",
    "    \"\"\"Use the file next to this notebook if it is there (a clone of the repo),\n",
    "    otherwise download the synthetic sample once (Colab).\"\"\"\n",
    "    if os.path.exists(path):\n",
    "        return path\n",
    "    name = os.path.basename(path)\n",
    "    if not os.path.exists(name):\n",
    "        urllib.request.urlretrieve(url, name)\n",
    "    return name\n",
    "\n",
    "CSV = local_or_download(\"data/activities-synthetic.csv\",\n",
    "                        \"https://aifitnessapi.com/notebooks/data/activities-synthetic.csv\")\n",
    "COLUMNS = {\"start_time\": \"start_time\", \"activity_type\": \"activity_type\",\n",
    "           \"duration_min\": \"duration_min\", \"distance_km\": \"distance_km\", \"avg_hr_bpm\": \"avg_hr_bpm\"}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "819b8001",
   "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</th>\n",
       "      <th>activity_type</th>\n",
       "      <th>duration_min</th>\n",
       "      <th>distance_km</th>\n",
       "      <th>avg_hr_bpm</th>\n",
       "      <th>active_kcal</th>\n",
       "      <th>start</th>\n",
       "      <th>week</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2026-06-01 19:48:00 -0700</td>\n",
       "      <td>Running</td>\n",
       "      <td>30.1</td>\n",
       "      <td>4.60</td>\n",
       "      <td>145</td>\n",
       "      <td>240</td>\n",
       "      <td>2026-06-01 19:48:00</td>\n",
       "      <td>2026-06-01</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2026-06-03 18:44:00 -0700</td>\n",
       "      <td>TraditionalStrengthTraining</td>\n",
       "      <td>48.2</td>\n",
       "      <td>NaN</td>\n",
       "      <td>127</td>\n",
       "      <td>514</td>\n",
       "      <td>2026-06-03 18:44:00</td>\n",
       "      <td>2026-06-01</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2026-06-05 18:02:00 -0700</td>\n",
       "      <td>Running</td>\n",
       "      <td>29.8</td>\n",
       "      <td>5.17</td>\n",
       "      <td>130</td>\n",
       "      <td>271</td>\n",
       "      <td>2026-06-05 18:02:00</td>\n",
       "      <td>2026-06-01</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2026-06-06 19:01:00 -0700</td>\n",
       "      <td>Cycling</td>\n",
       "      <td>28.6</td>\n",
       "      <td>13.65</td>\n",
       "      <td>147</td>\n",
       "      <td>203</td>\n",
       "      <td>2026-06-06 19:01:00</td>\n",
       "      <td>2026-06-01</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2026-06-08 18:50:00 -0700</td>\n",
       "      <td>Running</td>\n",
       "      <td>63.4</td>\n",
       "      <td>10.99</td>\n",
       "      <td>136</td>\n",
       "      <td>631</td>\n",
       "      <td>2026-06-08 18:50:00</td>\n",
       "      <td>2026-06-08</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                  start_time                activity_type  duration_min  \\\n",
       "0  2026-06-01 19:48:00 -0700                      Running          30.1   \n",
       "1  2026-06-03 18:44:00 -0700  TraditionalStrengthTraining          48.2   \n",
       "2  2026-06-05 18:02:00 -0700                      Running          29.8   \n",
       "3  2026-06-06 19:01:00 -0700                      Cycling          28.6   \n",
       "4  2026-06-08 18:50:00 -0700                      Running          63.4   \n",
       "\n",
       "   distance_km  avg_hr_bpm  active_kcal               start       week  \n",
       "0         4.60         145          240 2026-06-01 19:48:00 2026-06-01  \n",
       "1          NaN         127          514 2026-06-03 18:44:00 2026-06-01  \n",
       "2         5.17         130          271 2026-06-05 18:02:00 2026-06-01  \n",
       "3        13.65         147          203 2026-06-06 19:01:00 2026-06-01  \n",
       "4        10.99         136          631 2026-06-08 18:50:00 2026-06-08  "
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "a = pd.read_csv(CSV, comment=\"#\").rename(columns={v: k for k, v in COLUMNS.items()})\n",
    "# Keep the local wall-clock time as written: the offset is dropped, not converted.\n",
    "a[\"start\"] = pd.to_datetime(a[\"start_time\"].str[:19])\n",
    "a[\"week\"] = a[\"start\"].dt.to_period(\"W-SUN\").dt.start_time\n",
    "a.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cf24a499",
   "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>sessions</th>\n",
       "      <th>minutes</th>\n",
       "      <th>km</th>\n",
       "      <th>avg_hr</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>activity_type</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Cycling</th>\n",
       "      <td>17</td>\n",
       "      <td>763.9</td>\n",
       "      <td>341.2</td>\n",
       "      <td>138.8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Running</th>\n",
       "      <td>35</td>\n",
       "      <td>1729.4</td>\n",
       "      <td>288.8</td>\n",
       "      <td>134.7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TraditionalStrengthTraining</th>\n",
       "      <td>17</td>\n",
       "      <td>761.8</td>\n",
       "      <td>0.0</td>\n",
       "      <td>134.8</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                             sessions  minutes     km  avg_hr\n",
       "activity_type                                                \n",
       "Cycling                            17    763.9  341.2   138.8\n",
       "Running                            35   1729.4  288.8   134.7\n",
       "TraditionalStrengthTraining        17    761.8    0.0   134.8"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a.groupby(\"activity_type\").agg(sessions=(\"start\", \"size\"), minutes=(\"duration_min\", \"sum\"),\n",
    "                               km=(\"distance_km\", \"sum\"), avg_hr=(\"avg_hr_bpm\", \"mean\")).round(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f50a621",
   "metadata": {},
   "source": [
    "## Weekly minutes by activity\n",
    "\n",
    "A pivot table with weeks as rows and activity types as columns, then a stacked bar chart."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "08e8657d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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MpSqPvroI3r16kXVqlWNbY0aNWL//v3pOv5h+ALw8fGhZ8+elC1blly5chkvOW3evDn+/v5Uq1YNS0tLvvnmm1S3NkVERESeV+l+V6aHhwfHjh0jf/78xqOfCQkJdO/enXHjxmVpkc+id2WKiIiIWRubnyqlS6asYzY27dz5h9I9YrZ582YmT55M+/btjcn/VlZWf/tWpoiIiIiklu5UZWtrS5cuXbh27RqhoaHGdjs7O9zd3bOiNhEREZEXSoaGuwIDA9m9ezcAJpOJXbt24e3trWAmIiIi8gzPeh0TZDCYde7cmc6dOxufo6KiUi0GKyIiIiJ/3z96O3hsbCynTp3KrFpEREREXmgZGjGbPn26saxFUlISly5dMl5KLiIiIiL/TIaCWf/+/enRo4fxuWDBgsarkERERETkn8lQMMubNy9Xrlzh5MmTJCYmApA7d+5U885ERERE5O/J0Byz1atX0759e06ePMnVq1e5evUqQUFBWVWbiIiIyAslQyNme/fuZd68ebRo0SKr6hERERF5YWVoxKxDhw4cPHiQhISErKpHRERE5IWVoWD2+++/M23aNAoVKkSJEiUoUaIEderUyaraRERERF4oGbqVOXLkSEaOHJlVtYiIiIi80NIVzPz8/MifPz9xcXEcOXIk1b68efPy7rvvZklxIiIiIi+SdAWz3LlzkytXLgDy5MmTZp+IiIiI/HPpCmavvfaa8XOjRo2Ii4vLsoJEREREXlQZmmMWGBiIl5cXtra2WFlZAeDi4sKePXuyojYRERGRF0qGgtmCBQuYPn06HTp0yKp6RERERF5YGVouo3Llyty7dy+LShERERF5sWVoxKxjx440aNCAcePGYWtrC0CRIkXYt29flhQnIiIi8iLJUDD7/PPPeeedd3j//fextMzQYJuIiIiIPEOGglm5cuUoWrQoNjY2WVWPiIiIyAsrQ8HM3t6ed955h6+//tpYv6xQoUIsXrw4K2oTEREReaFkKJi1aNGCGjVqpNqm0TMRERGRzJGhYFa0aFGKFi36t08WFxeHhYWF8eAAQFhYGE5OTmnaRkZGYm9vn6qtiIiIyPMs22bwL1iwgJdeeokJEyYAEBwcTL169XjzzTepVq0a58+fByApKYm3336bVq1aUb16dX744YfsKlFEREQkR2VLMAsICOD3339n3LhxxrYxY8bwwQcfsHv3bqZPn86wYcMA+OWXX4iOjubgwYMcOnSISZMmae00EREReSFk6Fbm31WtWjV8fX1ZuHChsW3Pnj34+voC0Lx5c/r06UNycjLbtm2jZ8+eAOTLlw93d3f2799Pu3btUvXp6+trHB8REYHJZCIhIYH4+HjCwsKy47JERERE0sehgvFj2glc/y9bgtnj1jxLSEjA2vr/T1+gQAHCw8MJDQ3FxcXF2O7i4kJoaGia4wcMGMCAAQMAcHNzMx5CsLW1feycNREREZEcE30+Xc1ybJXYhy9Bf+j+/fs4OjpSqFChVLcuIyMjKVSoUDZXJyIiIpL9ciyYvfLKK8arnM6dO4ezszPW1tbUq1ePrVu3AikPAvz666/UqlUrp8oUERERyTbZcivzccaOHUvv3r1p1aoV27dvZ8aMGQB0796dZs2a8c4773DlyhVatmxJyZIlc6pMERERkWyTrcGsXbt2xMXFAVCrVi327t1LQEAAH374IS+99BKQ8naBPXv2cPjwYRwdHalatWp2ligiIiKSY7I1mD06qR/AyckJT0/PNO3s7Oxo0qRJdpUlIiIiYhZybI6ZiIiIiKSmYCYiIiJiJhTMRERERMyEgpmIiIiImVAwExERETETCmYiIiIiZkLBTERERMRMKJiJiIiImAkFMxEREREzoWAmIiIiYiYUzERERETMhIKZiIiIiJlQMBMRERExEwpmIiIiImZCwUxERETETCiYiYiIiJgJBTMRERERM6FgJiIiImImFMxEREREzISCmYiIiIiZUDATERERMRMKZiIiIiJmQsFMRERExEwomImIiIiYiRwLZuHh4Xh6ehr/ffDBB8a+DRs24OHhQadOnbh48WJOlSgiIiKSraxz6sRXrlzB3t6e7777DgAbGxsAAgMDGTduHJs3b+bixYu88cYbnDhxAisrq5wqVURERCRb5NiI2fXr1ylfvjwmkwlra2sKFiwIwPLlyxkyZAgvvfQSTZo0oVKlSuzfvz+nyhQRERHJNjk2YhYREcGmTZv4888/OXjwIBMmTMDHx4erV6/y+uuvG+3Kly/PlStXaNq0aarjfX198fX1NfoymUwkJCQQHx9PWFhYtl6LiIiIyFM5VDB+dHpKsxwLZj4+Pvj4+ABw8+ZNKleuTN++fbGxsSExMdFol5CQgK2tbZrjBwwYwIABAwBwc3MzboXa2tri5PS0SxYRERHJZtHn09Usx25lnj59mqCgIABcXFzIlSsXDx48oHz58hw/ftxod+LECcqXL59DVYqIiIhknxwbMTt79ixTp05lwoQJbNmyhRo1apA3b1769OmDp6cnZcuW5fLly9y/f5/atWvnVJkiIiIi2SbHglmnTp2wsrJi7dq1lClThokTJwJQokQJfvzxRxYvXoyjoyN+fn45VaKIiIhItrJITk5Ozuki/ik3NzfmzZuHyWTCwcGBevXq5XRJIiIiIv9vbP5Hfo58YjOt/C8iIiJiJhTMRERERLJYqdgVqf7/JApmIiIiItngWaEMFMxEREREzIaCmYiIiIiZUDATERERMRMKZiIiIiJmQsFMRERExEwomImIiIiYCQUzERERETOhYCYiIiJiJhTMRERERMyEgpmIiIiImVAwExERETETCmYiIiIiZkLBTERERMRMKJiJiIiImAkFMxEREREzoWAmIiIiYiYUzERERETMhIKZiIiIiJlQMBMRERExEwpmIiIiImZCwUxERETETFjndAGPExISwtq1aylQoABdunTBxsYmp0sSERERyXJmN2IWHh6Oh4cH1tbWHDt2jK5du+Z0SSIiIiLZwuxGzJYsWULv3r0ZOHAgAI0bN+bs2bNUqlQphysTERERyVpmF8wCAwPp06eP8blevXoEBgamCWa+vr74+voCcO7cOV577TWSk5OxtramSpUq2VlypoiMjCR//vw5XcY/9jxch67BPOgazIOuwXw8D9fxIl+D0yM/t2r1DVu3bn18w2Qz06lTp+RDhw4Zn0ePHp28cOHCpx5jaWmZDBj//Rv1798/p0vIFM/DdegazIOuwTzoGszH83AduoZnM7s5ZsWLF+fatWvG52vXrlG8ePEcrCh7eHl55XQJmeJ5uA5dg3nQNZgHXYP5eB6uQ9fwbBbJycnJWXqGDNq/fz+jRo1ix44dBAcH4+npyalTp8iVK9cTj7GysiIpKcn4bGaXJCIiIpIuZjfHrFGjRrRv3566detiZ2fHokWLnhrKAGrXrs3ly5eJjY3F2dk5myoVERERyVxmN2ImIiIi8qIyuzlmIiIiIi8qBTMRERERM2F2c8wedf36da5evcpff/1F2bJlKVu2LJaWqbPk3bt3iYmJoWjRosa2hIQEjhw5QmRkJA0bNuTYsWNcu3YNW1tbrK2tcXFxwdra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",
      "text/plain": [
       "<Figure size 720x230.4 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "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>activity_type</th>\n",
       "      <th>Cycling</th>\n",
       "      <th>Running</th>\n",
       "      <th>TraditionalStrengthTraining</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>week</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2026-06-01</th>\n",
       "      <td>29.0</td>\n",
       "      <td>60.0</td>\n",
       "      <td>48.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-08</th>\n",
       "      <td>37.0</td>\n",
       "      <td>123.0</td>\n",
       "      <td>55.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-15</th>\n",
       "      <td>42.0</td>\n",
       "      <td>122.0</td>\n",
       "      <td>28.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-22</th>\n",
       "      <td>61.0</td>\n",
       "      <td>124.0</td>\n",
       "      <td>69.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-29</th>\n",
       "      <td>39.0</td>\n",
       "      <td>109.0</td>\n",
       "      <td>30.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-06</th>\n",
       "      <td>69.0</td>\n",
       "      <td>117.0</td>\n",
       "      <td>37.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-13</th>\n",
       "      <td>50.0</td>\n",
       "      <td>65.0</td>\n",
       "      <td>32.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-20</th>\n",
       "      <td>37.0</td>\n",
       "      <td>61.0</td>\n",
       "      <td>66.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-27</th>\n",
       "      <td>61.0</td>\n",
       "      <td>78.0</td>\n",
       "      <td>42.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-03</th>\n",
       "      <td>45.0</td>\n",
       "      <td>92.0</td>\n",
       "      <td>60.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-10</th>\n",
       "      <td>38.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>53.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-17</th>\n",
       "      <td>32.0</td>\n",
       "      <td>121.0</td>\n",
       "      <td>32.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-24</th>\n",
       "      <td>48.0</td>\n",
       "      <td>85.0</td>\n",
       "      <td>28.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-31</th>\n",
       "      <td>38.0</td>\n",
       "      <td>96.0</td>\n",
       "      <td>61.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-07</th>\n",
       "      <td>38.0</td>\n",
       "      <td>105.0</td>\n",
       "      <td>52.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-14</th>\n",
       "      <td>40.0</td>\n",
       "      <td>114.0</td>\n",
       "      <td>30.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-21</th>\n",
       "      <td>62.0</td>\n",
       "      <td>95.0</td>\n",
       "      <td>40.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-28</th>\n",
       "      <td>0.0</td>\n",
       "      <td>64.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "activity_type  Cycling  Running  TraditionalStrengthTraining\n",
       "week                                                        \n",
       "2026-06-01        29.0     60.0                         48.0\n",
       "2026-06-08        37.0    123.0                         55.0\n",
       "2026-06-15        42.0    122.0                         28.0\n",
       "2026-06-22        61.0    124.0                         69.0\n",
       "2026-06-29        39.0    109.0                         30.0\n",
       "2026-07-06        69.0    117.0                         37.0\n",
       "2026-07-13        50.0     65.0                         32.0\n",
       "2026-07-20        37.0     61.0                         66.0\n",
       "2026-07-27        61.0     78.0                         42.0\n",
       "2026-08-03        45.0     92.0                         60.0\n",
       "2026-08-10        38.0    100.0                         53.0\n",
       "2026-08-17        32.0    121.0                         32.0\n",
       "2026-08-24        48.0     85.0                         28.0\n",
       "2026-08-31        38.0     96.0                         61.0\n",
       "2026-09-07        38.0    105.0                         52.0\n",
       "2026-09-14        40.0    114.0                         30.0\n",
       "2026-09-21        62.0     95.0                         40.0\n",
       "2026-09-28         0.0     64.0                          0.0"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams.update({\"figure.figsize\": (10, 3.2), \"axes.spines.top\": False, \"axes.spines.right\": False,\n",
    "                     \"axes.grid\": True, \"grid.alpha\": 0.3, \"figure.dpi\": 72})\n",
    "\n",
    "weekly = a.pivot_table(index=\"week\", columns=\"activity_type\", values=\"duration_min\", aggfunc=\"sum\").fillna(0)\n",
    "ax = weekly.plot(kind=\"bar\", stacked=True, width=0.85, title=\"Training minutes per week (synthetic)\")\n",
    "ax.set_xticks(range(len(weekly)))\n",
    "ax.set_xticklabels([d.strftime(\"%b %d\") for d in weekly.index], rotation=0, fontsize=8)\n",
    "ax.set_ylabel(\"minutes\"); ax.set_xlabel(\"week starting\")\n",
    "plt.show()\n",
    "weekly.round(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "528ada17",
   "metadata": {},
   "source": [
    "## Load change week to week\n",
    "\n",
    "A common rule of thumb compares each week with the average of the previous four. Here it is computed, not judged: what counts as too fast an increase is a coaching question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c6777731",
   "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>minutes</th>\n",
       "      <th>vs previous 4-week mean</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>week</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2026-06-01</th>\n",
       "      <td>137.0</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-08</th>\n",
       "      <td>214.0</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-15</th>\n",
       "      <td>191.0</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-22</th>\n",
       "      <td>254.0</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-06-29</th>\n",
       "      <td>178.0</td>\n",
       "      <td>0.90</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-06</th>\n",
       "      <td>223.0</td>\n",
       "      <td>1.06</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-13</th>\n",
       "      <td>146.0</td>\n",
       "      <td>0.69</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-20</th>\n",
       "      <td>164.0</td>\n",
       "      <td>0.82</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-07-27</th>\n",
       "      <td>182.0</td>\n",
       "      <td>1.02</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-03</th>\n",
       "      <td>197.0</td>\n",
       "      <td>1.10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-10</th>\n",
       "      <td>190.0</td>\n",
       "      <td>1.11</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-17</th>\n",
       "      <td>184.0</td>\n",
       "      <td>1.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-24</th>\n",
       "      <td>160.0</td>\n",
       "      <td>0.85</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-08-31</th>\n",
       "      <td>195.0</td>\n",
       "      <td>1.06</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-07</th>\n",
       "      <td>195.0</td>\n",
       "      <td>1.07</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-14</th>\n",
       "      <td>184.0</td>\n",
       "      <td>1.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-21</th>\n",
       "      <td>196.0</td>\n",
       "      <td>1.07</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-09-28</th>\n",
       "      <td>64.0</td>\n",
       "      <td>0.33</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            minutes  vs previous 4-week mean\n",
       "week                                        \n",
       "2026-06-01    137.0                      NaN\n",
       "2026-06-08    214.0                      NaN\n",
       "2026-06-15    191.0                      NaN\n",
       "2026-06-22    254.0                      NaN\n",
       "2026-06-29    178.0                     0.90\n",
       "2026-07-06    223.0                     1.06\n",
       "2026-07-13    146.0                     0.69\n",
       "2026-07-20    164.0                     0.82\n",
       "2026-07-27    182.0                     1.02\n",
       "2026-08-03    197.0                     1.10\n",
       "2026-08-10    190.0                     1.11\n",
       "2026-08-17    184.0                     1.00\n",
       "2026-08-24    160.0                     0.85\n",
       "2026-08-31    195.0                     1.06\n",
       "2026-09-07    195.0                     1.07\n",
       "2026-09-14    184.0                     1.00\n",
       "2026-09-21    196.0                     1.07\n",
       "2026-09-28     64.0                     0.33"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "total = weekly.sum(axis=1)\n",
    "ratio = (total / total.shift(1).rolling(4).mean()).round(2)\n",
    "pd.DataFrame({\"minutes\": total.round(0), \"vs previous 4-week mean\": ratio})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "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.17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
