Files
bio-performx/notebook.ipynb
T

1906 lines
853 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"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": 2,
"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_1280137/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": 3,
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": 4,
"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": 5,
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": 6,
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": 7,
2025-09-23 18:47:10 +01:00
"id": "7361fb05",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
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": 8,
2025-09-23 19:08:30 +01:00
"id": "c89478ff",
"metadata": {},
2025-09-23 20:36:24 +01:00
"outputs": [
{
"data": {
"image/png": "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
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": 9,
2025-09-23 19:08:30 +01:00
"id": "1db16040",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
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": 10,
"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": 10,
"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": 11,
"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\": { \"bad\": [(0, 5), (25, 50)], \"okay\": [(5, 10), (20, 25)], \"good\": [(10, 20)] },\n",
" \"40-59\": { \"bad\": [(0, 5), (30, 50)], \"okay\": [(5, 10), (20, 30)], \"good\": [(10, 20)] },\n",
" \"60-79\": { \"bad\": [(0, 5), (30, 50)], \"okay\": [(5, 10), (20, 25)], \"good\": [(10, 25)] }\n",
" },\n",
" \"female\": { \n",
" \"20-39\": { \"bad\": [(0, 15), (40, 50)], \"okay\": [(15, 20), (35, 40)], \"good\": [(20, 35)] },\n",
" \"40-59\": { \"bad\": [(0, 20), (40, 50)], \"okay\": [(20, 25), (35, 40)], \"good\": [(25, 35)] },\n",
" \"60-79\": { \"bad\": [(0, 20), (40, 50)], \"okay\": [(20, 25), (35, 40)], \"good\": [(25, 35)] }\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": 12,
"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": 13,
"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": 16,
"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>Pre</th>\n",
" <th>Best</th>\n",
" <th>LLN</th>\n",
" <th>Pred.</th>\n",
" <th>%Pred.</th>\n",
" <th>ZScore</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>FVC</td>\n",
" <td>4.24</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",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>FEV1</td>\n",
" <td>3.26</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",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>FEV1/FVC%</td>\n",
" <td>76.90</td>\n",
" <td>76.90</td>\n",
" <td>72.47</td>\n",
" <td>83.78</td>\n",
" <td>91.8</td>\n",
" <td>-1.05</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>PEF</td>\n",
" <td>444.00</td>\n",
" <td>444.00</td>\n",
" <td>222.00</td>\n",
" <td>384.00</td>\n",
" <td>178.7</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>FEF2575</td>\n",
" <td>2.74</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",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>FEF25</td>\n",
" <td>6.08</td>\n",
" <td>6.08</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.0</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>FEF50</td>\n",
" <td>3.06</td>\n",
" <td>3.06</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.0</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>FEF75</td>\n",
" <td>1.06</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",
" </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>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</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>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>FEV6</td>\n",
" <td>4.22</td>\n",
" <td>4.22</td>\n",
" <td>3.03</td>\n",
" <td>3.79</td>\n",
" <td>111.4</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Parameters Pre Best LLN Pred. %Pred. ZScore\n",
"0 FVC 4.24 4.24 3.03 3.79 112.0 0.95\n",
"1 FEV1 3.26 3.26 2.53 3.16 103.3 0.28\n",
"2 FEV1/FVC% 76.90 76.90 72.47 83.78 91.8 -1.05\n",
"3 PEF 444.00 444.00 222.00 384.00 178.7 NaN\n",
"4 FEF2575 2.74 2.74 2.15 3.42 80.2 -0.84\n",
"5 FEF25 6.08 6.08 0.00 0.00 0.0 NaN\n",
"6 FEF50 3.06 3.06 0.00 0.00 0.0 NaN\n",
"7 FEF75 1.06 1.06 0.71 1.41 75.1 -0.72\n",
"8 PEFTime 79.00 NaN NaN NaN NaN NaN\n",
"9 EVol 78.00 NaN NaN NaN NaN NaN\n",
"10 FEV6 4.22 4.22 3.03 3.79 111.4 NaN"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spirometry_data = pd.read_csv('data/spirometry_data.csv')\n",
"spirometry_data"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "d468d687",
"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('graphs/spirometry_chart.png', dpi=300, bbox_inches='tight')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "26c7b01e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Resting Metabolic Rate (RMR): 14741 kcal\n",
"Fuel Source: Fats 15.7%, Carbs 84.1%\n",
"Estimated Caloric Intake: 15080 kcal/day\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/tmp/ipykernel_1280137/3162302372.py:31: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all Axes decorations.\n",
" plt.tight_layout()\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAFrQAAADRCAYAAAAjt5ZTAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAVIRJREFUeJzs3WeYVtX5PuxrhqJ0RBEVQQSFUFTAgiiCYi8o9orGqJEk1sRfgomxRKPRGGvsJkENGrGLItgx9oINGwqKgoKIwNBnYOb9kNfn7zg4DIqO5TyPwyPPs9baa937fnbc8+myqKKioiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8CWKa7sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7bBFoDAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC2B1gAAAAAAAAAAAAAAAAAAAAAAAAAAAABUS6A1AAAAAAAAAAAAAAAAAAAAAAAAAAAAANUSaA0AAAAAAAAAAAAAAAAAAAAAAAAAAABAtQRaAwAAAAAAAAAAAAAAAAAAAAAAAAAAAFAtgdYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEugNQAAAAAAAAAAAAAAAAAAAAAAAAAAAADVEmgNAAAAAAAAAAAAAAAAAAAAAAAAAAAAQLUEWsP3zJAhQ9KpU6d06tQpzzzzTG2Xw4/AoEGDCs/c5MmTV+jet99+e2HvSy+99Fs5EwAAAAAAAAAAAAAAAAAAAAAAAAAAWH51a7sAIJk6dWr+/ve/58knn8zHH3+clVZaKS1atEiHDh3SrVu3HHPMMbVd4nfKoEGD8uyzz37p/LbbbpvLL7/8Gzm7pKQk1113XZKkdevW2WuvvWp03ZAhQ3LHHXcUvu+3334588wzK60ZPXp0jjvuuML31VZbLU888cRXqvOZZ54p9Gi77bZL586dv9I+AAAAAAAAAAAAAAAAAAAAAAAAAAAAiUBrqHXTp0/PPvvsk+nTpxfGysrKMnfu3Lz//vt57LHHBFp/h5SUlOTvf/97kmSzzTarcaD1F9177705+eST07Bhw8LY8OHDV0iNSfLss88W6mzduvX3LtD6lFNOyZw5c5Ikq6++ei1XAwAAAAAAAAAAAAAAAAAAAAAAAAAACLSGWvbvf/+7EGbdu3fvHHzwwWnYsGGmTJmSV155JQ8++GAtV/jdNnjw4Gy11VaVxlZZZZVaqqbm5s2bl5EjR2afffZJkkyZMiVPPvlkLVf13dGpU6faLgEAAAAAAAAAAAAAAAAAAAAAAAAAAPgcgdZQy1577bXC55NPPrlSkO9+++2XP/zhDzXe66mnnsq//vWvvPzyy5k3b15WXXXV9O7dO4MHD067du2SJG+99VZ23333JMmAAQNy/vnnJ0kuvPDCXHnllUmShx56KGuvvXbmzZuXTTfdNEuWLMmGG26YW265Zann3n///Tn22GOTJIMGDcopp5xSmBs7dmwOPPDAJMlOO+2Uiy++OAsXLszFF1+chx56KB9++GHq1q2bFi1apEuXLtljjz2y/fbb1/ie11lnnWyyySZLnRs/fnyuuuqqvPHGG/nkk08yb968NG3aNBtssEGOOuqobLrppoW15eXlueqqq3Lvvffm/fffT0VFRVZdddV07Ngx22+/ffbdd98MGTIkd9xxR+GaZ599tvB7bbbZZrnhhhtqVHOjRo0yb9683HrrrYVA61tvvTXl5eWFuaWpqKjI7bffnltuuSXjx4/P4sWL065du+y9994ZNGhQiouLk1QNgz755JNz8sknJ0nOOeec7LXXXrnlllsyatSoTJgwIbNmzcqSJUuy5pprZquttsqvfvWrtGjRYqk1LFy4MGeddVbuvffeLFiwIL169cof/vCHtG3bttK61157LVdddVVeeOGFzJ49O82aNcvGG2+cn//85+nWrdsyezRo0KA8++yzSf7f85gko0ePztChQzN+/PgsWrQozZo1S9u2bdOzZ8+cdNJJKSoqyjPPPJNDDz00SbLnnnumX79+ueSSS/Lhhx+ma9euOe2007L++uvn8ssvz80335ySkpJsuummOeOMM9K6detl1gYAAAAAAAAAAAAAAAAAAAAAAAAAAD9GxbVdAPzYNWrUqPD5oosuyvPPP5/S0tLCWIMGDWq0z7Bhw3L44YdnzJgxmTVrVsrKyjJ16tTccccd2WuvvfLKK68kSTp27JhmzZolSV5++eXC9S+99FKVz6+88kqWLFmSJF8aGp0kW2+9dZo2bZokeeCBB1JRUVGYGz16dOHzZ0Haf/rTn/LPf/4zkyZNSllZWRYsWJApU6bkgQceqLT+63r77bdzzz33ZMKECZk9e3YWL16cTz/9NGPGjMmhhx6ap59+urD2iiuuyEUXXZS33347ixYtSmlpaT766KOMGTMmt99++wqrKUl23XXXJMmLL76Yd955J0uWLMltt92WJNltt92+9LohQ4bk97//fV588cXMmzcvixYtyltvvZWzzz47v/nNb5arhlGjRuXxxx/PRx99lAULFqS0tDSTJk3Kv//97xxyyCFZtGjRUq/79a9/nRtuuCGffvppFixYkEcffTSHHHJIZs6cWVjz0EMPZf/998/o0aPzySefpKysLJ988klGjx6dAw44IA899NBy1fqZZ599NieccELGjh2buXPnFvYdO3Zsrr322sKz+nnPPfdcTjzxxEycODELFy7MCy+8kCOOOCKnnXZaLr300nz88cdZuHBh/vvf/+akk076SnUBAAAAAAAAAAAAAAAAAAAAAAAAAMCPgUBrqGVbbLFF4fPDDz+cgw8+OD179syBBx6Yf/7zn5k/f/4y9/joo49yzjnnpKKiIsXFxfnFL36Rq6++OjvttFOSZN68eTn55JNTUVGRoqKi9OzZM0ny/vvv59NPP82SJUsKgdfJ/4KWP/+/SfWB1vXr18+OO+6YJJk6dWqlcOz7778/SdK8efP07ds3SQqBxq1bt84ll1ySf/7zn/nzn/+cgQMHFsK2a+rkk09Op06dKv3zWQD1uuuumyFDhuSyyy7Lddddl6FDh+b0009P/fr1U15enquvvrqwz2c1NW3aNH/9618zdOjQnHvuuTnggAPSsmXLJMngwYNz8cUXF67p3Llzhg0blmHDhuWUU06pcc1dunRJly5dkiS33HJLxowZk2nTpqVu3brZc889l3rNqFGjcueddxbu64ILLsiVV16Z7t27J0lGjhyZkSNHJvlfuPlee+1VuHbw4MGFOvv165ck2WWXXXL22Wfn6quvzg033JCrr746AwcOTJJMmDCh8Lt90ccff5xzzjknF198cdq0aZMkmTZtWq666qokyfz58/OHP/whZWVlSZIDDzwwV199dQ466KAkSVlZWf7whz/U6Ln+okceeSTl5eVJ/hesPXTo0Fx44YX5xS9+kfXWWy9FRUVVrpk8eXL23HPPXH311enYsWOSZPr06Rk+fHiOPvroXHbZZVlttdWSJGPHjs3bb7+93HUBAAAAAAAAAAAAAAAAAAAAAAAAAMCPQd3aLgB+7PbZZ58899xzGTFiRGGsrKwsY8eOzdixY3PTTTfl1ltvrTboefTo0YUA4e233z4nnHBCkv+FZb/wwguZPn163nnnnbz55pvp3LlzNtlkkzzyyCNJ/hda3bp168yfPz/rr79+3n777UKQ9WfB1EVFRdl4442rvY8BAwbklltuKdTTo0ePvPLKK/nwww+TJDvttFPq1auXJIX/bdKkSdq2bZsOHTqkfv362WeffZandcvUqVOnPPfcc7nyyiszceLEzJ8/PxUVFYX5cePGFT5/VlODBg3Stm3bdOrUKQ0aNCiEPCdJu3btUrfu//vXZpMmTaoN+q7OPvvskz/96U+566678s477yRJ+vbtWwjP/qK777678Pnggw9Oq1atCvt89jvdfffd2WWXXbLJJpvkqaeeKqxfZ511qtS5xRZb5PLLL8+TTz6Zjz/+OKWlpZXmx40blwEDBlSp4ze/+U0hLLtp06Y5/PDDkyQPPvhghgwZkieeeCIzZ85MknTt2jWnn356kqRfv355+eW
"text/plain": [
"<Figure size 800x150 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 800x150 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"--- Summary ---\n",
"RMR: 14741 kcal\n",
"NEAT: 762 kcal\n",
"Deficit: 423 kcal\n",
"Caloric Intake: 15080 kcal/day\n"
]
}
],
"source": [
"# --- Extract Key Values ---\n",
"# Average RMR (EE per day)\n",
"rmr = df['EE(kcal/day)'].mean()\n",
"\n",
"# Average fuel source ratios\n",
"fat_pct = df['FAT(%)'].mean()\n",
"carb_pct = df['CARBS(%)'].mean()\n",
"\n",
"# --- Constants ---\n",
"NEAT = 762\n",
"DEFICIT = 423\n",
"\n",
"# --- Caloric Intake Calculation ---\n",
"caloric_intake = rmr + NEAT - DEFICIT\n",
"\n",
"print(f\"Resting Metabolic Rate (RMR): {rmr:.0f} kcal\")\n",
"print(f\"Fuel Source: Fats {fat_pct:.1f}%, Carbs {carb_pct:.1f}%\")\n",
"print(f\"Estimated Caloric Intake: {caloric_intake:.0f} kcal/day\")\n",
"\n",
"# --- Plot 1: Slow vs Fast Metabolism ---\n",
"fig, ax = plt.subplots(figsize=(8, 1.5))\n",
"ax.barh([0], [rmr], color='lightgreen')\n",
"ax.set_xlim(1000, 2500)\n",
"ax.set_yticks([])\n",
"ax.set_xlabel('Metabolic Rate (kcal/day)')\n",
"ax.set_title('Slow vs Fast Metabolism', fontsize=12, fontweight='bold')\n",
"ax.axvline(rmr, color='green', linewidth=4)\n",
"ax.text(rmr + 20, 0, f'{rmr:.0f} kcal', va='center', fontsize=10, fontweight='bold', color='green')\n",
"ax.text(1050, 0.1, 'Very Slow', fontsize=8)\n",
"ax.text(2450, 0.1, 'Very Fast', fontsize=8, ha='right')\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# --- Plot 2: Fuel Source Ratio ---\n",
"fig, ax = plt.subplots(figsize=(8, 1.5))\n",
"\n",
"ax.barh([0], [fat_pct], color='#f4d35e', label=f'Fats {fat_pct:.0f}%')\n",
"ax.barh([0], [carb_pct], left=[fat_pct], color='#9ad0f5', label=f'Carbs {carb_pct:.0f}%')\n",
"ax.set_xlim(0, 100)\n",
"ax.set_yticks([])\n",
"ax.set_xlabel('Fuel Source (%)')\n",
"ax.set_title('Fuel Source Ratio', fontsize=12, fontweight='bold')\n",
"\n",
"ax.axvline(70, color='black', linestyle='--', label='Optimal')\n",
"ax.legend(loc='upper center', ncol=3, bbox_to_anchor=(0.5, -0.3))\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# --- Summary Box ---\n",
"print(f\"\\n--- Summary ---\\nRMR: {rmr:.0f} kcal\\nNEAT: {NEAT} kcal\\nDeficit: {DEFICIT} kcal\\nCaloric Intake: {caloric_intake:.0f} kcal/day\")\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "55cfd2d4",
"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>13</th>\n",
" <td>2025-07-29T18:58:54.0000000Z</td>\n",
" <td>NaN</td>\n",
" <td>10000001583275_0055003f5631501320313557</td>\n",
" <td>KM6479696509</td>\n",
" <td>Keirstyn</td>\n",
" <td>Moran</td>\n",
" <td>1991-02-01T00:00:00.0000000Z</td>\n",
" <td>34</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>1 rows × 147 columns</p>\n",
"</div>"
],
"text/plain": [
" MeasurementDate Comment \\\n",
"13 2025-07-29T18:58:54.0000000Z NaN \n",
"\n",
" ExternalDeviceId ExternalPatientId FirstName \\\n",
"13 10000001583275_0055003f5631501320313557 KM6479696509 Keirstyn \n",
"\n",
" LastName BirthDate Age Ethnicity Gender ... \\\n",
"13 Moran 1991-02-01T00:00:00.0000000Z 34 Caucasian Female ... \n",
"\n",
" Child_XC Child_XC_Unit Child_BIVA_ZRh Child_BIVA_ZXcH Child_PhA \\\n",
"13 NaN NaN NaN NaN NaN \n",
"\n",
" Child_PhA_Unit Child_REE_Kcal Child_REE_MJ Child_TEE_Kcal Child_TEE_MJ \n",
"13 NaN NaN NaN NaN NaN \n",
"\n",
"[1 rows x 147 columns]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"keirstyn_data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e94d5f23",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Estimated RMR from data: 1385 kcal/day\n"
]
}
],
"source": [
"# Step 1: Filter resting phase (usually lowest VO2 or MET values)\n",
"rest_phase = df[df['MET'] <= 1.1] # assuming <1.1 MET means rest\n",
"\n",
"# Step 2: Compute resting metabolic rate\n",
"rmr = rest_phase['EE(kcal/day)'].mean()\n",
"\n",
"print(f\"Estimated RMR from data: {rmr:.0f} kcal/day\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "03fbb87e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Resting phase fuel mix: Fats 0.8%, Carbs 82.1%\n"
]
}
],
"source": [
"rest_phase = df[df['MET'] <= 1.1] # filter rest data\n",
"fat_rest = rest_phase['FAT(%)'].mean()\n",
"carb_rest = rest_phase['CARBS(%)'].mean()\n",
"\n",
"print(f\"Resting phase fuel mix: Fats {fat_rest:.1f}%, Carbs {carb_rest:.1f}%\")\n"
]
},
{
"cell_type": "code",
"execution_count": 61,
"id": "bc6610a7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Resting RER: 1.00\n",
"Estimated Fuel Mix → Fats: 0.9%, Carbs: 99.1%\n"
]
}
],
"source": [
"import numpy as np\n",
"\n",
"# 1. Filter to resting phase\n",
"rest_data = df[df['MET'] < 1.3]\n",
"\n",
"# 2. Compute mean RER\n",
"mean_rer = df['RER'].mean()\n",
"\n",
"# 3. Compute fuel mix\n",
"fat_pct = (1.0 - mean_rer) / 0.3 * 100\n",
"carb_pct = 100 - fat_pct\n",
"\n",
"print(f\"Resting RER: {mean_rer:.2f}\")\n",
"print(f\"Estimated Fuel Mix → Fats: {fat_pct:.1f}%, Carbs: {carb_pct:.1f}%\")\n"
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "3a9c9e66",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- RMR Fuel Usage Calculation Confirmation ---\n",
"1. Average RER Measured during Resting Phase: 0.997\n",
"2. Calculated Carbohydrate Usage: 99.1%\n",
"3. Calculated Fat Usage: 0.9%\n",
"\n",
"Note: The calculated value differs from 33%. This is likely due to the actual measured RER being slightly different from the exact 0.80 benchmark.\n"
]
}
],
"source": [
"def calculate_fuel_usage_from_rer(df, phase_name=\"RMR\"):\n",
" \"\"\"\n",
" Calculates the percentage of Carbohydrate and Fat oxidation using the\n",
" measured Respiratory Exchange Ratio (RER) during a specified phase.\n",
"\n",
" This function uses the standard non-protein RER calculation (the RER\n",
" is assumed to be the Non-Protein Respiratory Quotient, or non-protein RQ).\n",
" \n",
" Args:\n",
" df (pd.DataFrame): The DataFrame containing the metabolic data.\n",
" phase_name (str): The name of the phase to analyze (e.g., 'RMR').\n",
" The RMR phase is usually the initial resting period.\n",
"\n",
" Returns:\n",
" tuple: (average_rer, carbs_percent, fat_percent)\n",
" \"\"\"\n",
" \n",
" # 1. Clean the data columns\n",
" # We must ensure RER is numeric and handle potential missing values\n",
" df['RER'] = pd.to_numeric(df['RER'], errors='coerce')\n",
" df['PHASE'] = df['PHASE'].astype(str).str.strip()\n",
"\n",
" # The RMR phase is usually recorded in the 'PHASE' column.\n",
" # If the RMR phase isn't explicitly labeled, we'll try to use the very start of the test.\n",
" \n",
" # First, let's identify the resting data. PNOE often uses an empty string or 'Rest'\n",
" # for the initial RMR phase, or simply the first block of data.\n",
" resting_data = df[df['PHASE'].str.contains(phase_name, case=False, na=False)].copy()\n",
" \n",
" if resting_data.empty:\n",
" # Fallback: If no explicit phase name is found, assume the first 10 minutes (600 seconds)\n",
" # or the first 50 rows of data are the resting phase, which is standard RMR protocol.\n",
" print(f\"Warning: Phase '{phase_name}' not found. Assuming first 50 data points (approx. 5-10 min) are RMR.\")\n",
" resting_data = df.head(50).copy()\n",
" \n",
" if resting_data.empty:\n",
" print(\"Error: Could not find any valid resting data to analyze.\")\n",
" return None, None, None\n",
"\n",
" # Calculate the average RER during the identified resting period\n",
" average_rer = resting_data['RER'].mean()\n",
"\n",
" # 2. Apply the RER to Fuel Conversion Formulas\n",
" # These formulas are based on the non-protein respiratory exchange:\n",
" # RER of 0.70 = 100% Fat, 0% Carb\n",
" # RER of 1.00 = 0% Fat, 100% Carb\n",
" \n",
" # Non-Protein Fat Oxidation Formula:\n",
" # %Fat = ((1.00 - RER) / (1.00 - 0.70)) * 100\n",
" # %Fat = ((1.00 - RER) / 0.30) * 100\n",
" \n",
" # Non-Protein Carbohydrate Oxidation Formula:\n",
" # %Carb = 100 - %Fat\n",
" \n",
" if average_rer is None or pd.isna(average_rer):\n",
" print(\"Error: Average RER calculation resulted in NaN.\")\n",
" return None, None, None\n",
" \n",
" # We constrain the RER to the physiological range [0.70, 1.00] for fuel calculation\n",
" constrained_rer = max(0.70, min(1.00, average_rer))\n",
"\n",
" # Calculate the percentages\n",
" fat_percent = ((1.00 - constrained_rer) / 0.30) * 100\n",
" carbs_percent = 100.0 - fat_percent\n",
" \n",
" return average_rer, carbs_percent, fat_percent\n",
"\n",
"# --- Execution ---\n",
"# file_path = 'Pnoe_20250729_1550-Moran_Keirstyn.csv'\n",
"# df = pd.read_csv(file_path, delimiter=';')\n",
"\n",
"# Run the calculation. Since the RMR is the resting phase, we look for the\n",
"# beginning of the test or a 'Rest' phase.\n",
"avg_rer, carbs, fat = calculate_fuel_usage_from_rer(df, phase_name='')\n",
"\n",
"if avg_rer is not None:\n",
" print(f\"--- RMR Fuel Usage Calculation Confirmation ---\")\n",
" print(f\"1. Average RER Measured during Resting Phase: {avg_rer:.3f}\")\n",
" print(f\"2. Calculated Carbohydrate Usage: {carbs:.1f}%\")\n",
" print(f\"3. Calculated Fat Usage: {fat:.1f}%\")\n",
" \n",
" # This comparison confirms the 33% calculation\n",
" if 32.5 <= carbs <= 34.5:\n",
" print(\"\\n✅ This confirms the reported ~33% fuel usage based on the RER = 0.80 standard conversion.\")\n",
" else:\n",
" print(\"\\nNote: The calculated value differs from 33%. This is likely due to the actual measured RER being slightly different from the exact 0.80 benchmark.\")"
]
}
],
"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
}