794 lines
224 KiB
Plaintext
794 lines
224 KiB
Plaintext
|
|
{
|
|||
|
|
"cells": [
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 16,
|
|||
|
|
"id": "462ce5a7",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [],
|
|||
|
|
"source": [
|
|||
|
|
"import pandas as pd\n",
|
|||
|
|
"import seaborn as sns\n",
|
|||
|
|
"import matplotlib.pyplot as plt\n",
|
|||
|
|
"import numpy as np\n",
|
|||
|
|
"\n",
|
|||
|
|
"import matplotlib.transforms as mtransforms\n",
|
|||
|
|
"from matplotlib.patches import FancyBboxPatch"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 19,
|
|||
|
|
"id": "da5ac3c1",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [],
|
|||
|
|
"source": [
|
|||
|
|
"pnoe_df = pd.read_csv('data/pnoe_data.csv', delimiter=';')\n",
|
|||
|
|
"patients_info = pd.read_excel('data/patients_data.xlsx')\n",
|
|||
|
|
"spirometry_df = pd.read_csv('data/spirometry_data.csv')"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": null,
|
|||
|
|
"id": "4eb44d7c",
|
|||
|
|
"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>T(sec)</th>\n",
|
|||
|
|
" <th>PHASE</th>\n",
|
|||
|
|
" <th>HR(bpm)</th>\n",
|
|||
|
|
" <th>VO2(ml/min)</th>\n",
|
|||
|
|
" <th>VCO2(ml/min)</th>\n",
|
|||
|
|
" <th>RER</th>\n",
|
|||
|
|
" <th>VE(l/min)</th>\n",
|
|||
|
|
" <th>FEO2</th>\n",
|
|||
|
|
" <th>FECO2</th>\n",
|
|||
|
|
" <th>FETO2</th>\n",
|
|||
|
|
" <th>...</th>\n",
|
|||
|
|
" <th>EE(kcal/min)</th>\n",
|
|||
|
|
" <th>CARBS(kcal)</th>\n",
|
|||
|
|
" <th>CARBS(%)</th>\n",
|
|||
|
|
" <th>FAT(kcal)</th>\n",
|
|||
|
|
" <th>FAT(%)</th>\n",
|
|||
|
|
" <th>MET</th>\n",
|
|||
|
|
" <th>CUMULATIVE EE(kcal)</th>\n",
|
|||
|
|
" <th>BP(kPa)</th>\n",
|
|||
|
|
" <th>Watts</th>\n",
|
|||
|
|
" <th>Speed</th>\n",
|
|||
|
|
" </tr>\n",
|
|||
|
|
" </thead>\n",
|
|||
|
|
" <tbody>\n",
|
|||
|
|
" <tr>\n",
|
|||
|
|
" <th>0</th>\n",
|
|||
|
|
" <td>3.0</td>\n",
|
|||
|
|
" <td>NaN</td>\n",
|
|||
|
|
" <td>64.0</td>\n",
|
|||
|
|
" <td>225.40</td>\n",
|
|||
|
|
" <td>204.22</td>\n",
|
|||
|
|
" <td>0.91</td>\n",
|
|||
|
|
" <td>8.29</td>\n",
|
|||
|
|
" <td>0.1751</td>\n",
|
|||
|
|
" <td>0.0352</td>\n",
|
|||
|
|
" <td>0</td>\n",
|
|||
|
|
" <td>...</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>1.19</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>99.46</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" </tr>\n",
|
|||
|
|
" <tr>\n",
|
|||
|
|
" <th>1</th>\n",
|
|||
|
|
" <td>8.0</td>\n",
|
|||
|
|
" <td>NaN</td>\n",
|
|||
|
|
" <td>62.0</td>\n",
|
|||
|
|
" <td>383.07</td>\n",
|
|||
|
|
" <td>382.56</td>\n",
|
|||
|
|
" <td>1.00</td>\n",
|
|||
|
|
" <td>14.36</td>\n",
|
|||
|
|
" <td>0.1769</td>\n",
|
|||
|
|
" <td>0.0350</td>\n",
|
|||
|
|
" <td>0</td>\n",
|
|||
|
|
" <td>...</td>\n",
|
|||
|
|
" <td>1.91</td>\n",
|
|||
|
|
" <td>0.16</td>\n",
|
|||
|
|
" <td>99.55</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>0.45</td>\n",
|
|||
|
|
" <td>2.03</td>\n",
|
|||
|
|
" <td>0.16</td>\n",
|
|||
|
|
" <td>99.46</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" </tr>\n",
|
|||
|
|
" <tr>\n",
|
|||
|
|
" <th>2</th>\n",
|
|||
|
|
" <td>12.0</td>\n",
|
|||
|
|
" <td>NaN</td>\n",
|
|||
|
|
" <td>62.0</td>\n",
|
|||
|
|
" <td>310.11</td>\n",
|
|||
|
|
" <td>298.57</td>\n",
|
|||
|
|
" <td>0.96</td>\n",
|
|||
|
|
" <td>11.56</td>\n",
|
|||
|
|
" <td>0.1765</td>\n",
|
|||
|
|
" <td>0.0349</td>\n",
|
|||
|
|
" <td>0</td>\n",
|
|||
|
|
" <td>...</td>\n",
|
|||
|
|
" <td>1.54</td>\n",
|
|||
|
|
" <td>0.09</td>\n",
|
|||
|
|
" <td>87.60</td>\n",
|
|||
|
|
" <td>0.01</td>\n",
|
|||
|
|
" <td>12.40</td>\n",
|
|||
|
|
" <td>1.64</td>\n",
|
|||
|
|
" <td>0.26</td>\n",
|
|||
|
|
" <td>99.46</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" </tr>\n",
|
|||
|
|
" <tr>\n",
|
|||
|
|
" <th>3</th>\n",
|
|||
|
|
" <td>14.0</td>\n",
|
|||
|
|
" <td>NaN</td>\n",
|
|||
|
|
" <td>61.0</td>\n",
|
|||
|
|
" <td>296.31</td>\n",
|
|||
|
|
" <td>293.30</td>\n",
|
|||
|
|
" <td>0.99</td>\n",
|
|||
|
|
" <td>11.12</td>\n",
|
|||
|
|
" <td>0.1778</td>\n",
|
|||
|
|
" <td>0.0351</td>\n",
|
|||
|
|
" <td>0</td>\n",
|
|||
|
|
" <td>...</td>\n",
|
|||
|
|
" <td>1.48</td>\n",
|
|||
|
|
" <td>0.05</td>\n",
|
|||
|
|
" <td>96.61</td>\n",
|
|||
|
|
" <td>0.00</td>\n",
|
|||
|
|
" <td>3.39</td>\n",
|
|||
|
|
" <td>1.57</td>\n",
|
|||
|
|
" <td>0.31</td>\n",
|
|||
|
|
" <td>99.46</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" </tr>\n",
|
|||
|
|
" <tr>\n",
|
|||
|
|
" <th>4</th>\n",
|
|||
|
|
" <td>17.0</td>\n",
|
|||
|
|
" <td>NaN</td>\n",
|
|||
|
|
" <td>60.0</td>\n",
|
|||
|
|
" <td>189.86</td>\n",
|
|||
|
|
" <td>175.37</td>\n",
|
|||
|
|
" <td>0.92</td>\n",
|
|||
|
|
" <td>7.32</td>\n",
|
|||
|
|
" <td>0.1772</td>\n",
|
|||
|
|
" <td>0.0328</td>\n",
|
|||
|
|
" <td>0</td>\n",
|
|||
|
|
" <td>...</td>\n",
|
|||
|
|
" <td>0.93</td>\n",
|
|||
|
|
" <td>0.03</td>\n",
|
|||
|
|
" <td>74.56</td>\n",
|
|||
|
|
" <td>0.01</td>\n",
|
|||
|
|
" <td>25.44</td>\n",
|
|||
|
|
" <td>1.00</td>\n",
|
|||
|
|
" <td>0.36</td>\n",
|
|||
|
|
" <td>99.46</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" <td>0.0</td>\n",
|
|||
|
|
" </tr>\n",
|
|||
|
|
" </tbody>\n",
|
|||
|
|
"</table>\n",
|
|||
|
|
"<p>5 rows × 28 columns</p>\n",
|
|||
|
|
"</div>"
|
|||
|
|
],
|
|||
|
|
"text/plain": [
|
|||
|
|
" T(sec) PHASE HR(bpm) VO2(ml/min) VCO2(ml/min) RER VE(l/min) FEO2 \\\n",
|
|||
|
|
"0 3.0 NaN 64.0 225.40 204.22 0.91 8.29 0.1751 \n",
|
|||
|
|
"1 8.0 NaN 62.0 383.07 382.56 1.00 14.36 0.1769 \n",
|
|||
|
|
"2 12.0 NaN 62.0 310.11 298.57 0.96 11.56 0.1765 \n",
|
|||
|
|
"3 14.0 NaN 61.0 296.31 293.30 0.99 11.12 0.1778 \n",
|
|||
|
|
"4 17.0 NaN 60.0 189.86 175.37 0.92 7.32 0.1772 \n",
|
|||
|
|
"\n",
|
|||
|
|
" FECO2 FETO2 ... EE(kcal/min) CARBS(kcal) CARBS(%) FAT(kcal) FAT(%) \\\n",
|
|||
|
|
"0 0.0352 0 ... 0.00 0.00 0.00 0.00 0.00 \n",
|
|||
|
|
"1 0.0350 0 ... 1.91 0.16 99.55 0.00 0.45 \n",
|
|||
|
|
"2 0.0349 0 ... 1.54 0.09 87.60 0.01 12.40 \n",
|
|||
|
|
"3 0.0351 0 ... 1.48 0.05 96.61 0.00 3.39 \n",
|
|||
|
|
"4 0.0328 0 ... 0.93 0.03 74.56 0.01 25.44 \n",
|
|||
|
|
"\n",
|
|||
|
|
" MET CUMULATIVE EE(kcal) BP(kPa) Watts Speed \n",
|
|||
|
|
"0 1.19 0.00 99.46 0.0 0.0 \n",
|
|||
|
|
"1 2.03 0.16 99.46 0.0 0.0 \n",
|
|||
|
|
"2 1.64 0.26 99.46 0.0 0.0 \n",
|
|||
|
|
"3 1.57 0.31 99.46 0.0 0.0 \n",
|
|||
|
|
"4 1.00 0.36 99.46 0.0 0.0 \n",
|
|||
|
|
"\n",
|
|||
|
|
"[5 rows x 28 columns]"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"execution_count": 20,
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "execute_result"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"df = pnoe_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()"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 11,
|
|||
|
|
"id": "2fa8ff13",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"data": {
|
|||
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAAJ8CAYAAAB5mtehAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAhuhJREFUeJzt3Xd8W9X9//HXlbxjJ7bjOHuTRUIWIQOyIMxAyg5Q9miBAt9vobSli9LJ7LdlFEqhwK9QZgl7BEiAEAjZIXtvJ3GceG9b9/7+OLZix05i2bKvxvv5eAgkWZI/jmXpvnXO5xzLcRwHERERERGRIPK4XYCIiIiIiEQeBQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BQ0REREREQk6BY0ItGnTJoYMGcKgQYP44Q9/6HY5QXX11VczaNAgBg0axD333OO/fvfu3f7rBw0axMKFC1vl+//ud7/zf4958+a1yvcQERERiQQxgdx44cKFXHPNNce83YUXXsgDDzzQ7KKuvvpqFi1a1OzHOu2008jKyvJfjo2N5fPPP6dTp071blddXc20adPYt29fves3bNjQzMpDw//93/9h2zYAN910U72vzZs3j4ULF7J8+XL27dvHwYMHsSyLLl26cPLJJ3P99dfTs2fPeve55557eOutt5r0vefMmUOPHj0Cqnft2rW8+OKLLF68mP379xMTE0PHjh0ZNmwYF198MRMnTgzo8VrT9ddfz2uvvYbP5+Ovf/0rkyZNwrIst8sSERERCTkBBY1wVVVVxauvvsodd9xR7/pPPvmkQcgId6tXr2bu3LkADB48mLFjx9b7+m233UZlZWWD+23bto1t27Yxa9Ysnn76acaNG9es7x/oQfcTTzzBE088geM4/usqKiooKSlh586dJCUlhVTQ6NWrF1OmTGHu3LmsXbuWTz/9lDPPPNPtskRERERCTouCxvTp0xk2bFiD6wcMGNCSh20Vr732GjfffDNxcXH+61588UUXK2odr732mv/8ueee2+htPB4PY8aMYfTo0Xg8Hr744gvWrl0LQFlZGffccw9z5szB4zEz66ZPn37E3+nTTz9NQUEBYH7v3bp1a3KtL7/8Mo8//rj/8qhRoxg1ahQdOnSgoKCALVu2kJaW1uTHayvnnnuuP8y99tprChoiIiIijWhR0Jg0aRIXXXTREb9eXV3NE088wZo1a9i2bRv5+fmUlZWRnJxM//79Oeecc7j88suJjY0F4PHHH+eJJ56o9xhvvfVWvWk7gU7N8Xg82LZNTk4OH330Eeeffz4Aa9asYdmyZQB4vV58Pl+j91+3bh2vvfYaa9asYd++fRQUFOA4DhkZGYwYMYKrrrqKMWPGNPi5X3rpJT766CO2bNlCaWkpKSkpZGRkMHToUKZMmVIvBGzYsIFnnnmGZcuWsX//fjweD+np6fTq1cv/PTp37nzMn7W8vJwPPvjAf7mxA+ALL7yQH/zgB/WmR91xxx1cd911/r6GPXv2sGnTJgYNGgTA5MmTmTx5coPHWrZsmT9kANxwww1NHtEoLi7mL3/5i//y7373Oy6//PIm3bepPvzwQ5599lm2bNlCUlISU6dO5Sc/+QkZGRn1bjdnzhxefvll1q1bR0FBAfHx8aSnpzNw4EBGjBjBD37wA3/oAjj11FOJjY2lqqqKb775hr1799K1a9eg1i4iIiIS7lp16lRFRQVPPfVUg+vz8/NZunQpS5cuZe7cuTz77LN4vd5WqWH8+PGsWLGC0tJSXnrpJX/Q+Pe//+2/zamnnspnn33W6P2XLl3KK6+80uD6PXv2sGfPHj7++GP+/Oc/1wtcv/71rxv0NOTn55Ofn8/mzZvZvn27P2hs3ryZyy67jLKysnq337t3L3v37mXhwoWcdNJJTQoaK1asoKSkBID09HT69OnT4Da///3vG1zn8Xg488wz6zVQV1VVHfP7/etf//Kf79y5M+edd94x71Nr9uzZFBcXA9ClSxeys7OZMWMGO3fuJCEhgRNPPJGbb76ZESNGNPkx63ruuef44osv/JfLy8uZNWsWixcv5vXXXyc9PR2AWbNm8Ytf/KLefaurqykpKWHXrl3MmTOH6667jvj4eP/X27Vrx8CBA1mzZg22bbNgwYKjBm4RERGRaNSioPHVV1+Rl5fX4Prp06fTtWtXLMuiZ8+ejBgxgs6dO9OhQweqqqrYtm0bH3/8MdXV1XzzzTfMnj2b6dOnc8opp5CUlMQrr7zCrl27ABg2bBjTp0/3P3ZqampANaakpHDhhRfyn//8h5UrV7JixQp69uzJhx9+CMDYsWMZPHjwEYNGXFwcI0eOZPDgwaSmptKuXTuKiopYsGABq1atwnEcHnzwQaZPn05CQgIlJSW8++67/vufddZZHH/88RQVFbFnzx4WL15c7/Hfeustf8jo0qUL3/ve90hMTGTfvn1s2rSJ7777rsk/65IlS/znhw4d2uT7AWzdutV/vl27dvTr1++ot9+2bZt/+hDANddcU29a2rEsX77cf37fvn08+eST/svl5eXMmTOHL7/8kocffrje77+pvvjiC8aNG8eYMWNYtmwZCxYsAGDXrl08/PDD3H///QD1QuQJJ5zA1KlT8fl87Nu3j++++44tW7Y0+vgnnHACa9asAcy/u4KGiIiISH0tChoffvih/4C9rmHDhtG1a1eSkpL47LPPOHjwICtWrCA7O5vy8nKOP/54Nm7cyMaNGwGYP38+06dPZ/To0YwePZovvvjCHzQGDBjAjTfe2JIyueqqq3j55ZdxHIcXX3yRvn37+huir7766qOuMjVz5kxmzpzJ+vXr2bhxI/n5+Xi9XqZNm8aqVasAM1qxevVqxowZQ3V1tX8aVnJyMo888ki9A3DHcdi9e7f/ckVFhf/8lVde2WA52rpTk46l9t8MCGgqz5IlS3j99df9l2+44QaSkpKOep/nn3/ev7JVcnJywNOecnJy6l2Oi4tj5syZxMfH8/rrr1NUVER1dTW//vWvmTBhQsC9GhMnTuTZZ5/Fsiwcx+Gmm25i/vz5ALz33nvce++9JCYm1vv3//Wvf83IkSPrPc7u3bv9U/vq6tKli/983X93ERERETFadepUeXk5v/vd73j77bf9B6WNyc7Obs0y6NevH5MmTWLevHnMnj2blJQUALp37860adOOGjTWrFnDz3/+czZt2nTU71G7elWHDh0YMGAAmzZtori4mGnTpnHCCSfQu3dvBg0axIQJE+r1R4wZM8bflP63v/2NuXPn0rdvX/r27cuIESMYM2ZMk6eV5ebm+s936NChSfeZM2cOd999t3+q1LnnnsuPfvSjo97n4MGDvP322/7LM2fOJDk5uUnfr9bhU7N+9rOfcfXVVwPm3+TWW28FoKSkhLlz53LxxRcH9PgzZszw94tYlsWMGTP8QaOqqoqNGzf6/31rf//XX389o0aNonfv3hx33HGMGTPG36dyuLoja3X/3UVERETEaFHQuP/++486ZeQvf/kLs2bNOubjNLbcarBdffXVzJs3j6qqKv+B4fe///2jHsSXl5dz8803N/j0vTF1f4ZHHnmEn/zkJ2zevJn9+/czZ84c/9c8Hg/XXHONvy/g7LPP5oYbbuCll16isrKS5cuX15tW1L17d55++ulWWcnrhRde4MEHH/SHwIsvvpg//OEP9RqfG/PSSy/5RwJiY2O57rrrAv7etWGvVt1leA9fknfnzp0BP37Hjh2PermwsBCAu+66i127djFv3jxKS0v5+uuv+frrr+vV8vTTTzcY4am7HK+IiIiINNSqO4N/9NFH/vMDBw7k/fffZ82aNWzYsIGzzz67Nb91A5MmTaJv377+y4mJiVx66aVHvc/ixYvrhYwbbriBBQsWsGHDBlasWHHE+w0ePJgPPviAd999lwceeIBbbrnFv2qTbdu88MILfPvtt/7b//z
|
|||
|
|
"text/plain": [
|
|||
|
|
"<Figure size 800x800 with 1 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"def body_composition_chart(first_name='Keirstyn', last_name='Moran'):\n",
|
|||
|
|
"\n",
|
|||
|
|
" \n",
|
|||
|
|
" #=========================== Body Composition Donut Chart ========================#\n",
|
|||
|
|
" patient_data = patients_info[(patients_info['FirstName'].str.contains(first_name, case=False, na=False)) & \n",
|
|||
|
|
" (patients_info['LastName'].str.contains(last_name, case=False, na=False))]\n",
|
|||
|
|
"# Get the fat mass percentage for Keirstyn\n",
|
|||
|
|
" fat_percentage = patient_data['Adult_FMP'].iloc[0]\n",
|
|||
|
|
" weight_kg = patient_data['Weight'].iloc[0]\n",
|
|||
|
|
" lean_percentage = 100 - fat_percentage\n",
|
|||
|
|
"\n",
|
|||
|
|
"# Create donut chart\n",
|
|||
|
|
" fat_mass_lbs = weight_kg * (fat_percentage / 100) * 2.20462\n",
|
|||
|
|
" lean_mass_lbs = weight_kg * (lean_percentage / 100) * 2.20462\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, f'Fat Mass ({fat_mass_lbs:.1f}lbs)\\n{fat_percentage:.1f}%', \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, f'Lean Mass ({lean_mass_lbs:.1f}lbs)\\n{lean_percentage:.1f}%', \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('graphs/body_composition_chart.png', bbox_inches='tight', dpi=600)\n",
|
|||
|
|
" plt.show()\n",
|
|||
|
|
"\n",
|
|||
|
|
"# Set a common style\n",
|
|||
|
|
" sns.set_theme(style=\"whitegrid\")\n",
|
|||
|
|
"\n",
|
|||
|
|
"\n",
|
|||
|
|
"body_composition_chart()"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 15,
|
|||
|
|
"id": "22b1c806",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"data": {
|
|||
|
|
"image/png": "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
|
|||
|
|
"text/plain": [
|
|||
|
|
"<Figure size 1000x200 with 1 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"def body_fat_percentage_chart(gender='female', age=25, fat_percentage=22.4):\n",
|
|||
|
|
" \"\"\"\n",
|
|||
|
|
" Creates a body fat percentage bar chart based on gender and age group.\n",
|
|||
|
|
" \n",
|
|||
|
|
" Parameters:\n",
|
|||
|
|
" - gender: 'male' or 'female'\n",
|
|||
|
|
" - age: age of the person\n",
|
|||
|
|
" - fat_percentage: body fat percentage value\n",
|
|||
|
|
" \"\"\"\n",
|
|||
|
|
" \n",
|
|||
|
|
" # Determine age group\n",
|
|||
|
|
" if age >= 20 and age <= 39:\n",
|
|||
|
|
" age_group = \"20-39\"\n",
|
|||
|
|
" elif age >= 40 and age <= 59:\n",
|
|||
|
|
" age_group = \"40-59\"\n",
|
|||
|
|
" elif age >= 60 and age <= 79:\n",
|
|||
|
|
" age_group = \"60-79\"\n",
|
|||
|
|
" else:\n",
|
|||
|
|
" print(f\"Age {age} is outside the supported range (20-79)\")\n",
|
|||
|
|
" return\n",
|
|||
|
|
" \n",
|
|||
|
|
" # Normalize gender input\n",
|
|||
|
|
" gender = gender.lower()\n",
|
|||
|
|
" if gender not in ['male', 'female']:\n",
|
|||
|
|
" print(\"Gender must be 'male' or 'female'\")\n",
|
|||
|
|
" return\n",
|
|||
|
|
" \n",
|
|||
|
|
" # Define segments based on gender and age group\n",
|
|||
|
|
" if gender == 'female':\n",
|
|||
|
|
" if age_group == \"20-39\":\n",
|
|||
|
|
" segments = [\n",
|
|||
|
|
" ('#F8A8A8', 0, 15), # Bad: 0-15%\n",
|
|||
|
|
" ('#FFEECC', 15, 5), # Okay: 15-20%\n",
|
|||
|
|
" ('#D0F0C0', 20, 15), # Good: 20-35%\n",
|
|||
|
|
" ('#FFEECC', 35, 5), # Okay: 35-40%\n",
|
|||
|
|
" ('#F8A8A8', 40, 10) # Bad: 40-50%\n",
|
|||
|
|
" ]\n",
|
|||
|
|
" else: # 40-59 and 60-79 have same ranges for females\n",
|
|||
|
|
" segments = [\n",
|
|||
|
|
" ('#F8A8A8', 0, 20), # Bad: 0-20%\n",
|
|||
|
|
" ('#FFEECC', 20, 5), # Okay: 20-25%\n",
|
|||
|
|
" ('#D0F0C0', 25, 10), # Good: 25-35%\n",
|
|||
|
|
" ('#FFEECC', 35, 5), # Okay: 35-40%\n",
|
|||
|
|
" ('#F8A8A8', 40, 10) # Bad: 40-50%\n",
|
|||
|
|
" ]\n",
|
|||
|
|
" else: # male\n",
|
|||
|
|
" if age_group == \"20-39\":\n",
|
|||
|
|
" segments = [\n",
|
|||
|
|
" ('#F8A8A8', 0, 5), # Bad: 0-5%\n",
|
|||
|
|
" ('#FFEECC', 5, 5), # Okay: 5-10%\n",
|
|||
|
|
" ('#D0F0C0', 10, 10), # Good: 10-20%\n",
|
|||
|
|
" ('#FFEECC', 20, 5), # Okay: 20-25%\n",
|
|||
|
|
" ('#F8A8A8', 25, 25) # Bad: 25-50%\n",
|
|||
|
|
" ]\n",
|
|||
|
|
" elif age_group == \"40-59\":\n",
|
|||
|
|
" segments = [\n",
|
|||
|
|
" ('#F8A8A8', 0, 5), # Bad: 0-5%\n",
|
|||
|
|
" ('#FFEECC', 5, 5), # Okay: 5-10%\n",
|
|||
|
|
" ('#D0F0C0', 10, 10), # Good: 10-20%\n",
|
|||
|
|
" ('#FFEECC', 20, 10), # Okay: 20-30%\n",
|
|||
|
|
" ('#F8A8A8', 30, 20) # Bad: 30-50%\n",
|
|||
|
|
" ]\n",
|
|||
|
|
" else: # 60-79\n",
|
|||
|
|
" segments = [\n",
|
|||
|
|
" ('#F8A8A8', 0, 5), # Bad: 0-5%\n",
|
|||
|
|
" ('#FFEECC', 5, 5), # Okay: 5-10%\n",
|
|||
|
|
" ('#D0F0C0', 10, 15), # Good: 10-25%\n",
|
|||
|
|
" ('#FFEECC', 25, 5), # Okay: 25-30%\n",
|
|||
|
|
" ('#F8A8A8', 30, 20) # Bad: 30-50%\n",
|
|||
|
|
" ]\n",
|
|||
|
|
" \n",
|
|||
|
|
" # Create demographic label\n",
|
|||
|
|
" gender_abbrev = 'M' if gender == 'male' else 'F'\n",
|
|||
|
|
" demographic = f\"{age_group}\\n({gender_abbrev})\"\n",
|
|||
|
|
" \n",
|
|||
|
|
" # Create the chart\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(fat_percentage, 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",
|
|||
|
|
" ticks = range(0, 51, 5)\n",
|
|||
|
|
" ax.set_xticks(ticks)\n",
|
|||
|
|
" labels = [f\"{t}%\" for t in ticks]\n",
|
|||
|
|
" ax.set_xticklabels(labels)\n",
|
|||
|
|
" ax.set_yticks([])\n",
|
|||
|
|
" ax.text(-0.05, 0, demographic, transform=ax.get_yaxis_transform(), va='center', ha='right', fontsize=12)\n",
|
|||
|
|
" \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()\n",
|
|||
|
|
"\n",
|
|||
|
|
"# Example usage\n",
|
|||
|
|
"body_fat_percentage_chart(gender='male', age=65, fat_percentage=22.4)"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": 17,
|
|||
|
|
"id": "ee52abc9",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [
|
|||
|
|
{
|
|||
|
|
"data": {
|
|||
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA/YAAAFdCAYAAACpXPZQAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAeOJJREFUeJzt3Xd8FVX+//HXbek9oYQiRREioEDoSBXBglKUFSuIAvYCouJa1i4KyIqg4FpAQEREmgorXzoivUSX4tKkQ0i9qTc38/sjv8wSCJB+U97PxyMPJjNn5nzuyb0hnzlnzrEYhmEgIiIiIiIiIhWS1dMBiIiIiIiIiEjRKbEXERERERERqcCU2IuIiIiIiIhUYErsRURERERERCowJfYiIiIiIiIiFZgSexEREREREZEKTIm9iIiIiIiISAWmxF5ERERERESkAlNiLyIiIiIiIlKBKbEXERERERERqcCU2IuIiIiIiIhUYErsRURERERERCowJfYiIiIiIiIiFZgSexEREQ/56quvuP/++83vW7Zsyd69e8uk7uXLl9OjR48yqUtERERKlxJ7Eaky7r//fr766qsC7889FhUVxZ49e8x9SUlJNG7cmKNHj5ZSpFKe3H///TRr1oyWLVvStm1b7r//fn7//fdSqWv79u00btz4suUmTZrEY489VioxiIiISMWjxF5E5DKCgoKYMGGCp8MQD3ruuefYvn07a9euJSoqKt+k2uVyeSAyEREREbB7OgARkfLunnvu4euvv2bz5s20adPG0+GIB3l7e3PnnXcyffp0HnnkEcLCwkhJSWHt2rU8++yzDBo0iClTprB48WKSk5Np2bIlr7/+OjVq1ADgzz//5O9//zt//vknzZo1o3nz5nmu37hxYxYsWEBUVBQAS5YsYdq0aRw9epTg4GCefPJJgoKCmDp1KtnZ2bRs2RLI6ek3DIOvv/6a2bNnExsbS1RUFP/4xz+48sorATh58iQvvfQSO3bsoH79+vTq1asMW67sxcXF4XQ6i3x+YGAgoaGhJRiRiIhI6VFiLyJyGcHBwQwbNozx48czZ84cT4cjHpSWlsZ3331H7dq1CQkJ4ccff+Tjjz/mww8/JCMjgw8//JA//viD2bNnExISwocffsjIkSOZNWsWWVlZPProo9x6663MnDmTP/74gxEjRlx06P2KFSt48803mThx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
|
|||
|
|
"text/plain": [
|
|||
|
|
"<Figure size 1150x360 with 4 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"def spirometry_chart():\n",
|
|||
|
|
"# Ensure data is loaded\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",
|
|||
|
|
" 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()\n",
|
|||
|
|
"\n",
|
|||
|
|
"spirometry_chart()"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": null,
|
|||
|
|
"id": "605f15de",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [],
|
|||
|
|
"source": [
|
|||
|
|
"def respiratory_chart():\n",
|
|||
|
|
" df = pnoe_df.copy()\n",
|
|||
|
|
" 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",
|
|||
|
|
"\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.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": 21,
|
|||
|
|
"id": "3220bb8d",
|
|||
|
|
"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": [
|
|||
|
|
"<Figure size 1500x800 with 2 Axes>"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"metadata": {},
|
|||
|
|
"output_type": "display_data"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"source": [
|
|||
|
|
"def fuel_utilization_chart():\n",
|
|||
|
|
" \n",
|
|||
|
|
"# Group by speed and calculate mean for numeric columns only\n",
|
|||
|
|
" speed_groups = pnoe_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('graphs/fuel_utilization_chart.png', dpi=300)\n",
|
|||
|
|
" plt.show()\n",
|
|||
|
|
"\n",
|
|||
|
|
"fuel_utilization_chart()\n"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"cell_type": "code",
|
|||
|
|
"execution_count": null,
|
|||
|
|
"id": "f44f6da7",
|
|||
|
|
"metadata": {},
|
|||
|
|
"outputs": [],
|
|||
|
|
"source": []
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"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
|
|||
|
|
}
|