Files
bio-performx/notebooks/graphs.ipynb
T

1126 lines
917 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
"execution_count": 34,
"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\n",
"import os"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "97da3d1c",
"metadata": {},
"outputs": [],
"source": [
"base_dir = os.path.dirname(os.path.abspath('.'))"
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "b0ee2af1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Smoothed columns created:\n",
"['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"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/tmp/ipykernel_65416/3076306744.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(f'{base_dir}/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",
"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",
"df['CHO'] = df['EE(kcal/min)'] * df['CARBS(%)']/100\n",
"df['FAT'] = df['EE(kcal/min)'] * df['FAT(%)']/100\n",
"# Smooth key columns using rolling window\n",
"window_size = 10\n",
"\n",
"# List of columns to smooth\n",
"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",
"\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",
"\n",
"print(\"Smoothed columns created:\")\n",
"print([col for col in df.columns if '_smoothed' in col])"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "99116a35",
"metadata": {},
"outputs": [],
"source": [
"df_2 = pd.read_excel(f'{base_dir}/data/SECA body comp for all patients.xlsx')\n",
"spirometry_data = pd.read_csv(f'{base_dir}/data/spirometry_data.csv')\n",
"oxygenation = pd.read_csv(f'{base_dir}/data/Keirstyn Train Red NIRS Muscle Oxygen.csv')"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "fbd292c3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"22.369999999999997\n"
]
}
],
"source": [
"print(df['VO2 Pulse'].max())"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "4c439b2c",
"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",
"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",
"fat_mass_lbs = 27.6\n",
"lean_mass_lbs = 95.4\n",
"\n",
"# 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",
"# 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",
"wedges, texts, autotexts = plt.pie(sizes,\n",
" autopct='', # Remove auto percentages\n",
" startangle=90,\n",
" wedgeprops=dict(width=0.5, edgecolor='w'),\n",
" colors=colors,\n",
" labels=['', '']) # Remove default labels\n",
"\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",
"\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",
"\n",
"# Set the title\n",
"plt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle\n",
"plt.savefig(f'{base_dir}/graphs/body_composition_chart.png', bbox_inches='tight', dpi=600)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "a565f1b3",
"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(f'{base_dir}/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": 41,
"id": "470e871e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Displaying Metabolism Chart...\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x250 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Displaying Fuel Source Chart...\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x250 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"import matplotlib.patches as patches\n",
"\n",
"def plot_metabolism_chart():\n",
" \"\"\"\n",
" Generates and displays the 'Slow vs Fast Metabolism' chart.\n",
" \"\"\"\n",
" fig, ax = plt.subplots(figsize=(10, 2.5))\n",
"\n",
" # --- Chart data and positions ---\n",
" categories = ['Very Slow', 'Slow', 'Average', 'Fast', 'Very Fast']\n",
" positions = [1500, 3000, 4500, 6000, 7500]\n",
" kcal_value = 1386\n",
" # Position the indicator and highlight based on the kcal value\n",
" # For this example, we'll place it in the 'Very Slow' section.\n",
" indicator_pos = kcal_value\n",
" highlight_end = kcal_value\n",
"\n",
" # --- Main Bar (Background) ---\n",
" # Create a rounded rectangle for the main bar\n",
" main_bar = patches.FancyBboxPatch(\n",
" (0, 0.4), 9000, 0.2,\n",
" boxstyle=\"round,pad=0,rounding_size=0.1\",\n",
" ec=\"none\", fc=\"#E0E0E0\",\n",
" )\n",
" ax.add_patch(main_bar)\n",
"\n",
" # --- Highlighted Bar ---\n",
" # Create a rounded rectangle for the highlighted section\n",
" highlight_bar = patches.FancyBboxPatch(\n",
" (0, 0.4), highlight_end, 0.2,\n",
" boxstyle=\"round,pad=0,rounding_size=0.1\",\n",
" ec=\"none\", fc=\"#B2FFC8\",\n",
" )\n",
" ax.add_patch(highlight_bar)\n",
"\n",
" # --- Text and Labels ---\n",
" # Add the kcal text inside the highlighted bar\n",
" ax.text(highlight_end / 2, 0.5, f'{kcal_value}kCals', \n",
" ha='center', va='center', color='#006400', fontsize=14, weight='bold')\n",
"\n",
" # --- Indicator Triangle ---\n",
" ax.plot(indicator_pos, 0.65, 'v', markersize=15, color='#606060', clip_on=False)\n",
"\n",
" # --- Ticks and Labels ---\n",
" # Add category labels and tick marks below the bar\n",
" for pos, label in zip(positions, categories):\n",
" ax.text(pos, 0.15, label, ha='center', va='center', fontsize=12, color='#333333')\n",
" ax.plot([pos, pos], [0.35, 0.39], color='grey', lw=5)\n",
"\n",
"\n",
" # --- Chart Styling ---\n",
" ax.set_title('Slow vs Fast Metabolism', fontsize=18, weight='bold', loc='left')\n",
" ax.set_xlim(0, 9000) # Add padding before and after\n",
" ax.set_ylim(0, 1)\n",
" \n",
" # Hide axes and spines for a cleaner look\n",
" ax.axis('off')\n",
" \n",
" plt.tight_layout()\n",
" plt.savefig(f'{base_dir}/graphs/metabolism_chart.png', bbox_inches='tight', dpi=300)\n",
" plt.show()\n",
"\n",
"\n",
"def plot_fuel_source_chart():\n",
" \"\"\"\n",
" Generates and displays the 'Fuel Source' chart.\n",
" \"\"\"\n",
" fig, ax = plt.subplots(figsize=(10, 2.5))\n",
"\n",
" # --- Chart data and positions ---\n",
" fat_percentage = 33\n",
" carb_percentage = 100 - fat_percentage\n",
" optimal_point = 75\n",
"\n",
" # --- Main Bars (Fats and Carbs) ---\n",
" # Fats bar (yellow)\n",
" fats_bar = patches.FancyBboxPatch(\n",
" (0, 0.4), fat_percentage, 0.2,\n",
" boxstyle=\"round,pad=0,rounding_size=0.1\",\n",
" ec=\"none\", fc=\"#FEEAAB\",\n",
" )\n",
" ax.add_patch(fats_bar)\n",
"\n",
" # Carbs bar (blue) - starts where the fats bar ends\n",
" carbs_bar = patches.FancyBboxPatch(\n",
" (fat_percentage, 0.4), carb_percentage, 0.2,\n",
" boxstyle=\"round,pad=0,rounding_size=0.1\",\n",
" ec=\"none\", fc=\"#A7F5FF\",\n",
" )\n",
" ax.add_patch(carbs_bar)\n",
"\n",
" # --- Text and Labels ---\n",
" # Add percentage labels inside the bars\n",
" ax.text(fat_percentage / 2, 0.5, f'Fats\\n{fat_percentage}%', \n",
" ha='center', va='center', color='#333333', fontsize=12, weight='bold')\n",
" ax.text(fat_percentage + carb_percentage / 2, 0.5, f'Carbs\\n{100-fat_percentage}%', \n",
" ha='center', va='center', color='#333333', fontsize=12, weight='bold')\n",
" \n",
" # Add 'Optimal' label\n",
" ax.text(optimal_point, 0.75, 'Optimal', ha='center', va='center', fontsize=12)\n",
"\n",
" # --- Indicator Triangle ---\n",
" ax.plot(fat_percentage, 0.65, 'v', markersize=15, color='#606060', clip_on=False)\n",
" \n",
" # --- Ticks and Labels ---\n",
" positions = [0, 25, 50, 75, 100]\n",
" for pos in positions:\n",
" ax.text(pos, 0.15, str(pos), ha='center', va='center', fontsize=12, color='#333333')\n",
" ax.plot([pos, pos], [0.35, 0.39], color='grey', lw=5)\n",
" \n",
" # Add a special tick for the 'Optimal' point\n",
" ax.plot([optimal_point, optimal_point], [0.6, 0.7], color='black', lw=2)\n",
"\n",
" # --- Chart Styling ---\n",
" ax.set_title('Fuel Source', fontsize=18, weight='bold', loc='left')\n",
" # ax.set_xlim(-5, 105) # Add padding\n",
" ax.set_ylim(0, 1)\n",
"\n",
" # Hide axes and spines\n",
" ax.axis('off')\n",
"\n",
" plt.tight_layout()\n",
" plt.savefig(f'{base_dir}/graphs/fuel_source_chart.png', bbox_inches='tight', dpi=300)\n",
" plt.show()\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" # Call the functions to display each plot\n",
" print(\"Displaying Metabolism Chart...\")\n",
" plot_metabolism_chart()\n",
" \n",
" print(\"\\nDisplaying Fuel Source Chart...\")\n",
" plot_fuel_source_chart()\n"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "0ab6f0b0",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA/YAAAFdCAYAAACpXPZQAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAeJJJREFUeJzt3Xd8FVX+//HXbek9oYQiRREioEDoSFXBglJWVqwgCtiwgKi4lrWLArKiKLiKICAiIk2FlS9NEOklKsUFQTqEhCQ39d6b+f2RX2YJBEi/Ke/n45EHk5kzcz735N6Qz5wz51gMwzAQERERERERkQrJ6u0ARERERERERKTolNiLiIiIiIiIVGBK7EVEREREREQqMCX2IiIiIiIiIhWYEnsRERERERGRCkyJvYiIiIiIiEgFpsReREREREREpAJTYi8iIiIiIiJSgSmxFxEREREREanAlNiLiIiIiIiIVGBK7EVEREREREQqMCX2IiIiIiIiIhWYEnsRERERERGRCkyJvYiIiJd8/vnn3Hvvveb3LVu2ZM+ePWVS9/Lly+nRo0eZ1CUiIiKlS4m9iFQZ9957L59//nmB9+cei4mJYffu3ea+5ORkGjduzOHDh0spUilP7r33Xpo1a0bLli1p27Yt9957L7/++mup1LVt2zYaN258yXKTJk3ikUceKZUYREREpOJRYi8icgkhISFMmDDB22GIFz399NNs27aNn376iZiYmHyTapfL5YXIRERERMDu7QBERMq7u+66iy+++IJNmzbRpk0bb4cjXuTr68vtt9/O9OnTeeihh4iIiCA1NZWffvqJp556ioEDBzJ58mQWL15MSkoKLVu25JVXXqFGjRoA/PHHH/zjH//gjz/+oFmzZjRv3jzP9Rs3bsyCBQuIiYkBYMmSJUydOpXDhw8TGhrKiBEjCAkJYcqUKWRnZ9OyZUsgp6ffMAy++OILZs+eTXx8PDExMfzzn//k8ssvB+D48eM8//zzbN++nfr169OzZ88ybLmyl5CQgNPpLPL5wcHBhIeHl2BEIiIipUeJvYjIJYSGhjJ06FDGjx/PnDlzvB2OeFF6ejpff/01tWvXJiwsjO+++44PPviA9957j8zMTN577z1+++03Zs+eTVhYGO+99x4jR45k1qxZuN1uHn74YW655RZmzpzJb7/9xvDhwy849H7FihW89tprTJw4kXbt2pGYmMiJEye46qqrGD58OLt27WLy5Mlm+dmzZzNv3jw+/vhj6tSpw+zZs3nooYf47rvv8PHxYdSoUdSpU4d169Zx9OhRhg4dWlbNVuZcLhdjx44lJSWlyNcICQnhtddew+FwlGBkIiIipUND8UVECmDQoEEcOXKE5cuXezsU8YIJEybQunVrrr/+evbv328m1J06daJz585YrVb8/Pz48ssvGTNmDNWrV8fHx4cnn3ySrVu3cuzYMbZv305iYiKPPfYYPj4+tGzZkptuuumCdc6ePZt7772XDh06YLVaiYyM5Kqrrrpo+ccff5z69etjt9u57777yMjIYOfOnRw7dozNmzfzzDPP4O/vz+WXX87AgQNLvJ3KC7vdTkREBBaLpUjnWywWwsPDsdvV/yEiIhWD/scSESkAPz8/HnvsMSZMmMCsWbO8HY6UsZEjRzJ48ODz9teqVcvcTkxMJC0tjbvvvjtPQulwODh27BgnT56kevXqeXqAa9euzf79+/Ot8+jRo/Tt27fAMR45coTRo0djs9nMfS6Xi+PHj+NwOPD19SUyMjJP3ZWVxWLh1ltv5YMPPijS+YZhcOuttxb5xoCIiEhZU2IvIlJAt99+O9OmTWPBggXeDkXKibMTv7CwMPz9/Zk7d675XPvZNm/ezMmTJ3G5XGZyf/To0Qteu1atWhw8ePCS9eaqWbMmzz//PF26dDnv2LFjx8jMzOT06dNmcn+xuiuDmJgY6tWrx19//YVhGAU+z2KxcNlll5nzHIiIiFQEGoovIlWKx+MhMzPT/MrKyrro/rPZbDaeeuopPv7447IOWyoAq9XKwIEDGTt2LMeOHQNyevG///57AK655hpCQ0OZPHkyWVlZ7Nixgx9++OGC1xs4cCAzZsxg48aNZGdnc/r0aX7//XcAoqKiOHr0KG632yx/99138/7775sjAJxOJ8uXL8fpdBIdHU2rVq0YN24cGRkZ7N+/n6+++qq0mqJcyO21L0xSD+qtFxGRikmJvYhUKe+88w5XX321+XXjjTdedP+5evXqRb169coyZKlARo4cSYsWLRg0aBAtW7bkb3/7G2vXrgVyhuR/9NFHrF27lnbt2jFu3Dj69+9/wWtdf/31jBkzhldffZXY2Fhuv/129u7dC8CNN95IUFAQHTp0oHXr1gDcc8899OvXjxEjRtCqVStuuukmlixZYl5v/PjxHD9+nA4dOvD000/zt7/9rRRbonzI7bUvaJJusVioV6+eeutFRKTCsRiFvZWdj+eee45vv/0WgBkzZtCuXbtiB1YW5s+fz5gxYwAYMWIEjz32WJ7jbrebTp06cebMGcLDw1m7dm2BJtI5fPgw1113HQCPPfYYI0aMKPngRURE5JJ+//33Qj1r/9hjj110kkIREZHyqEr32F9//fXmc47Lli077/j69es5c+YMAD179tTsuCIiIhVMQXvt1VsvIiIVWZVO7ENCQujcuTMAe/fuZd++fXmOL1261Ny+5ZZbyjQ2ERERKb6CPmuvZ+tFRKQiK7PE/vDhwzRu3JjGjRszadIkc//8+fPN/Rs2bABgw4YN5r4vv/ySt99+m44dO9KmTRueeOIJEhMT81z7l19+oW/fvjRv3pxbb72V1atXc++999K4cWN69Ohx0bjOTtjPnsTI7Xab61VXr16dNm3aALB//35GjRpFp06daNasGZ07d2bMmDEFml049zU999xz5r6zX+v8+fPPa6uJEycyYcIE2rVrR9u2bXn33XfxeDx8//339OrVi1atWjFkyBAOHz6cpy6n08m7775Lr169aNasGW3atOGhhx7it99+u2ScIiIilcmleu3VWy8iIhVduR9bPn78eFJSUszvly5dit1uZ/z48QD8+eefDB061JzBeu/evTzyyCOEhIQU6Prdu3fH39+f9PR0li1bZj5n/8svv5jD8G+88UasViu7d+/mzjvvJC0tzTz/5MmTzJ8/n1WrVvH1119Tp06dknjZpi+//NKMA+Df//43+/fvZ+XKlWbvw7p163j66aeZM2cOAKmpqdx5553mJEuQs5bxypUrWbduHdOmTTMnWxIREansLrWuvXrrRUSkoiv3Q/GtViuzZ89m3bp1XHnllUDO8/DZ2dkA5rJBAE8//TRbtmzhmWeeISEhoUDXDwwMpGvXrkDe4fj5DcN/8803zaT+nXfeYcuWLTz77LMAJCQk8N577xX35Z4nKyuL2bNns2LFCgIDAwFYsWIFt99+O5s2bTJn7t62bRsnTpwAYPr06ezduxebzcaHH35IXFwcy5Yto169emRlZfHWW2+VeJwiIiLl2YV67dVbLyIilUG5T+xvv/12YmNjiYqKokuXLkBO73N8fDyQk9BCzpq+DzzwAEFBQQwaNIjo6OgC13HucHy3282PP/4IQO3atWnRogXp6els3rwZgGbNmtGnTx+CgoK4//77qVmzJoC5pFFJuu6664iNjaV27dpcfvnl5v7hw4cTEhJCx44dzX25jwOsWbMGyFmX+9FHH6V58+b06tWLgwcPAvDrr7/idDpLPFYREZHy6kLP2qu3XkREKgOvD8X3eDwXPV6/fn1z29fX19zO7aU/efIkkPMcvNX6v/sUNWrU4NixYwWKoWvXrgQGBpKamsqyZcto2bKlOfz95ptvBiA5OdmM9eybBhaLhZo1a3L8+HHOnDlzyddzrtyRBxdSu3Ztc9vPz8/czo0hd1Z/+F+bFGS0QlJSEkFBQYWKVUREpCLL7bX/66+/MAwDi8XCZZddpt56kXIsOzsbj8dDdnb2JSfBFCmPLBYLVqsVm82WJ18taWWW2Pv4+JjbmZmZ5va5k76d6+wl5vK7m169enUOHTrEqVOnzP+
"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(f'{base_dir}/graphs/spirometry_chart.png', dpi=300, bbox_inches='tight')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 43,
"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": [
"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",
"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.grid(True, alpha=0.1)\n",
"ax1.set_ylim(0, min(8, df['VT(l)_smoothed'].max()))\n",
"\n",
"# Set x-axis limits to remove padding\n",
"ax1.set_xlim(0, df['T(sec)'].max())\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))\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(f'{base_dir}/graphs/respiratory.png', dpi=300, bbox_inches='tight')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "06244aa2",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAAMfCAYAAAA5Z570AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4U9X/B/B3uvekA0rpADrYyChLKbJBNggypIIgSxFEvg6mICCIIojgzwEIyJSlKEvZm8oeZXSwSinQ0kF37u+PY1aTdKRJB7xfz5Mnyb3nnnNucnPTfnLu58gkSZJARERERERERERERERazMq6A0RERERERERERERE5RWD6EREREREREREREREejCITkRERERERERERESkB4PoRERERERERERERER6MIhORERERERERERERKQHg+hERERERERERERERHowiE5EREREREREREREpAeD6EREREREREREREREejCITkRERERERERERESkB4PoRERUYv7+/pDJZJDJZJgxY0ZZd0dp5cqVyn7JZDKNdeHh4crlERERyuWxsbEa2xw4cKB0O10E+vpOVNGU13NHeXPq1Cl07twZ7u7uMDMzU75mycnJZd21CnHOpKIr6HuzNLYvz2bMmKHcL39//7LuTomV5/MKERFRecQgOhFRBXDgwAGNf0r13SpSQDX/P9q6Ai/qweL8/7A+r/+oPy8BcvXgqPrNzs4OgYGBGDBgAPbv31/W3TSp/J/b2NjYUms7IiKizD8fJQ2QmzLArv76hIeHG7VuY3vw4AE6d+6MXbt24cmTJ5AkqVjbP3nyBFOmTEHDhg3h6OgIKysreHp6IjQ0FL169cLMmTNx584dE/WeFIryXa1+zJfH4/J5/d41NX1/s9nY2MDPzw8DBgzAoUOHSrVPJT2vEBERvYgsyroDREREptKkSRMsWLCgWNu4ublpbFO9enVjd6vERo8ejddeew0AUKdOnTLuTfFkZGQgJiYGMTEx2LBhA77//nuMHDmyrLtFZeTTTz/F06dPAQAtWrQo496UT7t378aTJ08AiGDc2LFj4efnBwCwtbUtcNu4uDi0atUKd+/e1ViemJiIxMREXLt2Ddu2bUP9+vXh6+trUP8qwjmTqDzKysrC7du3cfv2bWzYsAGzZ8/Gp59+Wiptl+S8QkRE9KJiEJ2IqALq378/Gjdu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
"text/plain": [
"<Figure size 1500x800 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"\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",
"# Create figure with specific size\n",
"plt.figure(figsize=(15, 8))\n",
"plt.style.use('default')\n",
"\n",
"# 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",
"# 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",
"# 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",
"\n",
"\n",
"# 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",
"# Set x-axis formatting\n",
"ax1.set_xticks(x_positions)\n",
"ax1.set_xticklabels(stage_labels, fontsize=11)\n",
"\n",
"# Add title\n",
"plt.suptitle('Fuel Utilization Report - Institute of Science, Health and Performance',\n",
" fontsize=14, fontweight='bold', y=0.95)\n",
"\n",
"# Create legend\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",
" frameon=True, fancybox=True, shadow=True)\n",
"\n",
"# 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",
"plt.subplots_adjust(bottom=0.1, top=0.9)\n",
"plt.savefig(f'{base_dir}/graphs/fuel_utilization_chart.png', dpi=300)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "8a1878a0",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"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",
"#\n",
"line1 = sns.lineplot(data=df, x='T(sec)', y='VO2 Pulse_smoothed', label='VO2 Pulse (mL/beat)', color='blue')\n",
"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",
"ax1.grid(True, alpha=0.1)\n",
"# Set x-axis limits to remove padding\n",
"ax1.set_xlim(0, df['T(sec)'].max())\n",
"# Create second y-axis for heart rate\n",
"#\n",
"ax2 = ax1.twinx()\n",
"line2 = sns.lineplot(data=df, x='T(sec)', y='HR(bpm)_smoothed', color='red', ax=ax2, \n",
" 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(60, df['HR(bpm)_smoothed'].max() + 1)\n",
"\n",
"# Create third y-axis for speed\n",
"ax3 = ax1.twinx()\n",
"ax3.spines['right'].set_position(('outward', 60))\n",
"\n",
"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",
"\n",
"ax1.set_xticks(np.arange(0, df['T(sec)'].max(), 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(f'{base_dir}/graphs/vo2_pulse_chart.png', bbox_inches='tight', dpi=300)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "7361fb05",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"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",
"line1 = sns.lineplot(data=df, x='T(sec)', y='VO2 Breath_smoothed', label='VO2 per Breath (mL/breath)')\n",
"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",
"ax1.grid(True, alpha=0.1)\n",
"# Set x-axis limits to remove padding\n",
"ax1.set_xlim(0, df['T(sec)'].max())\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))\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",
"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(f'{base_dir}/graphs/vo2_breath_chart.png', bbox_inches='tight', dpi=300)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "c89478ff",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"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",
"df['CHO']\n",
"# Plot VT with step-like appearance\n",
"line1 = sns.lineplot(data=df, x='T(sec)', y='CHO_smoothed', label='CHO (kcal/min)')\n",
"ax1.set_xlabel('Time (sec)')\n",
"ax1.set_ylabel('CHO (g/min)')\n",
"# ax1.set_title('CHO and Speed Over Time')\n",
"ax1.grid(True, alpha=0.1)\n",
"# Set x-axis limits to remove padding\n",
"ax1.set_xlim(0, df['T(sec)'].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))\n",
"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",
"\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(f'{base_dir}/graphs/fat_metabolism_chart.png', bbox_inches='tight', dpi=300)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "1db16040",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"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",
"line1 = sns.lineplot(data=df, x='T(sec)', y='VCO2(ml/min)_smoothed', label='VCO2 (ml/min)', color='blue')\n",
"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['VCO2(ml/min)'].max())\n",
"ax1.grid(True, alpha=0.1)\n",
"# Set x-axis limits to remove padding\n",
"ax1.set_xlim(0, df['T(sec)'].max())\n",
"\n",
"# Create second y-axis for heart rate\n",
"ax2 = ax1.twinx()\n",
"line2 = sns.lineplot(data=df, x='T(sec)', y='HR(bpm)_smoothed', color='red', ax=ax2, \n",
" linewidth=2, label='Heart Rate (bpm)')\n",
"ax2.set_ylabel('Heart Rate (bpm)', color='red')\n",
"ax2.set_ylim(df['HR(bpm)_smoothed'].min() - 1, df['HR(bpm)_smoothed'].max() + 1)\n",
"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",
"line3 = sns.lineplot(data=df, x='T(sec)', y='BF(bpm)_smoothed', color='green', ax=ax3, linewidth=2, label='BF (bpm)')\n",
"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",
"ax1.set_xticks(np.arange(0, df['T(sec)'].max(), 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(f'{base_dir}/graphs/recovery_chart.png', bbox_inches='tight', dpi=300)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 49,
"id": "c8ad6076",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAIcCAYAAAAnqB3MAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAA70NJREFUeJzs3Xd4FNX6wPHvbE+ym56QhIQkBELvoEhXmogUFRD02ntDvT+v3ntt4BXLtVesV65eUUAR7ApIR0Sq9B5KSCC97GbrzO+PIQtLAgQEI/B+nmee3Z09M3Nm9uyEfTnnPYqmaRpCCCGEEEIIIYQQQvyBDPVdASGEEEIIIYQQQghx7pGglBBCCCGEEEIIIYT4w0lQSgghhBBCCCGEEEL84SQoJYQQQgghhBBCCCH+cBKUEkIIIYQQQgghhBB/OAlKCSGEEEIIIYQQQog/nASlhBBCCCGEEEIIIcQfToJSQgghhBBCCCGEEOIPJ0EpIYQQQgghhBBCCPGHk6CUEEKIU+7OO++kf//+p23/fr+fBx98kLS0NAwGA8OHDz9m+a5du/Lggw+etvqcrHHjxqEoSn1X45RatmwZFouFXbt2ndT2OTk5KIrCpEmTTnrb559//qSODVBUVERERATffvvtSe+jLq6//noyMjLqXNZut5/W+lSbNGkSiqKQk5NzWvb/R52LoiiMGzfutB/ndDtbzkMIIYQ4GglKCSHEWURRlDot8+bNA6CgoIB7772X5s2bExYWRmJiIueddx4PPfQQlZWVwf2eyA/JnTt38t577/HPf/4zuO5UBAsO95///IfnnnuOESNG8N///pf777+fDRs2MG7cuFp/TD/00EO88cYb5Ofnn5Lj/1HmzZtX58+02tq1axkxYgTp6enYbDYaNmxI//79ee2110L2nZGRwaWXXhp8XVRUxHPPPUevXr1ISEggOjqarl27MmXKlBOq88MPP8yYMWNIT08PruvTp09IXcPCwmjbti0vv/wyqqqe5NX5fb799ttaf+zHxcVx88038+ijj/6h9XG5XIwbNy743TyVjrz+FouFzMxMbr31Vvbs2XPKj3c6LVq0iEGDBtGwYUNsNhuNGjViyJAhTJ48ub6rdkKqv9ufffZZfVfljKaqKh9++CHnn38+sbGxOBwOsrOzufbaa1m6dGmw3LH+PgghhKhfpvqugBBCiFPno48+Cnn94YcfMmvWrBrrW7RoQXFxMZ07d6a8vJwbb7yR5s2bU1RUxG+//cbEiRO54447TqpHwyuvvEJmZiYXXnjh7zqXY/npp59o2LAhL730UnDdZ599xvjx4+nTp0+NHijDhg0jMjKSN998kyeeeOK01etUa9GiRY3P7h//+Ad2u52HH364RvklS5Zw4YUX0qhRI2655RaSkpLYs2cPS5cu5ZVXXuGee+456rF+/vlnHn74YS655BIeeeQRTCYTn3/+OaNHj2bDhg2MHz/+uPVdvXo1s2fPZsmSJTXeS01N5emnnwagsLCQyZMnc//991NQUMCECROC5dLT06mqqsJsNh/3eL/Ht99+yxtvvFFrYOr222/n1Vdf5aeffuKiiy46Lcd/9913QwJyLpcreI379Olzyo93+PX3er1s2LCBt956ix9++IGNGzcSHh5+yo95qk2bNo0rr7yS9u3bc++99xITE8POnTtZsGAB7777LldddVV9V/GUq6qqwmSSf64fzdixY3njjTcYNmwYV199NSaTic2bN/Pdd9/RuHFjunbtChC8h9X290EIIUT9kr9yQghxFvnLX/4S8nrp0qXMmjWrxnqA5557jt27d7N48WK6desW8l55eTkWi+WEj+/z+fj444+5/fbbT3jbE3HgwAGio6PrXN5gMDBixAg+/PBDxo8ff8YMmWvQoEGNz+6ZZ54hPj6+1s90woQJREVF8euvv9a4PgcOHDjmsVq1asXWrVtDejjdeeed9OvXj2effZYHH3yQiIiIY+7jgw8+oFGjRsEfgoeLiooKqfPtt99O8+bNee2113jiiScwGo2A3tvPZrMd8zinW4sWLWjdujWTJk06bUGp0x10O9KR1x8gMzOTu+++m8WLF5/W4banyrhx42jZsiVLly6tcX86Xvs+U9X3d6G+qaqK1+ut9Trs37+fN998k1tuuYV33nkn5L2XX36ZgoKCP6qaQgghfgcZvieEEOeo7du3YzQaaw0gREZGntSPoUWLFlFYWEi/fv1Oqk4ej4fHH3+cJk2aYLVaSUtL48EHH8Tj8QCHhgHOnTuX9evXB4cjTZo0iZEjRwJw4YUX1himCNC/f3927drF6tWrj1uP559/nm7duhEXF0dYWBidOnWqdZiNoijcfffdzJgxg9atW2O1WmnVqhXff/99rdemS5cu2Gw2srKyePvtt0/qGh3L9u3badWqVa0Bu8TExGNum5mZGRKQAv38hg8fjsfjYceOHcc9/owZM7jooovqFPSz2Wx06dKFioqKkIDC0XJKTZs2jZYtW2Kz2WjdujVffPHFMfMyvfPOO2RlZWG1WunSpQu//vpr8L3rr7+eN954I3iORw6BBL29fPXVV2iadtRzKC0txWg08uqrrwbXFRYWYjAYiIuLC9n2jjvuICkpKaQO1XXPyckhISEBIBg0rS2XUG5uLsOHD8dut5OQkMADDzxAIBA4av2Op7o+x+uJM3PmTAYPHkxKSgpWq5WsrCz+9a9/1XrsX375hUsuuYSYmBgiIiJo27Ytr7zyyjH3v3r1ahISEujTp0/IsOEjbd++nS5dutQaMD9e+wZYtWoVgwYNIjIyErvdTt++fUOGeMGhnFoLFizgtttuIy4ujsjISK699lpKSkpq7PO7776jZ8+eRERE4HA4GDx4MOvXrz9uXerqyHZQnYdu27ZtXH/99URHRxMVFcUNN9yAy+Wqsf3//vc/OnXqRFhYGLGxsYwePbrGkM2tW7dyxRVXkJSUhM1mIzU1ldGjR1NWVnbMuvXp04fWrVuzYsUKunXrRlhYGJmZmbz11ls1yh7v3n74+d599918/PHHtGrVCqvVWuv9FPSh4pqm0b1791qvW3WbON7fhxNp32+88QaNGzcmLCyM8847j4ULF9KnT58avRvrer5CCCGkp5QQQpyz0tPTCQQCfPTRR1x33XWnZJ9LlixBURQ6dOhwwtuqqsrQoUNZtGgRt956Ky1atGDt2rW89NJLbNmyhRkzZpCQkMBHH33EhAkTqKysDA5Hatq0KWPHjuXVV1/ln//8Jy1atAAIPgJ06tQJgMWLFx+3fq+88gpDhw7l6quvxuv18umnnzJy5Ei+/vprBg8eHFJ20aJFTJ8+nTvvvBOHw8Grr77KFVdcwe7du4mLiwP0PE8DBgwgISGBcePG4ff7efzxx2nQoMEJX6djSU9P5+eff2bdunW0bt36lOyzOg9XfHz8Mcvl5uaye/duOnbsWOd9Vwegjtfr7ZtvvuHKK6+kTZs2PP3005SUlHDTTTfRsGHDWstPnjyZiooKbrvtNhRF4d///jeXX345O3bswGw2c9ttt7Fv375ah7ZW69SpEy+99BLr168/6rWMjo6mdevWLFiwgLFjxwJ6e1AUheLiYjZs2ECrVq0AWLhwIT179qx1PwkJCcEhs5dddhmXX345AG3btg2WCQQCDBw4kPPPP5/nn3+e2bNn88ILL5CVlcUdd9xxzOtXvX1hYSGg92jcuHFj8EdzbT/qDzdp0iTsdjt//etfsdvt/PTTTzz22GOUl5fz3HPPBcvNmjWLSy+9lOTkZO69916SkpLYuHEjX3/9Nffee2+t+/71118ZOHAgnTt3ZubMmYSFhR21Hunp6cyZM4e9e/eSmpp63HM+3Pr16+nZsyeRkZE8+OCDmM1m3n77bfr06cP8+fM5//zzQ8rffffdREdHM27cODZv3szEiRPZtWtXMBcUELx3Dhw4kGeffRaXy8XEiRPp0aMHq1atOq3DxEaNGkVmZiZPP/00K1eu5L333iMxMZFnn302WGbChAk8+uijjBo1iptvvpmCggJee+01evXqxapVq4iOjsbr9TJw4EA8Hg/33HM
"text/plain": [
"<Figure size 1200x550 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Plot TSI (Left) and TSI2 (Right) with Black Slope (Trend) Lines per Stage\n",
"from numpy.polynomial.polynomial import Polynomial\n",
"\n",
"plt.figure(figsize=(12, 5.5))\n",
"\n",
"# Plot TSI (Left Leg)\n",
"plt.plot(\n",
" oxygenation['Time'], oxygenation['TSI'], \n",
" label='TSI (Left Leg)', color='steelblue', linewidth=2\n",
")\n",
"\n",
"# Plot TSI2 (Right Leg)\n",
"plt.plot(\n",
" oxygenation['Time'], oxygenation['TSI-second'],\n",
" label='TSI2 (Right Leg)', color='orange', linewidth=2\n",
")\n",
"\n",
"# Define time intervals for stages (adjust these based on your test protocol)\n",
"# Looking at the data range, we'll create intervals\n",
"max_time = oxygenation['Time'].max()\n",
"intervals = [\n",
" (0, 250),\n",
" (250, 500),\n",
" (500, 750),\n",
" (750, 1000),\n",
" (1000, 1250),\n",
" (1250, 1500),\n",
" (1500, max_time)\n",
"]\n",
"\n",
"# Calculate and plot trend lines for each interval\n",
"for start_time, end_time in intervals:\n",
" # Filter data for this interval\n",
" mask_interval = (oxygenation['Time'] >= start_time) & (oxygenation['Time'] <= end_time)\n",
" \n",
" # TSI (Left Leg) trend for this interval\n",
" mask_left = mask_interval & ~oxygenation['TSI'].isna()\n",
" if mask_left.sum() > 1: # Need at least 2 points for a line\n",
" x_left = oxygenation.loc[mask_left, 'Time']\n",
" y_left = oxygenation.loc[mask_left, 'TSI']\n",
" coefs_left = Polynomial.fit(x_left, y_left, 1).convert().coef\n",
" trend_left = coefs_left[0] + coefs_left[1] * x_left\n",
" plt.plot(x_left, trend_left, color='black', linestyle='--', linewidth=2, alpha=0.8)\n",
" \n",
" # TSI-second (Right Leg) trend for this interval\n",
" mask_right = mask_interval & ~oxygenation['TSI-second'].isna()\n",
" if mask_right.sum() > 1: # Need at least 2 points for a line\n",
" x_right = oxygenation.loc[mask_right, 'Time']\n",
" y_right = oxygenation.loc[mask_right, 'TSI-second']\n",
" coefs_right = Polynomial.fit(x_right, y_right, 1).convert().coef\n",
" trend_right = coefs_right[0] + coefs_right[1] * x_right\n",
" plt.plot(x_right, trend_right, color='black', linestyle='--', linewidth=2, alpha=0.8)\n",
"\n",
"plt.xlabel('Time (s)')\n",
"plt.ylabel('TSI (%)')\n",
"plt.title('TSI (Left) and TSI2 (Right) with Black Slope Lines per Stage')\n",
"plt.legend(fontsize=10, loc='upper right')\n",
"plt.tight_layout()\n",
"plt.grid(alpha=0.25)\n",
"# plt.savefig('graphs/tsi_comparison_with_trends.png', bbox_inches='tight', dpi=160)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "84addc63",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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
}