Files
bio-performx/notebook.ipynb
T

1551 lines
801 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
"execution_count": 38,
"id": "63f43af5",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import seaborn as sns\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "b0ee2af1",
"metadata": {},
"outputs": [
2025-09-23 19:20:36 +01:00
{
"name": "stdout",
"output_type": "stream",
"text": [
"Smoothed columns created:\n",
2025-09-23 20:36:24 +01:00
"['VO2(ml/min)_smoothed', 'VCO2(ml/min)_smoothed', 'HR(bpm)_smoothed', 'VT(l)_smoothed', 'BF(bpm)_smoothed', 'VE(l/min)_smoothed', 'VO2 Pulse_smoothed', 'VO2 Breath_smoothed', 'CHO_smoothed', 'FAT_smoothed']\n"
2025-09-23 19:20:36 +01:00
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/tmp/ipykernel_226264/622539462.py:3: FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead\n",
" df = df.apply(pd.to_numeric, errors='ignore')\n"
]
}
],
"source": [
"df = pd.read_csv('data/Pnoe_20250729_1550-Moran_Keirstyn.csv', delimiter=';')\n",
"# Convert all columns to numeric where possible, coercing errors to NaN\n",
"df = df.apply(pd.to_numeric, errors='ignore')\n",
2025-09-23 18:47:10 +01:00
"df['VO2 Pulse'] = df['VO2(ml/min)'] / df['HR(bpm)'] # VO2 Pulse in mL/beat\n",
"df['VO2 Breath'] = df['VO2(ml/min)'] / df['BF(bpm)'] # VO2 per Breath in mL/breath\n",
2025-09-23 20:36:24 +01:00
"df['CHO'] = df['EE(kcal/min)'] * df['CARBS(%)']/100\n",
"df['FAT'] = df['EE(kcal/min)'] * df['FAT(%)']/100\n",
2025-09-23 19:20:36 +01:00
"# Smooth key columns using rolling window\n",
"window_size = 10\n",
"\n",
"# List of columns to smooth\n",
2025-09-23 20:36:24 +01:00
"columns_to_smooth = ['VO2(ml/min)', 'VCO2(ml/min)', 'HR(bpm)', 'VT(l)', 'BF(bpm)', 'VE(l/min)', 'VO2 Pulse', 'VO2 Breath', 'CHO', 'FAT']\n",
2025-09-23 19:20:36 +01:00
"\n",
"# Apply smoothing to each column\n",
"for col in columns_to_smooth:\n",
" if col in df.columns:\n",
" df[f'{col}_smoothed'] = df[col].rolling(window=window_size, min_periods=1).mean()\n",
2025-09-23 19:20:36 +01:00
"\n",
"print(\"Smoothed columns created:\")\n",
"print([col for col in df.columns if '_smoothed' in col])"
]
},
{
"cell_type": "code",
"execution_count": 40,
2025-09-23 18:47:10 +01:00
"id": "fbd292c3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"22.369999999999997\n"
2025-09-23 18:47:10 +01:00
]
}
],
"source": [
"print(df['VO2 Pulse'].max())"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "ef8bc7ac",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1800x500 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
2025-09-23 18:17:06 +01:00
"first_unique_phase = df.drop_duplicates(subset='PHASE')\n",
"phase_times = first_unique_phase['T(sec)'].tolist()\n",
"\n",
"plt.figure(figsize=(18, 5))\n",
"ax1 = plt.subplot()\n",
"\n",
2025-09-23 19:20:36 +01:00
"\n",
"# Plot VT with step-like appearance\n",
2025-09-23 19:20:36 +01:00
"line1 = sns.lineplot(data=df, x='T(sec)', y='VT(l)_smoothed', label='VT (L)')\n",
"ax1.set_xlabel('Time (sec)')\n",
"ax1.set_ylabel('VT (L)')\n",
"# ax1.set_title('Respiratory')\n",
"ax1.grid(True, alpha=0.1)\n",
"ax1.set_ylim(0, min(8, df['VT(l)_smoothed'].max()))\n",
"# Plot speed as step function on secondary y-axis\n",
"ax2 = ax1.twinx()\n",
"ax1.set_xticks(np.arange(0, df['T(sec)'].max() + 200, 200))\n",
"line2 = sns.lineplot(data=df, x='T(sec)', y='Speed', color='green', ax=ax2, \n",
" drawstyle='steps-post', linewidth=2, label='Speed')\n",
"ax2.set_ylabel('Speed')\n",
"ax2.set_ylim(0, min(30, df['Speed'].max()) + 1)\n",
"\n",
"# Remove default legends first\n",
"ax1.get_legend().remove()\n",
"ax2.get_legend().remove()\n",
"\n",
"# Combine legends from both axes in the top left\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
"ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')\n",
"\n",
"# Add colored background regions if you have phase information\n",
"ax1.axvspan(0, phase_times[1], alpha=0.2, color='lightblue')\n",
"ax1.axvspan(phase_times[1], phase_times[2], alpha=0.2, color='purple')\n",
"ax1.axvspan(phase_times[2], phase_times[3], alpha=0.2, color='lightgreen')\n",
"ax1.axvspan(phase_times[3], df['T(sec)'].max(), alpha=0.2, color='blue')\n",
"\n",
"plt.savefig('graphs/respiratory.png', dpi=300, bbox_inches='tight')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 42,
2025-09-23 18:17:06 +01:00
"id": "06244aa2",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAAL9CAYAAADEnC/JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd0VNXexvHvJKRAOhAIPcQQelGQWPAKipSLCCIqCFyCqKDYCyqvIggKdiwIXr1KFLvS7lVAUKkCSu8BQg8hgCaEBFJn3j+2M8mQhLRJg+ez1qyZOWefffbAAcIz+/y2xWaz2RARERERERERERERkTzcKnoAIiIiIiIiIiIiIiKVlUJ0EREREREREREREZECKEQXERERERERERERESmAQnQRERERERERERERkQIoRBcRERERERERERERKYBCdBERERERERERERGRAihEFxEREREREREREREpgEJ0EREREREREREREZECKEQXERERERERERERESmAQnQRERERERERERERkQJUqhB9ypQpXHnllfj5+VGnTh369+9PTEyMU5u0tDTGjBlDrVq18PX15bbbbiMhIeGC/dpsNsaPH0+9evWoXr063bt3Z+/evWX5UURERERERERERETkIlCpQvTly5czZswY1q5dy5IlS8jMzKRHjx6kpqY62jz22GP897//5dtvv2X58uUcO3aMAQMGXLDfV199lXfeeYeZM2eybt06fHx86NmzJ2lpaWX9kURERERERERERESkCrPYbDZbRQ+iICdPnqROnTosX76cf/zjH5w+fZrg4GC++OILBg4cCMDu3btp2bIla9as4aqrrsrTh81mo379+jzxxBM8+eSTAJw+fZq6desya9YsBg0aVK6fSURERERERERERESqjmoVPYALOX36NAA1a9YEYMOGDWRmZtK9e3dHmxYtWtC4ceMCQ/QDBw5w/Phxp2MCAgKIjIxkzZo1+Ybo6enppKenO95nZWWxa9cuGjVqhJtbpZq8LyIiIiIiIiIiIuISVquVhIQELr/8cqpVq9TRcbmqtL8SVquVRx99lGuvvZY2bdoAcPz4cTw9PQkMDHRqW7duXY4fP55vP/btdevWLfIxU6ZMYeLEiaX8BCIiIiIiIiIiIiJVz++//86VV15Z0cOoNCptiD5mzBi2b9/OqlWryv3czz77LI8//rjj/ZEjR2jTpg2///479erVK/fxSOGSkpLyfLkiUhhdN1JSunakJHTdSEnoupGS0rUjJaHrRkpC142UhK6byis+Pp7OnTvnmZB8qauUIfqDDz7I//73P1asWEHDhg0d20NCQsjIyMjzBy0hIYGQkJB8+7JvT0hIcArAExIS6NChQ77HeHl54eXl5XgfEBAAQL169ZzGI5WHj48PQUFBFT0MqWJ03UhJ6dqRktB1IyWh60ZKSteOlISuGykJXTdSErpuKj+VtHZWqX41bDYbDz74IHPnzuWXX36hadOmTvs7duyIh4cHP//8s2NbTEwMhw8f5uqrr863z6ZNmxISEuJ0THJyMuvWrSvwGBERERERERERERERqGQh+pgxY5g9ezZffPEFfn5+HD9+nOPHj3Pu3DnAzAgfOXIkjz/+OL/++isbNmxgxIgRXH311U6LirZo0YK5c+cCYLFYePTRR5k8eTILFixg27Zt/Otf/6J+/fr079+/Ij6miIiIiIiIiIiIiFQRlaqcy4wZMwDo2rWr0/ZPPvmEqKgoAN566y3c3Ny47bbbSE9Pp2fPnrz//vtO7WNiYjh9+rTj/dixY0lNTeW+++4jKSmJLl26sGjRIry9vcv084iIiIiIiIiIiIhI1VapQnSbzVZoG29vb6ZPn8706dOL3I/FYuHFF1/kxRdfLPUYLyQrK4uMjIwyPYfkLz09nbNnz5b5eTw9PalWrVL9sREREREREREREZEypDTQBWw2G4cPH+bUqVMVPZRLWlxcXLmcp3bt2jRu3BiLxVIu5xMREREREREREZGKoxDdBewBeoMGDfD19dXqtRcpq9VKSkqKI6xv0qRJBY9IREREREREREREyppC9FLKyspyBOghISEVPRwpY76+voCZ9W6z2QgNDa3YAYmIiIiIiIiIiEiZ0pTpUrLXQLeHq3Lxs/9eL126lG3btlXwaERERERERERERKQsKUR3EZVwuXTk/r3+5ZdfOHz4cAWORkRERERERERERMqSkl+REgoKCuLs2bMkJiZW9FBERERERERERESkjChEFykFi8VCVlZWRQ9DREREREREREREyogWFi1Dfd9dVW7n+u9DXYp9TFRUFNHR0Xm27927l/Dw8AKPmzVrFo8++ihJSUnFPqeIiIiIiIiIiIhIVaIQ/RLXq1cvPvnkE6dtwcHBFTQaERERERERERERkcpF5VwucV5eXoSEhDg93n77bdq2bYuPjw+NGjXigQceICUlBYBly5YxYsQITp8+jcViwWKxMGHChIr9ECIiIiIiIiIiIiJlRCG65OHm5sY777zDjh07iI6O5pdffmHs2LEAXHPNNUybNg1/f3/i4+OJj4/nySefrOARi4iIiIiIiIiIiJQNlXO5xP3vf//D19fX8b537958++23jvehoaFMnjyZ0aNH8/777+Pp6UlAQAAWi4WQkJCKGLKIiIiIiIiIiIhIuVGIfonr1q0bM2bMcLz38fFh6dKlTJkyhd27d5OcnExWVhZpaWmcPXuWGjVqVOBoRURERERERERERMqXyrlc4nx8fAgPD3c80tPTufnmm2nXrh3ff/89GzZsYPr06QBkZGRU8GhFREREREREREREypdmoouTDRs2YLVaeeONN3BzM9+xfPPNN05tPD09yc7OrojhiYiIiIiIiIiIiJQrzUQXJ+Hh4WRmZvLuu++yf/9+PvvsM2bOnOnUJjQ0lJSUFH7++WdOnTrF2bNnK2i0IiIiIiIiIiIiImVLIbo4ad++PW+++SavvPIKbdq04fPPP2fKlClOba655hpGjx7NnXfeSXBwMK+++moFjVZERERERERERESkbKmcSxn670NdKnoIFzRr1qx8tz/22GM89thjTtuGDRvm9H7GjBlOC5KKiIiIiIiIiIiIXIw0E11EREREREREREREpAAK0UVERERERERERERECqAQXURERERERERERESkAArRRUREREREREREREQKoBBdRERERERERERERKQACtFFRERERERERERERAqgEF1EREREREREREREpAAK0UVERERERERERERECqAQXURERERERERERMpe165gsVBjzJiKHolIsShElzIRFRVF//79K3oYIiIiIiIiIiJSkBUr4J//hOBgsFjMY+ZM5zYHD0JUFDRpAt7e0Lw5vPoqWK359/nDDzl9WSyQllbWn0KkzFWr6AFc1D64vvzONWp5iQ47fvw4L730Ej/88ANxcXHUqVOHDh068Oijj3LjjTe6eJAiIiIiIiIiIlJpbNwIS5ZAWBicOpV3/8mT0Lmzefb1hRYtYPt2ePppOHYMpk1zbp+QAHffXS5DFylPmol+CTt48CAdO3bkl19+4bXXXmPbtm0sWrSIbt26MaaEt9VkZ2djLeibSBERERERERERqTyGDYPkZFi8OP/9335rAnSAtWth82aYMcO8f+89OHLEuf2IEZCUBP36FX7ul16CevXAxwcGDTLH2YWGmlnszzwDDz4INWtCQAA88ACkp+e0s892f+opGD7c9BUeDj/+CLt3Q5cuZts118CuXUX7NRHJh0L0S9gDDzyAxWLh999/57bbbiMiIoLWrVvz+OOPs3btWgDefPNN2rZti4+PD40aNeKBBx4gJSXF0cesWbMIDAxkwYIFtGrVCi8vLw4fPuzYP3HiRIKDg/H392f06NFkZGQ49n333Xe0bduW6tWrU6tWLbp3705qamr5/QKIiIiIiIiIiFzKatWC6tUL3p97oqSbm/Nzdjb8+mvO/nffhYULYcoU6NDhgqf1XLDAlIQJCICzZ+Hrr/OfwT5tGnz1FQQGmrB/xgx49tm87d59F37+Gby8IDbWhPI33WRmywOsWaMZ8lIqCtE
"text/plain": [
2025-09-23 18:17:06 +01:00
"<Figure size 1500x800 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
2025-09-23 18:17:06 +01:00
"\n",
"# Group by speed and calculate mean for numeric columns only\n",
"speed_groups = df.groupby('Speed').mean(numeric_only=True).round(1)\n",
"\n",
"# Drop the first and last row from speed_groups\n",
"speed_groups = speed_groups.iloc[1:-1]\n",
"\n",
"# Filter data to only include speeds in the desired range\n",
"filtered_data = speed_groups[(speed_groups.index >= 3.5) & (speed_groups.index <= 7.5)]\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Create figure with specific size\n",
"plt.figure(figsize=(15, 8))\n",
"plt.style.use('default')\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Create stage labels and positions\n",
"stage_labels = [f'Stage {i}' for i in range(1, len(filtered_data) + 1)]\n",
"x_positions = np.arange(len(filtered_data))\n",
"\n",
2025-09-23 17:18:10 +01:00
"# Calculate fat and carbs energy expenditure from percentages\n",
"fat_ee = filtered_data['EE(kcal/min)'] * filtered_data['FAT(%)'] / 100\n",
"carbs_ee = filtered_data['EE(kcal/min)'] * filtered_data['CARBS(%)'] / 100\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Create the main axis for the stacked bars\n",
"ax1 = plt.gca()\n",
"\n",
"# Create stacked bar chart with colors\n",
"bars_fat = ax1.bar(x_positions, fat_ee, color='#1f77b4', alpha=0.8, width=0.6, label='Fat')\n",
"bars_carbs = ax1.bar(x_positions, carbs_ee, bottom=fat_ee, color='#ff7f0e', alpha=0.8, width=0.6, label='Carbs')\n",
"\n",
"# Set labels and formatting for primary axis\n",
"ax1.set_xlabel('', fontsize=12)\n",
"ax1.set_ylabel('Fuel (kcal/min)', fontsize=12)\n",
"ax1.set_ylim(0, 20)\n",
"\n",
"# Add individual values on each bar segment\n",
"for i, (fat_val, carb_val, total_val) in enumerate(zip(fat_ee, carbs_ee, filtered_data['EE(kcal/min)'])):\n",
" if fat_val > 0.3: # Fat value\n",
" ax1.text(i, fat_val/2, f'{fat_val:.1f}', ha='center', va='center',\n",
" fontsize=9, fontweight='bold', color='white')\n",
" if carb_val > 0.3: # Carbs value\n",
" ax1.text(i, fat_val + carb_val/2, f'{carb_val:.1f}', ha='center', va='center',\n",
" fontsize=9, fontweight='bold', color='white')\n",
" # Total EE\n",
" ax1.text(i, total_val + 0.5, f'{total_val:.1f} kcal', ha='center', va='bottom',\n",
" fontsize=10, fontweight='bold', color='black')\n",
"\n",
"# Add speed labels below x-axis\n",
"for i, speed in enumerate(filtered_data.index):\n",
" ax1.text(i, -1.5, f'{speed:.1f} mph', ha='center', va='top', fontsize=9)\n",
" ax1.text(i, -2.8, f'{speed*1.609:.1f} min/km', ha='center', va='top', fontsize=8, color='gray')\n",
"\n",
"# Create secondary y-axis for heart rate\n",
"ax2 = ax1.twinx()\n",
"\n",
"# Plot heart rate line (no manual offset)\n",
"hr_line = ax2.plot(x_positions, filtered_data['HR(bpm)'],\n",
" marker='o', linewidth=3, markersize=8, color='red', label='Heart Rate')\n",
"\n",
"# Set heart rate axis formatting\n",
"ax2.set_ylabel('Heart Rate (bpm)', fontsize=12, color='red')\n",
"ax2.tick_params(axis='y', labelcolor='red')\n",
"\n",
"# Dynamically adjust HR axis to float above bars\n",
"max_bar_height = max(filtered_data['EE(kcal/min)'])\n",
"ax2.set_ylim(0, 220) # ensures HR line is above bars\n",
2025-09-23 17:18:10 +01:00
"\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Add HR values above the points\n",
"for i, hr in enumerate(filtered_data['HR(bpm)']):\n",
" ax2.text(i, hr + 10, f'{int(hr)}bpm', ha='center', va='bottom',\n",
" fontsize=10, fontweight='bold', color='red')\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Set x-axis formatting\n",
"ax1.set_xticks(x_positions)\n",
"ax1.set_xticklabels(stage_labels, fontsize=11)\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Add title\n",
"# plt.suptitle('Fuel Utilization Report - Institute of Science, Health and Performance',\n",
"# fontsize=14, fontweight='bold', y=0.95)\n",
2025-09-23 18:17:06 +01:00
"\n",
"# Create legend\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
2025-09-23 18:17:06 +01:00
"ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left',\n",
" frameon=True, fancybox=True, shadow=True)\n",
"\n",
2025-09-23 18:17:06 +01:00
"# Add grid\n",
"ax1.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)\n",
"ax1.set_axisbelow(True)\n",
"\n",
"# Adjust layout\n",
"plt.tight_layout()\n",
2025-09-23 18:17:06 +01:00
"plt.subplots_adjust(bottom=0.1, top=0.9)\n",
"plt.savefig('graphs/fuel_utilization_chart.png', dpi=300)\n",
"plt.show()"
]
2025-09-23 17:18:10 +01:00
},
{
"cell_type": "code",
"execution_count": 43,
2025-09-23 17:18:10 +01:00
"id": "8a1878a0",
"metadata": {},
2025-09-23 18:47:10 +01:00
"outputs": [
{
"data": {
"image/png": "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
2025-09-23 18:47:10 +01:00
"text/plain": [
"<Figure size 1800x500 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"first_unique_phase = df.drop_duplicates(subset='PHASE')\n",
"phase_times = first_unique_phase['T(sec)'].tolist()\n",
"\n",
"plt.figure(figsize=(18, 5))\n",
"ax1 = plt.subplot()\n",
"\n",
"# Plot VO2 Pulse\n",
2025-09-23 19:20:36 +01:00
"#\n",
"line1 = sns.lineplot(data=df, x='T(sec)', y='VO2 Pulse_smoothed', label='VO2 Pulse (mL/beat)', color='blue')\n",
2025-09-23 18:47:10 +01:00
"ax1.set_xlabel('Time (sec)')\n",
"ax1.set_ylabel('VO2 Pulse (mL/beat)')\n",
"# ax1.set_title('VO2 Pulse, Heart Rate, and Speed Over Time')\n",
"ax1.set_ylim(0, df['VO2 Pulse_smoothed'].max())\n",
2025-09-23 18:47:10 +01:00
"ax1.grid(True, alpha=0.1)\n",
"\n",
"# Create second y-axis for heart rate\n",
2025-09-23 19:20:36 +01:00
"#\n",
2025-09-23 18:47:10 +01:00
"ax2 = ax1.twinx()\n",
2025-09-23 19:20:36 +01:00
"line2 = sns.lineplot(data=df, x='T(sec)', y='HR(bpm)_smoothed', color='red', ax=ax2, \n",
2025-09-23 18:47:10 +01:00
" linewidth=2, label='Heart Rate (bpm)')\n",
"ax2.set_ylabel('Heart Rate (bpm)', color='red')\n",
"ax2.tick_params(axis='y', labelcolor='red')\n",
"ax2.set_ylim(0, df['HR(bpm)_smoothed'].max() + 1)\n",
2025-09-23 18:47:10 +01:00
"\n",
"# Create third y-axis for speed\n",
"ax3 = ax1.twinx()\n",
"ax3.spines['right'].set_position(('outward', 60))\n",
2025-09-23 19:20:36 +01:00
"\n",
2025-09-23 18:47:10 +01:00
"line3 = sns.lineplot(data=df, x='T(sec)', y='Speed', color='green', ax=ax3, \n",
" drawstyle='steps-post', linewidth=2, label='Speed')\n",
"ax3.set_ylabel('Speed', color='green')\n",
"ax3.tick_params(axis='y', labelcolor='green')\n",
"ax3.set_ylim(0, df['Speed'].max() + 1)\n",
2025-09-23 18:47:10 +01:00
"\n",
"ax1.set_xticks(np.arange(0, df['T(sec)'].max() + 200, 200))\n",
"\n",
"# Remove default legends first\n",
"if ax1.get_legend():\n",
" ax1.get_legend().remove()\n",
"if ax2.get_legend():\n",
" ax2.get_legend().remove()\n",
"if ax3.get_legend():\n",
" ax3.get_legend().remove()\n",
"\n",
"# Combine legends from all axes in the top left\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
"lines3, labels3 = ax3.get_legend_handles_labels()\n",
"ax1.legend(lines1 + lines2 + lines3, labels1 + labels2 + labels3, loc='upper left')\n",
"\n",
"# Add colored background regions if you have phase information\n",
"ax1.axvspan(0, phase_times[1], alpha=0.2, color='lightblue')\n",
"ax1.axvspan(phase_times[1], phase_times[2], alpha=0.2, color='purple')\n",
"ax1.axvspan(phase_times[2], phase_times[3], alpha=0.2, color='lightgreen')\n",
"ax1.axvspan(phase_times[3], df['T(sec)'].max(), alpha=0.2, color='blue')\n",
"\n",
"plt.savefig('graphs/vo2_pulse_chart.png', bbox_inches='tight', dpi=300)\n",
2025-09-23 18:47:10 +01:00
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 44,
2025-09-23 18:47:10 +01:00
"id": "7361fb05",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdwAAAHFCAYAAADoozskAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd8U/X6wPHPyezee0BLoZS9N4IiCAruvbde9bpQ+blBrsp1r+sV18WJiqiIoqigInvvWaADCt17ZJ/fH2kDldEW0iZtnzevvl4kOTnnSfLNSfKc5zxfRVVVFSGEEEIIIYQQQgghhBBCnBaNpwMQQgghhBBCCCGEEEIIIdoCSbgLIYQQQgghhBBCCCGEEG4gCXchhBBCCCGEEEIIIYQQwg0k4S6EEEIIIYQQQgghhBBCuIEk3IUQQgghhBBCCCGEEEIIN5CEuxBCCCGEEEIIIYQQQgjhBpJwF0IIIYQQQgghhBBCCCHcQBLuQgghhBBCCCGEEEIIIYQbSMJdCCGEEEIIIYQQQgghhHADSbgLIYQQQgghhBBCCCGEEG4gCXchhBBCCCGEEEIIIYQQrU5FRQUPPPAAHTt2xNfXl+HDh7N27VqPxiQJdyGEEEIIIYQQQgghhBCtzm233cZvv/3Gp59+ytatWznnnHMYO3YsOTk5HotJUVVV9djWhRBCCCGEEEIIIYQQQogmqqmpITAwkO+//56JEye6rh8wYADnnnsuzz77rEfi0nlkqy3IZrOxceNGoqOj0WikoF8IIYQQQgghhBBCCCG8kcPhIDs7m+7du6PTHUldG41GjEZjvWVtNht2ux0fH5961/v6+rJs2bIWifd42nzCfePGjQwePNjTYQghhBBCCCGEEEIIIYQ4BVOnTmXatGn1rgsMDGTYsGH861//olu3bkRHR/PFF1+wcuVKOnfu7JlAaQcJ9+joaADWrFlDbGysh6NpWTa7g7zyCgwGH7ReUt1vqbZweG0OIf4GDD56T4fT7qmqik21oVN0KIri1nWbrWaKDcVEpUVh8DG4dd2i5aiqitVsRW/Uu32MiNZPxodoiIwR0RAZI+JkZHyIhsgYEQ2RMdIyqqodbFijJ8onFD9j60k1qqqKqppRFKNXjQ+Lxfk3dCj4+no6mpZ3+PBhBg8ezLZt20hMTHRd//fq9jqffvopt9xyC/Hx8Wi1Wvr378/VV1/N+vXrWyrkY7Sed8EpqmsjExsbS0JCgoejaVlWuwNNWTm+Rl90Wu9IuJsqLTgybESGB2D0k4S7p6mqitVhRa9x/5cPk9mEDh3x8fH4+Po0fAfhleQLqjgZGR+iITJGRENkjIiTkfEhGiJjRDRExkjLqKiykxmhISEknADf1pPrUVUVh8OERuPjVePDZIKKCoiPBz8/T0fjOcHBwQQFBTW4XEpKCkuWLKGqqory8nJiY2O58sor6dSpUwtEeXzekYUVQgghhHCj79YW8NCn6ZRUWT0dihBCCCGEEEKIZubv709sbCwlJSX88ssvXHjhhR6LRRLuQgghhGhzPvzjEMt2l/H+4kOeDkUIIYQQQgghRDP55ZdfWLhwIRkZGfz222+cddZZpKWlcfPNN3ssJkm4CyGEEKJNKamyUlDurGyft66AzIIaD0ckhBBCCCGEEKI5lJWVcc8995CWlsYNN9zAyJEj+eWXX9DrPdfeqM33cG8MVVWx2WzY7XZPh+JWVrsDm8WCFQ0OrXf0orJaraBzYFds2PCOmNozFRU7dpTaf+5kV5zrtVlsWJVmbOmgOOdq0Og0XtVzTQjhOXsOV7v+b3fAf3/N4cVrPTdDvRBCCCGEEEKI5nHFFVdwxRVXeDqMetp9wt1isXD48GGqq6sbXriVUVUVu6pSjYK35CFVh4ohVqVGU41J4yVBtWOqqrr+7+5ktapV8cWX6txqapTmry7V+eoIiglCq9c2+7aEEN5tz2HnPqdrrB9786pZsrOUjZkV9EsK9HBkQgghhBBCCCHaunadcHc4HGRkZKDVaomLi8NgMLSpClmHqmKz21EUjdck3B12FVu1FZ1OgxS4t3EqWLGi99GjNOfBFdV55kRhfiHFWcVEdIpo3u0JIbxeXYX7mB6hdE/w57u1Bby18CAf3pnWpj7nhRBCCCGEEEJ4n3adcLdYLDgcDhITE/Hz8/N0OG7nUFWsdjsaL0u4W20adDqNJEW9hIrq9nYy4Kye16BB76NHo2ne6SJ8fH3Q6XRkZ2Vjt9rRGdv1rk2Idq8u4Z4a58f5AyJYuLmI7Qer+H17CWf3DGvw/iarg+/W5BMVbGjU8kIIIYQQQgghRB3JSkGzJwOFEM2v7gDO0W1yhBDtj8liJ7vQBEBqrC/hgXquGxnD+78f4r+/5jAqLQS97sSf+1sPVPKvbzLJKjShKNAtzp+4MGNLhS+EEEIIIYQQopWTTLMQQggh2oy9eTU4VAj11xEe4JyV/poR0YQH6DlYbGbOqvzj3s9ic/DWwgPc8d4usmoT9qoKc9ccf3khhBBCCCGEEOJ4JOEuhBBCiDbDNWFqnJ+rX7ufUctd4+IB+OD3Q+SXW46530s/ZPPZsjwcKpzbN5yplyYB8MP6QkwWe8sEL4QQQgghhBCi1ZOEuxCnaMlfSzAGGCktLW3yfWd9PIvzLjjP/UE1YNyEcTw05aEW3+7JjD1rLA89cOKYLBYLXZK7sH7d+haMSgjRWrn6t8fUn5tlYr9weiX6U21x8MZPB+rd9vv2EuavL0RR4LkrOzHtsmTG9wknLtRAeY2dX7YUt1j8QgghhBBCCCFaN0m4tzLnn38+EyZMOO5tS5cuRVEUtmzZAkBNTQ3Tn3mG7t3S8Pf1JSYqiquuuILt27fXu98H77/PmaNHExkeTmR4OOPPOYc1a9Y0+2NpitTuqRgDjBgDjPgG+ZLUOYk7776TkpKSFtm+OxPVJpOJZ/71DE8+9uRpr8sYYOT7H74/6TJ/Lf2LlK4pp72t07XkzyUYNIYmH6AwGAw8+NCDPP7o480TmBCiTalLuHeJrZ9w12gUplzQEY0Ci7aVsCq9jINFJn7fXsKMeZkAXH9GDGN7OSdJ1WoULh0SBcCcVfkyP4QQQgghhBBCiEaRhHsrc+utt/Lbb79x8ODBY26bNWsWAwcOpHfv3pjNZs4ZN46PP/qIZ6ZPZ8euXcz/8UdsNhsjhg1j1apVrvstWbKEK6+6it8WL2bp8uUkJCRw3oQJ5OTktORDA8BqtZ7wtqlPTiVrXxZ7d+3low8/YunypUx+ZPIJl7fb7TgcjuYI87R8O+9bAgMDGT5seIts74cFP3DeuadeTW+xHNt6oaVdfe3VLF+2/JiDRUIIcTS7Q2VvXm1Lmb8l3AFSY/24bKgziX7/x+lc+to2HvtiH+U1drrF+3HHmLh6y5/fPwKjXsPe3Bo2ZVY2/wMQQgghhBBCCNHqScL9KKqqUm2xeeSvsZVzkyZNIjIyko8++qje9ZWVlXz99dfceuutALz++uusXLmS7+bN4/LLr6Bjx44MHjyYOXPnktatG3fefrtrm59+9hl33XUXffv2JS0tjffefx+Hw8HvixefMI7pzzzDgP79ee/dd0nu2JGggACuvvJKysrK6i334Qcf0KtHDwL8/OjZvTszZ77jui0zKxNjgJGv537N2PFjCQoP4ouvvjjhNgMCA4iJjiE+Lp4zR5/J9ddcz8ZNG123f/LZJ0TFR/HDgh/oM6APgWGBZB/Ixmw283+P/x/JXZIJjQpl5JkjWfLXEtf9ioqKuP6m60nukkxIZAj9B/fnqzlfuW6/7c7b+GvZX/znv/9xVdlnZmW6bt+wcQPDzhhGSGQIo88eze49u0/4GADmzJ3DxPMm1rvutjtv47KrLuOFl14gMTmRqPgonpvxHDabjUefeJSYxBg6pXbi408/Pum6j+fHn35k0sRJrss2m437J99PZFwkcR3imDZ9Wr3xl9o9lef//Ty33H4LEbER3H3v3QAsX7GcMePGEBwRTErXFB58+EGqqqpc9/v8i88ZdsYwwmPC6dCpAzfccgMFBQUAZGZmMm7MOACiwqIwaAzcevOtrvs6HA4enfI
2025-09-23 18:47:10 +01:00
"text/plain": [
"<Figure size 1800x500 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"first_unique_phase = df.drop_duplicates(subset='PHASE')\n",
"phase_times = first_unique_phase['T(sec)'].tolist()\n",
"\n",
"plt.figure(figsize=(18, 5))\n",
"ax1 = plt.subplot()\n",
"\n",
"# Plot VT with step-like appearance\n",
2025-09-23 19:20:36 +01:00
"line1 = sns.lineplot(data=df, x='T(sec)', y='VO2 Breath_smoothed', label='VO2 per Breath (mL/breath)')\n",
2025-09-23 18:47:10 +01:00
"ax1.set_xlabel('Time (sec)')\n",
"ax1.set_ylabel('VO2 per Breath (mL/breath)')\n",
"# ax1.set_title('VO2 per Breath and Speed Over Time')\n",
"ax1.set_ylim(0, df['VO2 Breath_smoothed'].max() + 1)\n",
2025-09-23 18:47:10 +01:00
"ax1.grid(True, alpha=0.1)\n",
"\n",
"# Plot speed as step function on secondary y-axis\n",
"ax2 = ax1.twinx()\n",
"ax1.set_xticks(np.arange(0, df['T(sec)'].max() + 200, 200))\n",
"line2 = sns.lineplot(data=df, x='T(sec)', y='Speed', color='green', ax=ax2, \n",
" drawstyle='steps-post', linewidth=2, label='Speed')\n",
"ax2.set_ylim(0, df['Speed'].max() + 1)\n",
2025-09-23 18:47:10 +01:00
"ax2.set_ylabel('Speed')\n",
"\n",
"# Remove default legends first\n",
"ax1.get_legend().remove()\n",
"ax2.get_legend().remove()\n",
"\n",
"# Combine legends from both axes in the top left\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
"ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')\n",
"\n",
"# Add colored background regions if you have phase information\n",
"ax1.axvspan(0, phase_times[1], alpha=0.2, color='lightblue')\n",
"ax1.axvspan(phase_times[1], phase_times[2], alpha=0.2, color='purple')\n",
"ax1.axvspan(phase_times[2], phase_times[3], alpha=0.2, color='lightgreen')\n",
"ax1.axvspan(phase_times[3], df['T(sec)'].max(), alpha=0.2, color='blue')\n",
"\n",
"plt.savefig('graphs/vo2_breath_chart.png', bbox_inches='tight', dpi=300)\n",
2025-09-23 18:47:10 +01:00
"plt.show()"
]
2025-09-23 19:08:30 +01:00
},
{
"cell_type": "code",
"execution_count": 45,
2025-09-23 19:08:30 +01:00
"id": "c89478ff",
"metadata": {},
2025-09-23 20:36:24 +01:00
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABeQAAAHACAYAAADDSmhbAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd8VGXWwPHfnZree4OEEjpE6aKCIgiKvWHv76prw7a6KzZ2batrw7p20VUUUUFRBAWUJr2EkpCQ3nubft8/JhmNJCSBJDNJztdPPjJz7zz3zMyTZHLuuedRVFVVEUIIIYQQQgghhBBCCCFEl9K4OwAhhBBCCCGEEEIIIYQQoi+QhLwQQgghhBBCCCGEEEII0Q0kIS+EEEIIIYQQQgghhBBCdANJyAshhBBCCCGEEEIIIYQQ3UAS8kIIIYQQQgghhBBCCCFEN5CEvBBCCCGEEEIIIYQQQgjRDSQhL4QQQgghhBBCCCGEEEJ0A0nICyGEEEIIIYQQQgghhBDdQOfuALqazWZj+/btREZGotHI+QchhBBCCCGEEEIIIYTwRA6Hg6KiIlJSUtDpemfqunc+qz/Yvn0748ePd3cYQgghhBBCCCGEEEIIIdph8+bNjBs3zt1hdIlen5CPjIwEnG9idHS0m6PpXja7g6LqGgwGL7QecnWApd5CwW95BPkaMHjp3R1On6eqKjbVhk7RoShKp45ttpopN5QTMSQCg5ehU8cW3UdVVaxmK3qjvtPniOj5ZH6ItsgcEW2ROSKORuaHaIvMEdEWmSPdo67ewbbNeiK8gvEx9pxUo6qqqKoZRTF61PywWJxfEyeCt7e7o+l+BQUFjB8/3pXT7Y16znfJMWpqUxMdHU1cXJybo+leVrsDTVU13kZvdFrPSMibai04Mm2Eh/ph9JGEvLupqorVYUWv6fwPJyazCR06YmNj8fL26tSxRfeRD7DiaGR+iLbIHBFtkTkijkbmh2iLzBHRFpkj3aOmzs7hMA1xQaH4efecXI+qqjgcJjQaL4+aHyYT1NRAbCz4+Lg7Gvfpza3He+8zE0IIIYQQQgghhBBCCCE8iCTkhRBCCCGEEEIIIYQQQohuIAl5IYQQQgghhBBCCCGEEKIb9Poe8u2hqio2mw273e7uUDqV1e7AZrFgRYND6xm9sKxWK+gc2BUbNjwjpq6moKBBg9JHnq8QQgghhBBCCCGEEKJlfT4hb7FYKCgooL6+3t2hdDpVVbGrKvUoeMraFKpDxRCt0qCpx6TxkKC6kgoqKjq7Dj/80KJ1d0RCCCGEEEIIIYQQQgg36dMJeYfDQWZmJlqtlpiYGAwGg0etqny8HKqKzW5HUTQek5B32FVs9VZ0Og19pWDcarVSVl5GZUMlIWqIVMoLIYQQQgghhBBCCNFH9emEvMViweFwEB8fj4+Pj7vD6XQOVcVqt6PxsIS81aZBp9Og9IUKecDL6IVOpyM7Jxu73Y6ub3/bCSGEEEIIIYQQQgjRZ8miroBGIy+D6FqKovSqqy+EEEIIIYQQQgghhBAdJ5loIYQQQgghhBBCCCGEEKIbSEJeCCGEEEIIIYQQQgghhOgGkpAXneKaq6/mqSefdN0emJTEiy++2GXHe+KfTzBu0jiPGPfkaSfz5dIvOz0WIYQQQgghhBBCCCFE7+LWhPzatWuZM2cOMTExKIrC0qVLW933L3/5C4qi8MILL3RbfJ6ssLCQ22+/naSkJIxGI/Hx8cyZM4dVq1a59klKTOTlFpLijz/2GCeecEKz+8rLy5l3990MSEzEx8uLhLg4brzhBrKzs9uMZefOnaz47jv+evvtx//EjtOCfy3g2huuPebH333n3axYtqJDj/nb/X/j74/8HYfDcczHFUIIIYQQQgghhBBC9H5uTcjX1dUxevRoFi5ceNT9vvzySzZu3EhMTEw3RebZDh8+zIknnsjq1at59tln2b17NytWrGDatGncdtttHR6vvLycKZMns3rVKha++ir7Dx5k0ccfc+jQISZNmEBGRsZRH7/wlVe48KKL8PPzO9an1Gm+Wf4NZ88++5gf7+fnR2hoaIcec+aMM6mtqWXFDx1L5AshhBBCCCGEEEIIIfoWtybkZ82axYIFCzj//PNb3ScvL4/bb7+dRYsWodfruzQeVVWpt9jc8qWqarvjvPXWW1EUhc2bN3PhhRcyePBghg8fzrx589i4cWOHn/fD//gH+fn5rPjhB86cNYuEhAROPuUUvv3uO/R6PXf89a+tPtZut7Pkiy84++yjJ8Hf/u9/CQsJYfVqZwW/w+Hg3//5N0NHDcU/xJ+BQwby1DNPufZ/6OGHGD5mOEHhQSSPSObRxx/FarUe9Rg5uTmk7ktlxhkzADD6GXnr7bc476LzCAoPYtQJo9i4aSPph9I548wzCI4I5tTTT+VQxiHXGH9uWXPj/93IRZddxPMvPk+/Af2ITojmjrvvaBaLVqvlzJlnsvjzxUeNTwghhBBCCCGEEOJo1h+s4p4P0yitsbg7FCFEF9G5O4CjcTgcXHXVVdx3330MHz68XY8xm82YzWbX7ZqaGsCZbP9z0rvpdtO2eouN4Y/80EnRd8zex2bgY2j77SgvL2fFihUsWLAAHx+fI55TYGBgh5L7DoeDzz79lLmXX05UVFSzbd7e3vzllluY//DDlJeXExIScsTjd+3aRVVVFSeOHdvqMf797LP8+9ln+XbFCsaeOA5LrYWHH32Yd95/h2efepbJkyZTWFjIgYMHXI/x9/Pnv6//l+joaPbs3cOtf70VP38/7r373laPs2z5Mk45+RQCAgJc9z359JM88+QzPPPkM/z94b9z9fVXk9g/kfvuvY/4uHj+79b/46577uKbL79pddw1a9cQFRnF999+z6GMQ1x5zZWMHjWaG667wbXP2BPH8u/n/93qGPD7PFNp//vT1VwxdWDOdGhsum580T26co6Ink/mh2iLzBHRFpkj4mhkfoi2yBwRbemJc+T1H/M4kF/PZxuKueWMWHeH0y6qqkIPfK09dX40vpyur77G096PruDRCfmnn34anU7HHXfc0e7HPPnkkzz22GNH3G82mzGZTEfc98dvPne+4e09flpaGqqqkpyc3K79//7QQzz6yCPN7rNYLAwdOgzVoVJcVExlZSVDkoegOo4cLzl5CKqqkn4wjXHjxx+xPetwFlqtlvCw8CMf71D52wMP8PGiRfy4ajXDhw/H4VCpqanmldde4T/P/Ycrr7gSgKSkJCZPnuxKVv/tgb+5hunXrx933XkXiz9fzD133wPg2u+Pye1vln/DnLPmNLvvqiuv4sILLwTgnnn3cOppp/LgAw9yxvQzALjtltu4+Zabjxjvj/8PCgrihedfQKvVkpyczKyZs/jp55+4/rrrXceJjo4mJzcHu8OORtP6hSc2h83jEvI2hw0ARVE6dWybw4YDBzaLDavm6Fc3CM+lqipWi/P96+w5Ino+mR+iLTJHRFtkjoijkfkh2iJzRLSlp82R6gYbB/PrAfhlfyU3nhLh5ojax2axo6BBVc04HHZ3h9NuqgoOhxlQ8KTp0bREodkMR0kx9Vp/LLTurTw2Ib9161ZefPFFtm3b1qEfmg8++CDz5s1z3c7Ly2PYsGEYjUa8vLyO2F9RFNeXj0HH3sdmdEr8HeWt13boeTbF3Ja777mHa665ttkPlldefpl169ahaBQUjXODquD6d/PjNP7jD/v+kcnUgNFoRKM98ifECy/8h7q6OjZu3kxSUpJzPBUOHDyA2WzmtKmnodDyc1j8+WIWvr6QjIwMautqsdlsBPgHuPb/8/+rq6tZ98s63nj1jWZjjhoxynU7MiISgJHDRza7z2QyUVNdQ0BAy+MPGzoMnfb3b5WoqCj27t3b7Dje3t44HA4sZgve3t4tPicAnUaHzoO+7ZpO6ug1+k7/cGLX2NGgQWfQoTd2bbsp0XVcc8TY+XNE9HwyP0RbZI6ItsgcEUcj80O0ReaIaEtPmyO70mtdJXyHik2UNjiIDjK6Nab20Nk0qGhQFCMaTc/5+985P1Q0GqNHzY+mJLzRCC2kMns9o9Hz5/zx8pzM4J+
2025-09-23 20:36:24 +01:00
"text/plain": [
"<Figure size 1800x500 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
2025-09-23 19:08:30 +01:00
"source": [
"first_unique_phase = df.drop_duplicates(subset='PHASE')\n",
"phase_times = first_unique_phase['T(sec)'].tolist()\n",
"\n",
"plt.figure(figsize=(18, 5))\n",
"ax1 = plt.subplot()\n",
"\n",
2025-09-23 20:36:24 +01:00
"df['CHO']\n",
2025-09-23 19:08:30 +01:00
"# Plot VT with step-like appearance\n",
2025-09-23 20:36:24 +01:00
"line1 = sns.lineplot(data=df, x='T(sec)', y='CHO_smoothed', label='CHO (kcal/min)')\n",
2025-09-23 19:08:30 +01:00
"ax1.set_xlabel('Time (sec)')\n",
2025-09-23 20:36:24 +01:00
"ax1.set_ylabel('CHO (g/min)')\n",
"# ax1.set_title('CHO and Speed Over Time')\n",
2025-09-23 19:08:30 +01:00
"ax1.grid(True, alpha=0.1)\n",
"\n",
"# Plot speed as step function on secondary y-axis\n",
"ax2 = ax1.twinx()\n",
"ax1.set_xticks(np.arange(0, df['T(sec)'].max() + 200, 200))\n",
2025-09-23 20:36:24 +01:00
"line2 = sns.lineplot(data=df, x='T(sec)', y='FAT_smoothed', color='green', ax=ax2, label='FAT (kcal/min)')\n",
"ax2.set_ylabel('FAT (kcal/min)')\n",
"\n",
"ax2.set_ylim(0, 15) # ensures HR line is above bars\n",
2025-09-23 19:08:30 +01:00
"\n",
"# Remove default legends first\n",
"ax1.get_legend().remove()\n",
"ax2.get_legend().remove()\n",
"\n",
"# Combine legends from both axes in the top left\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
"ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')\n",
"\n",
"# Add colored background regions if you have phase information\n",
"ax1.axvspan(0, phase_times[1], alpha=0.2, color='lightblue')\n",
"ax1.axvspan(phase_times[1], phase_times[2], alpha=0.2, color='purple')\n",
"ax1.axvspan(phase_times[2], phase_times[3], alpha=0.2, color='lightgreen')\n",
"ax1.axvspan(phase_times[3], df['T(sec)'].max(), alpha=0.2, color='blue')\n",
"\n",
"plt.savefig('graphs/fat_metabolism_chart.png', bbox_inches='tight', dpi=300)\n",
2025-09-23 19:08:30 +01:00
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 46,
2025-09-23 19:08:30 +01:00
"id": "1db16040",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABkkAAAHDCAYAAACAmv2VAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XWcVFUbwPHfndoOtnfpWnpZmgWkOwQFRRQBxUKUshsLXkFEFDBQxAIFFaSku7u7l9gltmP6vn+MDKybwCY8Xz/zcebec899ZvYwC/e55zyKqqoqQgghhBBCCCGEEEIIIYQQ9xhNUQcghBBCCCGEEEIIIYQQQghRFCRJIoQQQgghhBBCCCGEEEKIe5IkSYQQQgghhBBCCCGEEEIIcU+SJIkQQgghhBBCCCGEEEIIIe5JkiQRQgghhBBCCCGEEEIIIcQ9SZIkQgghhBBCCCGEEEIIIYS4J0mSRAghhBBCCCGEEEIIIYQQ9yRJkgghhBBCCCGEEEIIIYQQ4p4kSRIhhBBCCCGEEEIIIYQQQtyTJEkihBBCCCGEEEIIIYQQQoh7kiRJhBBCCCGEEEIIIYQQQghxxy4kXaD/X/3xH+eP28du1PmqDjsu7nDuV1WVd1e/S+iEUNw+dqP9T+05fu14EUYMuiI9ewlhNptZtmwZFSpUQKvVFnU4QgghhBBCCCGEEEIIIbJgt9u5fPkyLVq0QK/XF3U4JZ6qqiQnJ+Pl5YWiKDm2jU+Pp/n05rSp2IZ/HvuHQPdAjscdp5RrKWebcRvH8cXWL/ix149ULFWRd1a/Q6dfOnFo6CFcda4F/XayphahqVOnqnXq1FG9vLxULy8vtWnTpurixYud+1u1aqUCGR7PPvtshj7Onj2rdu3aVXVzc1MDAwPVl19+WbVYLBnarF69Wq1Xr55qMBjUypUrqz/88MMtxblgwYJMcchDHvKQhzzkIQ95yEMe8pCHPOQhD3nIQx7ykIc8iudj1apVt33dWtyQmJioAmpiYmKubV9b/praYnqLbPfb7XY15NMQdfzG8c5tCekJqsuHLuqs/bPyJd7bUaQzScqUKcP//vc/qlatiqqq/Pjjj/Ts2ZPdu3dTq1YtAJ5++mk++OAD5zHu7u7O5zabjW7duhESEsKmTZu4dOkSAwYMQK/XM2bMGABOnz5Nt27deO655/j1119ZuXIlTz31FKGhoXTq1ClPcVaoUAGABQsWULly5Xx69yWH1WbnSnIKeoMLWk3xWKHNnGYhdtdFfNwNGFwlI1yUVFXFqlrRKbpcs8m3ymQxEW+IJzA8EL2L/JxLKlVVsZqt6Az5P0bE3UHGiMiJjA+RGxkjIjcyRkROZHyI3MgYKUCqiv7UWdz2HMBw8BiGg0dJT1GxBJXHWC6cxGr1Sa4Sic3VPfe+ipCqqqiqCUVxKVZjxGwGiwUaNQLXIro5v6jExMTQtm1bKlWqVNSh3FWSkpIyvHZxccHFxSXDtvlH59OpcicemvMQa8+spbR3aZ5v+DxPN3gagNMJp4lJiaF9pfbOY3xcfWhSpgmbozfzSO1HCv6NZKFIkyQ9evTI8Prjjz/mq6++YsuWLc4kibu7OyEhIVkev2zZMg4dOsSKFSsIDg4mMjKSDz/8kNdee43Ro0djMBj4+uuvqVixIhMmTACgRo0abNiwgYkTJ+Y5SXJ9ia3KlStTo0aN2327JZbFZscnMQk3Fzd02uKRJDGmmHG/4kqgvycu7nLxvCipqorFbkGv0ef7XwaMJiMxxFC6Wmlc3e6x3+h3EVVVsZgs6F3yf4yIu4OMEZETGR8iNzJGRG5kjIicyPgQuZExkr805y9iWLUew8r1GFavR3spNnOj6OOwcwUAqlZLWngkKZEtiG/Xh9TIFoUcce5UVcVuN6LRuBarMWI0QnIyVKsG7sU7z5TvvLy8AKRsQj4rW7Zshtfvvfceo0ePzrDtVPwpvtrxFaOiRvFmizfZfnE7w5YMw6A1MDByIDEpMQAEewRnOC7YI5iY1JgCjT8nxaYmic1mY86cOaSmphIVFeXc/uuvv/LLL78QEhJCjx49eOedd5yzSTZv3kydOnUIDr7xoXbq1IkhQ4Zw8OBB6tWrx+bNm2nfvn2Gc3Xq1IkRI0ZkG4vJZMJkMjlfp6SkANczw2p+vN0S5fr7Lk7vX1VVVIpXTPeqghwb8nO+OxS37w9R/MgYETmR8SFyI2NE5EbGiMiJjA+RGxkjd05JTcNl3mLcv/0Jw+bt2bZTFQVV0aCx224ca7PhcXgnHod3EjxrEtc69SN6xASsAVnfUF0UiusYUdWMj3tJcftZ3C2io6Px9vZ2vv7vLBIAu2qnYVhDxrRzrPJUL7QeBy4f4OudXzMwcmChxXqrijxJsn//fqKiojAajXh6ejJ37lxq1qwJwKOPPkr58uUJCwtj3759vPbaaxw9epS//voLcEydujlBAjhfx8TE5NgmKSmJ9PR03NzcMsU0duxY3n///UzbTSYTRqPxzt90CWO1q1jNZiyKBnsxWW7LYrZgx4ZVtaKxF3U09zZVVbHarQD5fseE1W7Fjh2r2YpFY8nXvkXhUVUVi9nx8ytOd9WI4kPGiMiJjA+RGxkjIjcyRkROZHyI3MgYuQ0mE4a9B3FdswGX1Ruw79zBrkAraXrwKAtNLoDODnZ3N0zNm2Bq2Qxzw3rEVavB9j2uVE1PIvDkXjz3bsJr72bcTh5E+feit//SWfhsXMz5IaO5/MBTUAxmCqgq2O0mQKE4DRH7v9fLTCYoJpfzCs3NN7+L/OPt7Z0hSZKVUK9QagbWzLCtRkAN/jz8JwAhno4EZ2xqLKFeoc42samxRAZH5m/At6DIkyTVqlVjz549JCYm8scffzBw4EDWrl1LzZo1eeaZZ5zt6tSpQ2hoKO3atePkyZMFWhvkjTfeYNSoUc7XR48epXHjxri4uOB6ry3ih2O5LZ3BhN7gUmyW27JbFDRo0Sk69BpZbqsoXc/OF8RyWzaNDQ0adAad1CQpwZxjRKani2zIGBE5kfEhciNjRORGxojIiYwPkRsZIw5KcgqaizFoYy6D1Xpje3wiulNn0J46g/bkGXSnzqA5f8mZ1LBooMVTsDPsRl+RahDfN/6AsFZdwGBwbtem2rDrNBjLliU+PJL4Lo67zrVJ8ZRaMYcyU99ElxiHLiWRCuNHErB0DscnLcLm6VM4H0I2HGNERaMpXjVJridGXFzuvZokWc1wEIWjednmHL12NMO2Y9eOUd6nPAAVfSsS4hnCylMriQyJBCDJlMTW81sZ0nBIYYfrVORJEoPBQJUqVQBo0KAB27dvZ9KkSXzzzTeZ2jZp0gSAEydOULlyZUJCQti2bVuGNrGxjrUMr9cxCQkJcW67uY23t3eWs0ggc9EZT09PwHHHQHZfdqqqYrVasdlsWe4vySw2OzaLBauiRdUWjy97q9WColOxa2zYlOKRuCnuFBQ0aFDI/5/h9T8b+f2XAUVRUCiYvkXhKqgxIu4eMkZETmR8iNzIGBG5kTEiciLjQ+SmRI2R9HQ0SSmORIbFgmKxorkUg/bkGbSnzqI7fRYlKTn7401mtOfOoz134UYyRFWdSY+bHQiCS57Q9jRo/9192QP+agAXveD57TC7VsYECcAe5TJt97/G5IoedKjawbldURTI4rO2+/hxrfezJLTrTZlJrxKw4AcAPPdtouI7/Tk54e8in1FSHMfIvx+n83EvKU4/h3vNyKYjaTa9GWPWj+HhWg+z7cI2vt31Ld92/xZw/GxGNBnBR+s/oqp/VSr6VuSd1e8Q5hVGr+q9iizuIk+S/Jfdbs92StSePXsACA11TMWJiori448/5vLlywQFBQGwfPlyvL29nUt2RUVFsXjx4gz9LF++PEPdkztlNpu5dOkSaWlp+dZncaKqKjZVJa0YTRtU7SqGUJV0TRpGTTEJqjhTQUVFZ9PhiSdain46qhBCCCGEEEIIIe6ckpSMfuNW3GbMwmX+UpSbZnrcKptyI+GRHaMO3moLE6NAVaDeJXh8LyyuCqsqgv3fe1l/bOtHosYCFkdSZkjTISw9tpRTcadINCYycM5A3m/
2025-09-23 19:08:30 +01:00
"text/plain": [
"<Figure size 1800x500 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"first_unique_phase = df.drop_duplicates(subset='PHASE')\n",
"phase_times = first_unique_phase['T(sec)'].tolist()\n",
"\n",
"plt.figure(figsize=(18, 5))\n",
"ax1 = plt.subplot()\n",
"\n",
"# Plot VO2 Pulse\n",
2025-09-23 19:20:36 +01:00
"line1 = sns.lineplot(data=df, x='T(sec)', y='VCO2(ml/min)_smoothed', label='VCO2 (ml/min)', color='blue')\n",
2025-09-23 19:08:30 +01:00
"ax1.set_xlabel('Time (sec)')\n",
"ax1.set_ylabel('VO2 Pulse (mL/beat)')\n",
"# ax1.set_title('VO2 Pulse, Heart Rate, and Speed Over Time')\n",
2025-09-23 19:08:30 +01:00
"ax1.set_ylim(0, df['VCO2(ml/min)'].max())\n",
"ax1.grid(True, alpha=0.1)\n",
"\n",
"# Create second y-axis for heart rate\n",
"ax2 = ax1.twinx()\n",
2025-09-23 19:20:36 +01:00
"line2 = sns.lineplot(data=df, x='T(sec)', y='HR(bpm)_smoothed', color='red', ax=ax2, \n",
2025-09-23 19:08:30 +01:00
" linewidth=2, label='Heart Rate (bpm)')\n",
"ax2.set_ylabel('Heart Rate (bpm)', color='red')\n",
"ax2.set_ylim(df['HR(bpm)_smoothed'].min(), df['HR(bpm)_smoothed'].max() + 1)\n",
2025-09-23 19:08:30 +01:00
"ax2.tick_params(axis='y', labelcolor='red')\n",
"\n",
"# Create third y-axis for speed\n",
"ax3 = ax1.twinx()\n",
"ax3.spines['right'].set_position(('outward', 60))\n",
2025-09-23 19:20:36 +01:00
"line3 = sns.lineplot(data=df, x='T(sec)', y='BF(bpm)_smoothed', color='green', ax=ax3, linewidth=2, label='BF (bpm)')\n",
2025-09-23 19:08:30 +01:00
"ax3.set_ylabel('BF (bpm)', color='green')\n",
"ax3.tick_params(axis='y', labelcolor='green')\n",
"ax3.set_ylim(0, df['BF(bpm)_smoothed'].max() + 1)\n",
2025-09-23 19:08:30 +01:00
"ax1.set_xticks(np.arange(0, df['T(sec)'].max() + 200, 200))\n",
"\n",
"# Remove default legends first\n",
"if ax1.get_legend():\n",
" ax1.get_legend().remove()\n",
"if ax2.get_legend():\n",
" ax2.get_legend().remove()\n",
"if ax3.get_legend():\n",
" ax3.get_legend().remove()\n",
"\n",
"# Combine legends from all axes in the top left\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
"lines3, labels3 = ax3.get_legend_handles_labels()\n",
"ax1.legend(lines1 + lines2 + lines3, labels1 + labels2 + labels3, loc='upper left')\n",
"\n",
"# Add colored background regions if you have phase information\n",
"ax1.axvspan(0, phase_times[1], alpha=0.2, color='lightblue')\n",
"ax1.axvspan(phase_times[1], phase_times[2], alpha=0.2, color='purple')\n",
"ax1.axvspan(phase_times[2], phase_times[3], alpha=0.2, color='lightgreen')\n",
"ax1.axvspan(phase_times[3], df['T(sec)'].max(), alpha=0.2, color='blue')\n",
"\n",
"plt.savefig('graphs/recovery_chart.png', bbox_inches='tight', dpi=300)\n",
2025-09-23 19:08:30 +01:00
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "52642f49",
"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>MeasurementDate</th>\n",
" <th>Comment</th>\n",
" <th>ExternalDeviceId</th>\n",
" <th>ExternalPatientId</th>\n",
" <th>FirstName</th>\n",
" <th>LastName</th>\n",
" <th>BirthDate</th>\n",
" <th>Age</th>\n",
" <th>Ethnicity</th>\n",
" <th>Gender</th>\n",
" <th>...</th>\n",
" <th>Child_XC</th>\n",
" <th>Child_XC_Unit</th>\n",
" <th>Child_BIVA_ZRh</th>\n",
" <th>Child_BIVA_ZXcH</th>\n",
" <th>Child_PhA</th>\n",
" <th>Child_PhA_Unit</th>\n",
" <th>Child_REE_Kcal</th>\n",
" <th>Child_REE_MJ</th>\n",
" <th>Child_TEE_Kcal</th>\n",
" <th>Child_TEE_MJ</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2025-09-05T14:56:27.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>LD5163301170</td>\n",
" <td>Lucy</td>\n",
" <td>Dibenedetto</td>\n",
" <td>1997-08-28T00:00:00.0000000Z</td>\n",
" <td>28</td>\n",
" <td>Caucasian</td>\n",
" <td>Female</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2025-09-03T13:16:22.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>NS6479273340</td>\n",
" <td>Niyanta</td>\n",
" <td>Shah</td>\n",
" <td>1985-03-11T00:00:00.0000000Z</td>\n",
" <td>40</td>\n",
" <td>Other</td>\n",
" <td>Female</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2025-09-03T13:14:23.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>1985-03-11T00:00:00.0000000Z</td>\n",
" <td>40</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2025-08-27T20:57:32.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>1996-04-05T00:00:00.0000000Z</td>\n",
" <td>29</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2025-08-20T14:01:13.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>MW4167267833</td>\n",
" <td>Monica</td>\n",
" <td>Wong</td>\n",
" <td>1985-02-17T00:00:00.0000000Z</td>\n",
" <td>40</td>\n",
" <td>Asian</td>\n",
" <td>Female</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>58</th>\n",
" <td>2025-04-10T14:10:28.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>6473915195</td>\n",
" <td>Tristan</td>\n",
" <td>Walsh</td>\n",
" <td>1995-12-01T00:00:00.0000000Z</td>\n",
" <td>29</td>\n",
" <td>Caucasian</td>\n",
" <td>Female</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>59</th>\n",
" <td>2025-04-02T21:19:51.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>WSC4168020667</td>\n",
" <td>Scott</td>\n",
" <td>Christie</td>\n",
" <td>1968-08-19T00:00:00.0000000Z</td>\n",
" <td>56</td>\n",
" <td>Caucasian</td>\n",
" <td>Male</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>60</th>\n",
" <td>2025-04-02T15:23:54.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>6478809838</td>\n",
" <td>Lauren</td>\n",
" <td>Karatanevski</td>\n",
" <td>1984-07-07T00:00:00.0000000Z</td>\n",
" <td>40</td>\n",
" <td>Caucasian</td>\n",
" <td>Female</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>61</th>\n",
" <td>2025-03-26T15:47:42.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>2002-07-10T00:00:00.0000000Z</td>\n",
" <td>22</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>62</th>\n",
" <td>2025-03-19T15:10:21.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>6478809838</td>\n",
" <td>Lauren</td>\n",
" <td>Karatanevski</td>\n",
" <td>1984-07-07T00:00:00.0000000Z</td>\n",
" <td>40</td>\n",
" <td>Caucasian</td>\n",
" <td>Female</td>\n",
" <td>...</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>63 rows × 147 columns</p>\n",
"</div>"
],
"text/plain": [
" MeasurementDate Comment \\\n",
"0 2025-09-05T14:56:27.0000000Z NaN \n",
"1 2025-09-03T13:16:22.0000000Z NaN \n",
"2 2025-09-03T13:14:23.0000000Z NaN \n",
"3 2025-08-27T20:57:32.0000000Z NaN \n",
"4 2025-08-20T14:01:13.0000000Z NaN \n",
".. ... ... \n",
"58 2025-04-10T14:10:28.0000000Z NaN \n",
"59 2025-04-02T21:19:51.0000000Z NaN \n",
"60 2025-04-02T15:23:54.0000000Z NaN \n",
"61 2025-03-26T15:47:42.0000000Z NaN \n",
"62 2025-03-19T15:10:21.0000000Z NaN \n",
"\n",
" ExternalDeviceId ExternalPatientId FirstName \\\n",
"0 10000001583275_0055003f5631501320313557 LD5163301170 Lucy \n",
"1 10000001583275_0055003f5631501320313557 NS6479273340 Niyanta \n",
"2 10000001583275_0055003f5631501320313557 NaN NaN \n",
"3 10000001583275_0055003f5631501320313557 NaN NaN \n",
"4 10000001583275_0055003f5631501320313557 MW4167267833 Monica \n",
".. ... ... ... \n",
"58 10000001583275_0055003f5631501320313557 6473915195 Tristan \n",
"59 10000001583275_0055003f5631501320313557 WSC4168020667 Scott \n",
"60 10000001583275_0055003f5631501320313557 6478809838 Lauren \n",
"61 10000001583275_0055003f5631501320313557 NaN NaN \n",
"62 10000001583275_0055003f5631501320313557 6478809838 Lauren \n",
"\n",
" LastName BirthDate Age Ethnicity Gender ... \\\n",
"0 Dibenedetto 1997-08-28T00:00:00.0000000Z 28 Caucasian Female ... \n",
"1 Shah 1985-03-11T00:00:00.0000000Z 40 Other Female ... \n",
"2 NaN 1985-03-11T00:00:00.0000000Z 40 NaN NaN ... \n",
"3 NaN 1996-04-05T00:00:00.0000000Z 29 NaN NaN ... \n",
"4 Wong 1985-02-17T00:00:00.0000000Z 40 Asian Female ... \n",
".. ... ... ... ... ... ... \n",
"58 Walsh 1995-12-01T00:00:00.0000000Z 29 Caucasian Female ... \n",
"59 Christie 1968-08-19T00:00:00.0000000Z 56 Caucasian Male ... \n",
"60 Karatanevski 1984-07-07T00:00:00.0000000Z 40 Caucasian Female ... \n",
"61 NaN 2002-07-10T00:00:00.0000000Z 22 NaN NaN ... \n",
"62 Karatanevski 1984-07-07T00:00:00.0000000Z 40 Caucasian Female ... \n",
"\n",
" Child_XC Child_XC_Unit Child_BIVA_ZRh Child_BIVA_ZXcH Child_PhA \\\n",
"0 NaN NaN NaN NaN NaN \n",
"1 NaN NaN NaN NaN NaN \n",
"2 NaN NaN NaN NaN NaN \n",
"3 NaN NaN NaN NaN NaN \n",
"4 NaN NaN NaN NaN NaN \n",
".. ... ... ... ... ... \n",
"58 NaN NaN NaN NaN NaN \n",
"59 NaN NaN NaN NaN NaN \n",
"60 NaN NaN NaN NaN NaN \n",
"61 NaN NaN NaN NaN NaN \n",
"62 NaN NaN NaN NaN NaN \n",
"\n",
" Child_PhA_Unit Child_REE_Kcal Child_REE_MJ Child_TEE_Kcal Child_TEE_MJ \n",
"0 NaN NaN NaN NaN NaN \n",
"1 NaN NaN NaN NaN NaN \n",
"2 NaN NaN NaN NaN NaN \n",
"3 NaN NaN NaN NaN NaN \n",
"4 NaN NaN NaN NaN NaN \n",
".. ... ... ... ... ... \n",
"58 NaN NaN NaN NaN NaN \n",
"59 NaN NaN NaN NaN NaN \n",
"60 NaN NaN NaN NaN NaN \n",
"61 NaN NaN NaN NaN NaN \n",
"62 NaN NaN NaN NaN NaN \n",
"\n",
"[63 rows x 147 columns]"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_2 = pd.read_excel('data/SECA body comp for all patients.xlsx')\n",
"df_2"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "2056096d",
"metadata": {},
"outputs": [
{
"data": {
2025-09-24 09:57:15 +01:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAABLAAAAEiCAYAAADptysgAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAANzVJREFUeJzt3Xl0VdX9/vHnZiQhEGbCFEIgEQiCBARBRSiogAIiY+sQtBVBsFoG4adFUNoKUlAKLVarURegODGpaAWBqkQmCfNsGCWADGFOQrJ/f7ByvjlkDhk24f1a6y65Z599zr73wya5j/uc6zHGGAEAAAAAAACW8irtAQAAAAAAAAC5IcACAAAAAACA1QiwAAAAAAAAYDUCLAAAAAAAAFiNAAsAAAAAAABWI8ACAAAAAACA1QiwAAAAAAAAYDUCLAAAAAAAAFiNAAsAAAAAAABWI8ACAKAM8Hg82T58fHwUHByspk2b6pFHHtGSJUtKZXwTJkxwjevdd98tlfPm9rjllltKZEz5tWLFilzHW6FCBTVp0kR/+MMftGbNmtIeLgrp3LlzWrx4scaMGaOOHTsqMjJSlSpVkp+fn0JCQtSlSxe98cYbSklJybb/2rVrNXXqVD344INq3ry5QkJC5Ofnp+DgYLVs2VIjR47Uzz//XGTjPX/+vBo1apTl7+O+ffuy7Ltz504NHDhQNWrUkK+vr+rWrasnnnhCiYmJOR47LCxMHo9HL7/8cpGNGQBQNhBgAQBQhqWlpenMmTPavn27Zs+ere7du+vxxx8v7WGVae+++67rg/2ECROK5Tznzp3Tjh079Pbbb+u2227T2LFji+U8ZVFJ1Sg/vvrqK/Xs2VOvvvqqVq5cqd27dyspKUmpqak6evSoli1bpqFDh6ply5Y6ePBglv6dOnXSqFGjNH/+fG3evFlHjx5Vamqqzpw5o/j4eE2bNk1RUVGaM2dOkYx39OjR2rt3b5777d69W23bttW8efN04sQJ1ahRQ4cPH9Z//vMf3XbbbTp58mSWPn/+85+1f/9+RUVF8fcZAJCFT2kPAAAAFL1u3bopMDBQqampio+P14EDB5y22NhY9evXT926dSvFEZaO+vXrq3Xr1tm2NWjQoIRHUzCBgYFOzc6ePau1a9fq1KlTkiRjjCZPnqzIyEgCyutYQECAWrVqpQoVKmjTpk06fPiw07Zt2zYNGDBAq1atyrF/06ZN1aBBAx05ckQ//fSTs/3SpUsaNGiQbrnlFkVFRRV6fN98841mzZqVr33/+te/KikpSZK0cOFC3X///Zo5c6aefvpp7d+/XzNnztSLL77o7L9u3TrNmDFDXl5eeuutt+Tn51focQIAyiYCLAAAyqB//etfCgsLkySlpqbqjjvucF1mtmzZshsywOrYsWOJXb5Y1KpXr65PPvnEeX7ixAl17NhRW7Zscbb9/e9/J8C6DkVGRmrs2LEaOHCgAgICJF2Zt8OHD9ebb77p7BcXF6eNGzeqRYsWzraAgAANHTpUw4YNc+a8JC1dulTdu3dXamqqJOny5cv6z3/+o9dee61QY0xKSnL+bgUHB8vj8ej06dM57r927VpJUuXKlXX//fdLkh599FE9/fTTkuT69ygtLU2DBw9WWlqahg0bpnbt2hVqjACAso1LCAEAKON8fX3VoUMH17aLFy9mu++5c+c0Y8YMdenSRTVr1nTupdO8eXP98Y9/1Pbt23M8z8mTJ/WnP/1J9evXl7+/v0JDQzVs2DAdO3Ysxz6rVq1yXcr10EMPZbtfz549XftlDm2Kw/fff68//elP6tSpkxo2bKjKlSs79xO7+eabNXToUG3cuNHVJ+OytMcee8y1/aWXXiqWy9WqVq2qESNGuLZt375d586dc227ePGi3njjDd17772u+yO1bt1aL730kk6cOJHt8TOPOSwsTCkpKXr11VfVvHlzlS9fXh6Px7W/MUaLFi3SwIED1bBhQwUFBSkgIEChoaHq1q1bjit3vvvuO8XExCgiIkJBQUEqV66cGjRooJiYGCcEudqgQYNc41uxYoU2bNig/v37q0aNGvL391ejRo00btw4JScnO/1Kukb5cdddd2nLli167LHHnPBKujJvX3/9dfn4uP9/844dO1zPN27cqClTprjCK0nq0qWL+vfvn2vfgnj66ad16NAhSdKMGTMUHBxc6GNd7bXXXtOGDRtUt25dvfLKK0V2XABAGWMAAMB1T5LrkZCQ4LSlpKSYtm3butpjY2OzHCM+Pt6EhYVlOVbmh4+Pj/n73/+epe+hQ4dMeHh4tn1q1aplfve73+V4/vbt2zvb/fz8TGJiouvYv/76q/H19XX2ueOOO/L9vowfP9513piYmHz1GzZsWK7vgyTj7e1t3n77badPbGxsnn0kmfHjx+drDMuXL3f1q1+/fpZ9vvjiiyzH/+WXX5z2bdu2mcjIyFzHExISYlatWpXl2FfXsHPnzln6Zjh27Ji56667cj3P1eNPTU01jz32WK59PB6PGTduXJaxxcTEuPZ76KGHjLe3d7bHeOCBB4qtRiWhWrVqrrEtWbIk331HjRrl6jtgwIBCjWH+/PnOMR588EFjjDH169fP8d8cY4wZNGiQ07Z48WJjjDEzZsxwtr388svGGGMSEhJMYGCgkWQWLVpUqPEBAG4MXEIIAEAZ9NRTTykwMFCXL19WfHy89u/f77Tdeeed+t3vfufa/9dff9W9996ro0ePOtuqVq2q6OhoHT58WNu2bZN05TKkUaNGKSQkxLVaatCgQa5vOvP19VXbtm11+fJlrV27VnPnzs1xrKNHj1bv3r0lSSkpKXrrrbf05z//2WmfN2+ecxmUJA0ZMqSgb4djxYoV6tu3b7Ztw4cPV8eOHZ3nXl5eioyMVPXq1VW5cmWlpqZq3759ziq0jMudunXrplq1aiksLEx9+vTR/v37tW7dOuc4TZo0UdOmTZ3nmf98rTLf50i68r5XrVpVknTq1Cndc889zqoZSWrUqJFuuukmHT161BljYmKievTooU2bNql27drZnufIkSM6cuSIypcvr+joaJUrV85ZHZWWlqbu3bu7XrN05bK4Ro0a6ezZs1naJOmZZ55RbGys87xChQpq27atvLy8tGrVKp07d07GGE2cOFG1a9fOte5z5syRv7+/br/9dp06dUqbN2922hYsWKBVq1apffv2pVKja7F69Wr9+uuvzvPAwEC1bds2X33T0tKyfOvob37zmwKP4fjx43ryySclSTVq1NAbb7yRr37PP/+85s+fr6SkJPXq1UshISH65ZdfJF25F92wYcMkSUOHDtWFCxfUr18/9ejRo8DjAwDcQEo7QQMAANdO+VhVIsk0bNjQ7N27N0v/sWPHuvZr27atOXXqlNM+ceJEV3udOnVMWlqaMcaYdevWudp8fX1NXFyc03fJkiXG4/HkuAIrLS3NtUqobt26JjU11WnPvEKrWrVq5tKlS/l+X65egZXbI/OYdu/ebU6fPp3tMWfOnOnqN2vWLFf71at8CruaJ7cVWGfPnjUfffSRqVixomufrl27Ovv8+c9/drVNmjTJdfy5c+e62ocPH+5qv/r9ueWWW8yhQ4ec9ow6vPPOO679AgICnBU3mcf7/vvvO8937txpvLy8nD5t2rQxSUlJTvvRo0dNvXr1nPaqVaua5ORkp/3qFVjBwcEmPj4+x/aXXnrJNZ6iqlFxOnHihGnatKlrnC+88EK++48ZM8bVt1GjRubChQsFHseDDz7oHGPBggXO9rxWYBljzI4dO0z//v1N9erVjY+Pj6ldu7b5wx/+YI4cOWKM+b+/g5UqVXK2LViwwDz88MOmU6dOpnfv3mbmzJnm4sWLBR43AKDsYQUWAAA3kL1796p58+ZavHixOnXq5GxftGiRa78JEyaoUqVKzvOxY8dq1qxZzgqKw4cP66efflLr1q31zTffuPr26dNHt912m/O8a9eu6ty5s5YuXZrtmLy8vDRy5EhnlcehQ4e0YMEC9e3bVwkJCa5vXRs0aJD8/f0L9+ILIDw8XJ988onmzZun+Ph4JSYm6uLFizLGZNn3Wu4
"text/plain": [
"<Figure size 1200x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"body_fat_chart = {\n",
" \"male\": {\n",
" \"20-39\": {\n",
2025-09-24 09:57:15 +01:00
" \"bad\": [(0, 5), (25, 50)],\n",
" \"okay\": [(5, 10), (20, 25)],\n",
" \"good\": [(10, 20)]\n",
" },\n",
" \"40-59\": {\n",
2025-09-24 09:57:15 +01:00
" \"bad\": [(0, 5), (30, 50)],\n",
" \"okay\": [(5, 10), (20, 30)],\n",
" \"good\": [(10, 20)]\n",
" },\n",
" \"60-79\": {\n",
2025-09-24 09:57:15 +01:00
" \"bad\": [(0, 5), (30, 50)],\n",
" \"okay\": [(5, 10), (20, 25)],\n",
" \"good\": [(10, 25)]\n",
" }\n",
" },\n",
" \"female\": {\n",
" \"20-39\": {\n",
" \"bad\": [(0, 15), (40, 50)],\n",
" \"okay\": [(15, 20), (35, 40)],\n",
" \"good\": [(20, 35)]\n",
" },\n",
" \"40-59\": {\n",
" \"bad\": [(0, 20), (40, 50)],\n",
" \"okay\": [(20, 25), (35, 40)],\n",
" \"good\": [(25, 35)]\n",
" },\n",
" \"60-79\": {\n",
" \"bad\": [(0, 20), (40, 50)],\n",
" \"okay\": [(20, 25), (35, 40)],\n",
" \"good\": [(25, 35)]\n",
" }\n",
" }\n",
"}\n",
2025-09-24 09:57:15 +01:00
"\n",
"def create_body_fat_visualization(gender, age, body_fat_percentage):\n",
" # Determine age group\n",
" if 20 <= age <= 39:\n",
" age_group = \"20-39\"\n",
" elif 40 <= age <= 59:\n",
" age_group = \"40-59\"\n",
" elif 60 <= age <= 79:\n",
" age_group = \"60-79\"\n",
" else:\n",
" return \"Age out of range (20-79)\"\n",
" \n",
" # Get ranges for the specific gender and age group\n",
" ranges = body_fat_chart[gender.lower()][age_group]\n",
" \n",
" # Create figure\n",
" fig, ax = plt.subplots(figsize=(12, 3))\n",
" \n",
" # Define colors for different categories\n",
2025-09-24 09:57:15 +01:00
" colors = {'bad': '#ff6b6b', 'okay': '#ffeb3b', 'good': '#90ee90'}\n",
" \n",
" # Create the horizontal segments\n",
" bar_height = 0.4\n",
" y_position = 0\n",
" \n",
" # Plot each category's ranges\n",
" for category, ranges_list in ranges.items():\n",
" for range_tuple in ranges_list:\n",
" start, end = range_tuple\n",
" width = end - start\n",
" ax.barh(y_position, width, left=start, height=bar_height, \n",
2025-09-24 09:57:15 +01:00
" color=colors[category], alpha=0.9, edgecolor='black', linewidth=0.5)\n",
" \n",
" # Add the user's body fat percentage marker (triangle pointing down)\n",
2025-09-24 09:57:15 +01:00
" ax.plot(body_fat_percentage, y_position + bar_height / 2 + 0.05, 'v', markersize=15, color='black')\n",
" \n",
" # Customize the chart\n",
" ax.set_xlim(0, 50)\n",
2025-09-24 09:57:15 +01:00
" ax.set_ylim(-1, 1)\n",
" ax.set_xlabel('')\n",
" ax.set_title(f'Body Fat Percent - {body_fat_percentage:.1f}%', fontsize=16, fontweight='bold', pad=20)\n",
" \n",
2025-09-24 09:57:15 +01:00
" # Add age group and gender label on the left side\n",
" ax.text(-5, y_position, f'{age_group}\\n({gender[0].upper()})', ha='center', va='center', fontsize=12)\n",
" \n",
" # Adjust x-axis ticks to match the image\n",
" ax.set_xticks(range(0, 51, 5))\n",
" ax.set_xticklabels([f'{i}%' for i in range(0, 51, 5)])\n",
" \n",
" # Draw vertical lines for the tick marks\n",
" for tick in range(0, 51, 5):\n",
" ax.plot([tick, tick], [y_position - bar_height / 2, y_position - bar_height / 2 - 0.1], color='black', linewidth=1.5)\n",
" \n",
" # Remove y-axis and top/right spines\n",
" ax.set_yticks([])\n",
" ax.spines['left'].set_visible(False)\n",
" ax.spines['right'].set_visible(False)\n",
" ax.spines['top'].set_visible(False)\n",
2025-09-24 09:57:15 +01:00
" ax.spines['bottom'].set_visible(False)\n",
" \n",
" plt.tight_layout()\n",
" # plt.savefig('graphs/body_fat_percent_chart.png')\n",
" plt.show()\n",
"\n",
"# Create the chart using Keirstyn's data\n",
"gender = 'female'\n",
2025-09-24 09:57:15 +01:00
"age = 25\n",
"fat_percentage = 22.4\n",
"create_body_fat_visualization(gender, age, fat_percentage)"
]
},
{
"cell_type": "code",
"execution_count": 49,
"id": "bf55717b",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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0e+BgoaIiIhEqnpNnfriiy/Yv39/ldsnT54cbLLu3r07I0eOpEOHDqSlpVFSUsK6det47733KC0tZcaMGXzwwQdcfPHFnHnmmTRr1oznnnuOzZs3AzB06FAmT54cfO7mzZvXqsaUlBSuvPJKnnnmGRYvXsyiRYvo3r0777zzDgDjxo1j8ODBfPTRR9U+PjY2lpEjR3LiiSfSokULmjVrRk5ODjNnzmTJkiUYY7j99tuZPHky8fHxHDp0iNdffz34+AsvvJCTTz6ZnJwctm3bxpw5cyo9/6uvvkp+fj4AHTt25PLLLycxMZGdO3eyevVqFi1aVOPvdd68ecGPa7sXyrp164IfJyUl0bdv32Pe/4cffghOHwK49dZbK01LO56FCxcGP965cyd//OMfg9cLCgr4+OOPmTZtGm+88QYXX3xxjZ+33LRp05gwYQJjx45lwYIFzJw5E4DNmzdz991389JLLwHO9K1yw4YN4/zzz6e0tJQdO3bwzTffsHbt2mqff9iwYSxbtgxwfu71XQBBRERExGvqFTTeeeed4AF7RUOHDqVTp04kJiayadMm9u3bx6JFi9i1axf5+fmcfPLJrFq1itWrVwPwv//9j4svvpjRo0czevRoPv3002DQ6N+/P3fccUd9yuTXv/41zz77LMYYnnrqKfr27RtsiL7llltYuXLlUR973XXXcd1117Fy5UpWrVpFZmYmUVFRTJo0iSVLlgBw4MABli5dytixYykpKQkuZZqcnMybb75Z6QDcGFNpZabCwsLgxzfffDP33HNPpa9f3v9QE5s2bQp+3KlTpxo/bt68ebz44ovB67fffjuJiYnHfMzjjz8eXNkqOTmZG264ocZfD2DPnj2VrsfGxnLdddcRHx/Piy++SE5ODqWlpfziF79g4sSJtGjRolbPf+aZZ/LFF19gWRbGGM4+++zgClZvvPEGTz/9NAkJCZV+/k899RQjR46s9Dxbt26tNkB17Ngx+HHFn7uIiIiIOBp11amCggJuuukm/vOf/wQPSqvT2Cs/9enTh7PPPpvPP/+c999/n9TUVAC6dOnCpEmTjhk0li9fzhVXXMGaNWuO+TXKv4e0tDT69+/PmjVrOHjwIN26dWPYsGH06tWLgQMHMnHiRLp16xZ83NixY4NN6b///e+ZOnUqffv2pU+fPowYMYKxY8fWeAPFjIyM4Mc1HfmZOnUql156KSUlJYDT6PyHP/zhmI/Zt28f//nPf4LXr7/+epKTk2v09coVFxdXuv7YY4/x61//GnB+Jj/+8Y8ByM3NZerUqVx99dW1ev7LL788uAKWZVlcdtllwaBRXFzMqlWrgj/f8v//M844g1GjRtGrVy9OOOEETj31VAYOHFjt81cMPhV/7iIiIiLiqFfQePnll485ZeS3v/0tr7zyynGfp7rlVhvaLbfcwueff05JSUnwwPDmm28+5kF8QUEB559/fpWz79Wp+D28+eabXHLJJXz//ffs3r2bjz/+OPg5n8/HrbfeyhNPPAHARRddxB133ME//vEPioqK+Prrr/n666+D9+/SpQvTpk2jf//+tf6ej+fJJ5/kjjvuCIbAa665hhdffDG42tTRPP3008GRgOjoaH7zm9/U+muXh71yFZfTPXJp3bqMGLRu3brS9TZt2lS6np2dDTirZm3evJn
"text/plain": [
"<Figure size 800x800 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Filter df_2 for Keirstyn Moran\n",
"keirstyn_data = df_2[df_2['LastName'].str.contains('Moran', case=False, na=False)]\n",
"# Get the fat mass percentage for Keirstyn\n",
"fat_percentage = keirstyn_data['Adult_FMP'].iloc[0]\n",
2025-09-24 09:57:15 +01:00
"weight_kg = keirstyn_data['Weight'].iloc[0]\n",
"age = keirstyn_data['Age'].iloc[0]\n",
"gender = keirstyn_data['Gender'].iloc[0]\n",
"lean_percentage = 100 - fat_percentage\n",
"\n",
"# Create donut chart\n",
2025-09-24 09:57:15 +01:00
"fat_mass_lbs = 27.6\n",
"lean_mass_lbs = 95.4\n",
"\n",
2025-09-24 09:57:15 +01:00
"# Calculate percentages from the provided weights\n",
"total_weight = fat_mass_lbs + lean_mass_lbs\n",
"fat_percentage = (fat_mass_lbs / total_weight) * 100\n",
"lean_percentage = (lean_mass_lbs / total_weight) * 100\n",
"\n",
2025-09-24 09:57:15 +01:00
"# Data for the chart\n",
"sizes = [fat_percentage, lean_percentage]\n",
"colors = ['#fde3ac', '#ff9966'] # Light yellow/tan and orange from the image\n",
"\n",
"plt.figure(figsize=(8, 8))\n",
"\n",
"# Create the donut chart without labels first\n",
2025-09-24 09:57:15 +01:00
"wedges, texts, autotexts = plt.pie(sizes,\n",
" autopct='', # Remove auto percentages\n",
2025-09-24 09:57:15 +01:00
" startangle=90,\n",
" wedgeprops=dict(width=0.5, edgecolor='w'),\n",
" colors=colors,\n",
" labels=['', '']) # Remove default labels\n",
2025-09-24 09:57:15 +01:00
"\n",
"# Add custom text annotations positioned manually\n",
"plt.text(-1, 1, 'Fat Mass (27.6lbs)\\n22.4%', \n",
" fontsize=14, fontweight='bold', ha='center', va='center',\n",
" bbox=dict(boxstyle=\"round,pad=0.3\", facecolor='white', alpha=0.8))\n",
2025-09-24 09:57:15 +01:00
"\n",
"plt.text(1, -1, 'Lean Mass (95.4lbs)\\n77.6%', \n",
" fontsize=14, fontweight='bold', ha='center', va='center',\n",
" bbox=dict(boxstyle=\"round,pad=0.3\", facecolor='white', alpha=0.8))\n",
2025-09-24 09:57:15 +01:00
"\n",
"# Set the title\n",
"plt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle\n",
"plt.savefig('graphs/body_composition_chart.png', bbox_inches='tight', dpi=600)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 50,
"id": "21c1c0a5",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x200 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"# Set a common style\n",
"sns.set_theme(style=\"whitegrid\")\n",
"\n",
"# Define the segments with muted colors\n",
"segments = [\n",
" ('#F8A8A8', 0, 15), # Muted Red/Salmon: 0% to 15%\n",
" ('#FFEECC', 15, 5), # Pale Yellow/Cream: 15% to 20%\n",
" ('#D0F0C0', 20, 15), # Pale Green/Mint: 20% to 35%\n",
" ('#FFEECC', 35, 5), # Pale Yellow/Cream: 35% to 40%\n",
" ('#F8A8A8', 40, 10) # Muted Red/Salmon: 40% to 50%\n",
"]\n",
"\n",
"target_value = 22.4\n",
"demographic = \"20-39\\n(F)\"\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 2))\n",
"\n",
"# Create the Segmented Bar\n",
"for color, start, length in segments:\n",
" ax.barh(y=0, width=length, left=start, height=1, color=color, edgecolor='black', linewidth=0.5)\n",
"\n",
"# Add the Indicator (Triangle)\n",
"ax.plot(target_value, 1.05, marker='v', color='black', markersize=10, clip_on=False, transform=ax.get_xaxis_transform())\n",
"\n",
"# Set Axis Properties and Labels\n",
"ax.set_xlim(0, 50)\n",
"ax.set_xticks(range(0, 51, 5))\n",
"ax.set_yticks([])\n",
"ax.text(-0.05, 0, demographic, transform=ax.get_yaxis_transform(), va='center', ha='right', fontsize=12)\n",
"\n",
"ax.set_xlim(0, 50)\n",
"ticks = range(0, 51, 5)\n",
"ax.set_xticks(ticks)\n",
"labels = [f\"{t}%\" for t in ticks]\n",
"ax.set_xticklabels(labels)\n",
"# Clean up spines and add small ticks\n",
"ax.spines['right'].set_visible(False)\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['left'].set_visible(False)\n",
"ax.spines['bottom'].set_visible(True)\n",
"\n",
"for x in range(0, 51, 5):\n",
" ax.plot([x, x], [-0.05, -0.01], color='black', transform=ax.get_xaxis_transform(), clip_on=False)\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig('graphs/body_fat_percent_chart.png', bbox_inches='tight', dpi=300)\n",
"plt.show() # This is where the file is saved and displayed above."
]
},
{
"cell_type": "code",
"execution_count": 51,
"id": "10687f82",
"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>Parameters</th>\n",
" <th>Best</th>\n",
" <th>LLN</th>\n",
" <th>Pred.</th>\n",
" <th>%Pred.</th>\n",
" <th>ZScore</th>\n",
" <th>PRE#1</th>\n",
" <th>PRE#2</th>\n",
" <th>PRE#3</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>FVC</td>\n",
" <td>4.24</td>\n",
" <td>3.03</td>\n",
" <td>3.79</td>\n",
" <td>112.0</td>\n",
" <td>0.95</td>\n",
" <td>4.24</td>\n",
" <td>4.17</td>\n",
" <td>4.15</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>FEV1</td>\n",
" <td>3.26</td>\n",
" <td>2.53</td>\n",
" <td>3.16</td>\n",
" <td>103.3</td>\n",
" <td>0.28</td>\n",
" <td>3.26</td>\n",
" <td>3.21</td>\n",
" <td>3.14</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>FEV1/FVC%</td>\n",
" <td>76.89</td>\n",
" <td>72.47</td>\n",
" <td>83.78</td>\n",
" <td>91.8</td>\n",
" <td>-1.05</td>\n",
" <td>76.90</td>\n",
" <td>77.00</td>\n",
" <td>75.70</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>PEF</td>\n",
" <td>684.00</td>\n",
" <td>222.00</td>\n",
" <td>384.00</td>\n",
" <td>178.7</td>\n",
" <td>-</td>\n",
" <td>444.00</td>\n",
" <td>438.00</td>\n",
" <td>684.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>FEF2575</td>\n",
" <td>2.74</td>\n",
" <td>2.15</td>\n",
" <td>3.42</td>\n",
" <td>80.2</td>\n",
" <td>-0.84</td>\n",
" <td>2.74</td>\n",
" <td>2.68</td>\n",
" <td>2.48</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>FEF25</td>\n",
" <td>6.08</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>0.0</td>\n",
" <td>-</td>\n",
" <td>6.08</td>\n",
" <td>6.00</td>\n",
" <td>5.53</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>FEF50</td>\n",
" <td>3.06</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>0.0</td>\n",
" <td>-</td>\n",
" <td>3.06</td>\n",
" <td>3.10</td>\n",
" <td>2.77</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>FEF75</td>\n",
" <td>1.06</td>\n",
" <td>0.71</td>\n",
" <td>1.41</td>\n",
" <td>75.1</td>\n",
" <td>-0.72</td>\n",
" <td>1.06</td>\n",
" <td>1.12</td>\n",
" <td>0.94</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>PEFTime</td>\n",
" <td>79.00</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>49.0</td>\n",
" <td>-</td>\n",
" <td>79.00</td>\n",
" <td>40.00</td>\n",
" <td>39.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>EVol</td>\n",
" <td>78.00</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>77.0</td>\n",
" <td>-</td>\n",
" <td>78.00</td>\n",
" <td>77.00</td>\n",
" <td>197.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>FEV6</td>\n",
" <td>4.22</td>\n",
" <td>3.03</td>\n",
" <td>3.79</td>\n",
" <td>111.4</td>\n",
" <td>-</td>\n",
" <td>4.22</td>\n",
" <td>4.17</td>\n",
" <td>4.13</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Parameters Best LLN Pred. %Pred. ZScore PRE#1 PRE#2 PRE#3\n",
"0 FVC 4.24 3.03 3.79 112.0 0.95 4.24 4.17 4.15\n",
"1 FEV1 3.26 2.53 3.16 103.3 0.28 3.26 3.21 3.14\n",
"2 FEV1/FVC% 76.89 72.47 83.78 91.8 -1.05 76.90 77.00 75.70\n",
"3 PEF 684.00 222.00 384.00 178.7 - 444.00 438.00 684.00\n",
"4 FEF2575 2.74 2.15 3.42 80.2 -0.84 2.74 2.68 2.48\n",
"5 FEF25 6.08 NaN NaN 0.0 - 6.08 6.00 5.53\n",
"6 FEF50 3.06 NaN NaN 0.0 - 3.06 3.10 2.77\n",
"7 FEF75 1.06 0.71 1.41 75.1 -0.72 1.06 1.12 0.94\n",
"8 PEFTime 79.00 NaN NaN 49.0 - 79.00 40.00 39.00\n",
"9 EVol 78.00 NaN NaN 77.0 - 78.00 77.00 197.00\n",
"10 FEV6 4.22 3.03 3.79 111.4 - 4.22 4.17 4.13"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spirometry_data = pd.read_csv('data/spirometry_data.csv')\n",
"spirometry_data"
]
},
{
"cell_type": "code",
"execution_count": 53,
"id": "d468d687",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA/YAAAFdCAYAAACpXPZQAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAeOJJREFUeJzt3Xd8FVX+//HXbek9oYQiRREioEDoSBXBglKUFSuIAvYCouJa1i4KyIqg4FpAQEREmgorXzoivUSX4tKkQ0i9qTc38/sjv8wSCJB+U97PxyMPJjNn5nzuyb0hnzlnzrEYhmEgIiIiIiIiIhWS1dMBiIiIiIiIiEjRKbEXERERERERqcCU2IuIiIiIiIhUYErsRURERERERCowJfYiIiIiIiIiFZgSexEREREREZEKTIm9iIiIiIiISAWmxF5ERERERESkAlNiLyIiIiIiIlKBKbEXERERERERqcCU2IuIiIiIiIhUYErsRURERERERCowJfYiIiIiIiIiFZgSexEREQ/56quvuP/++83vW7Zsyd69e8uk7uXLl9OjR48yqUtERERKlxJ7Eaky7r//fr766qsC7889FhUVxZ49e8x9SUlJNG7cmKNHj5ZSpFKe3H///TRr1oyWLVvStm1b7r//fn7//fdSqWv79u00btz4suUmTZrEY489VioxiIiISMWjxF5E5DKCgoKYMGGCp8MQD3ruuefYvn07a9euJSoqKt+k2uVyeSAyEREREbB7OgARkfLunnvu4euvv2bz5s20adPG0+GIB3l7e3PnnXcyffp0HnnkEcLCwkhJSWHt2rU8++yzDBo0iClTprB48WKSk5Np2bIlr7/+OjVq1ADgzz//5O9//zt//vknzZo1o3nz5nmu37hxYxYsWEBUVBQAS5YsYdq0aRw9epTg4GCefPJJgoKCmDp1KtnZ2bRs2RLI6ek3DIOvv/6a2bNnExsbS1RUFP/4xz+48sorATh58iQvvfQSO3bsoH79+vTq1asMW67sxcXF4XQ6i3x+YGAgoaGhJRiRiIhI6VFiLyJyGcHBwQwbNozx48czZ84cT4cjHpSWlsZ3331H7dq1CQkJ4ccff+Tjjz/mww8/JCMjgw8//JA//viD2bNnExISwocffsjIkSOZNWsWWVlZPProo9x6663MnDmTP/74gxEjRlx06P2KFSt48803mThx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
"text/plain": [
"<Figure size 1150x360 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.transforms as mtransforms\n",
"from matplotlib.patches import FancyBboxPatch\n",
"# Ensure data is loaded\n",
"try:\n",
" spirometry_df = spirometry_data.copy()\n",
"except NameError:\n",
" spirometry_df = pd.read_csv('data/spirometry_data.csv')\n",
"\n",
"# Coerce numeric columns\n",
"for col in ['Best', 'LLN', 'Pred.', '%Pred.', 'ZScore']:\n",
" if col in spirometry_df.columns:\n",
" spirometry_df[col] = pd.to_numeric(spirometry_df[col], errors='coerce')\n",
"\n",
"# Select rows of interest and prepare display values\n",
"rows_map = {\n",
" 'Lung Volume': 'FVC',\n",
" 'Lung Power': 'FEV1',\n",
" 'Power/Volume': 'FEV1/FVC%'\n",
"}\n",
"\n",
"records = []\n",
"for label, param in rows_map.items():\n",
" row = spirometry_df.loc[spirometry_df['Parameters'].str.strip() == param]\n",
" if row.empty:\n",
" continue\n",
" row = row.iloc[0]\n",
" records.append({\n",
" 'label': label,\n",
" 'param': param,\n",
" 'best': row['Best'],\n",
" 'pct': row['%Pred.'],\n",
" 'z': row['ZScore']\n",
" })\n",
"\n",
"# Figure setup\n",
"os.makedirs('graphs', exist_ok=True)\n",
"fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(11.5, 3.6), sharex=True,\n",
" gridspec_kw={'hspace': 0.65})\n",
"\n",
"x_min, x_max = -5, 3\n",
"# Segment colors: red -> orange -> yellow -> green\n",
"segments = [\n",
" (-5, -4, '#f4a7a7'), # red-ish\n",
" (-4, -3, '#f7c49a'), # orange-ish\n",
" (-3, -1.7, '#f6e3a3'), # yellow-ish\n",
" (-1.7, 3, '#c9f0cc'), # green-ish\n",
"]\n",
"\n",
"ticks = np.arange(x_min, x_max + 1, 1)\n",
"labels = [str(i) for i in ticks]\n",
"\n",
"# Plot each row\n",
"for ax, rec in zip(axes, records):\n",
" # Background segments\n",
" for a, b, color in segments:\n",
" ax.barh(0, width=b-a, left=a, height=0.6, color=color, edgecolor='none')\n",
"\n",
" # LLN (-1) and Predicted (0) markers\n",
" # ax.axvline(-1, color='black', lw=1)\n",
" ax.axvline(0, color='black', lw=1)\n",
"\n",
" # Z-score pointer (downward triangle) at top of each panel\n",
" if pd.notna(rec['z']):\n",
" trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)\n",
" ax.plot(float(rec['z']), 1.2, marker='v', markersize=12, color='dimgray',\n",
" transform=trans, clip_on=False)\n",
"\n",
" # Labels, ticks, and styling\n",
" ax.set_title(rec['label'], loc='left', fontsize=11, fontweight='bold', pad=2)\n",
" ax.set_xlim(x_min, x_max)\n",
" ax.set_yticks([])\n",
" ax.set_xticks(ticks)\n",
" ax.set_xticklabels(labels, fontsize=8)\n",
" ax.set_xlabel('')\n",
"\n",
"# Add x-axis label to the bottom axis\n",
"# axes[-1].set_xlabel('Z-score', fontsize=10)\n",
"\n",
"# Top annotations\n",
"axes[0].text(-1.7, 0.45, 'LLN', ha='center', va='bottom', fontsize=9)\n",
"axes[0].text(0, 0.45, 'Predicted', ha='center', va='bottom', fontsize=9)\n",
"\n",
"# Right-side summary boxes\n",
"fig.subplots_adjust(right=0.78)\n",
"box_ax = fig.add_axes([0.805, 0.06, 0.18, 0.90]) # [left, bottom, width, height]\n",
"box_ax.axis('off')\n",
"\n",
"# Helper to draw a pill-shaped text box\n",
"\n",
"def pill(ax, xy, text):\n",
" x, y = xy\n",
" # Draw rounded rectangle background\n",
" bbox = FancyBboxPatch((x-0.48, y-0.09), 0.96, 0.18,\n",
" boxstyle='round,pad=0.02,rounding_size=0.08',\n",
" ec='#dddddd', fc='#f3f3f3', linewidth=1.0)\n",
" ax.add_patch(bbox)\n",
" ax.text(x, y+0.025, text, ha='center', va='center', fontsize=11, fontweight='bold')\n",
" ax.text(x, y-0.055, 'of predicted', ha='center', va='center', fontsize=9, color='#555555')\n",
"\n",
"box_ax.set_xlim(0, 1)\n",
"box_ax.set_ylim(0, 1)\n",
"\n",
"# Prepare display strings and positions (top to bottom)\n",
"right_items = []\n",
"for rec in records:\n",
" name = 'FVC' if rec['param'] == 'FVC' else ('FEV1' if rec['param'] == 'FEV1' else 'FEV1/FVC')\n",
" unit = 'L' if rec['param'] in ('FVC', 'FEV1') else '%'\n",
" value_fmt = f\"{rec['best']:.2f}{unit}\"\n",
" pct_fmt = f\"{rec['pct']:.1f}%\"\n",
" right_items.append((name, value_fmt, pct_fmt))\n",
"\n",
"# Sort to match image order on the right (FVC, FEV1, FEV1/FVC)\n",
"order = ['FVC', 'FEV1', 'FEV1/FVC']\n",
"right_items_sorted = [next(item for item in right_items if item[0] == k) for k in order]\n",
"\n",
"ys = [0.82, 0.48, 0.15]\n",
"for (name, value_fmt, pct_fmt), y in zip(right_items_sorted, ys):\n",
" main_line = f\"{name}\\n{value_fmt} → {pct_fmt}\"\n",
" pill(box_ax, (0.5, y), main_line)\n",
"\n",
"plt.savefig('graphs/spirometry_chart.png', dpi=300, bbox_inches='tight')\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "report_generation",
"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.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}