{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**What does this notebook do?**\n",
"- Load the exported CGM values from NutriSense\n",
"- Retrieve sleep data from an outside Excel file for that day\n",
"- Pull in Garmin step information and \"run activities\"\n",
"- Print out what days are included in the dataset\n",
"- Loop through the data, producing one chart vor every day in the dataset\n",
" - Smooth CGM data and interpolate missing values\n",
" - Calculate key metrics for that day, both glucose and steps\n",
" - Create a chart of the glucose values, meals, activities, steps, sleep and include some metrics"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import plotly.express as px\n",
"from plotly.subplots import make_subplots\n",
"import plotly.graph_objects as go\n",
"import datetime\n",
"from datetime import date\n",
"from garminconnect import (\n",
" Garmin,\n",
" GarminConnectConnectionError,\n",
" GarminConnectTooManyRequestsError,\n",
" GarminConnectAuthenticationError,\n",
")\n",
"from openpyxl import Workbook, load_workbook\n",
"\n",
"# Read in CSV file\n",
"df = pd.read_csv('export2.csv')\n",
"\n",
"# Remove \"time zone offset\" from \"occurred_at\" column and add new \"occurred_at_day\" column\n",
"df['occurred_at_day'] = df['occurred_at'].apply(lambda x: x[:len(x) - 15])\n",
"df['occurred_at'] = df['occurred_at'].apply(lambda x: x[:len(x) - 6])\n",
"\n",
"# Read Excel file\n",
"workbook = load_workbook(filename = 'tracking.xlsx')\n",
"# Load the sheet with the data I am interested in\n",
"todaySheet = workbook['Today']\n",
"\n",
"# Get Garmin Data\n",
"# This may not be so great, defaulting to simply retrieving the last 100 activities on Garmin.\n",
"# If the day that is plotted is further in the past, this may not work.\n",
"numberOfActivities = 100\n",
"try:\n",
" # Read UserID and Password from config file\n",
" config = {}\n",
" with open(\"config.dat\") as myfile:\n",
" for line in myfile:\n",
" name, var = line.partition(\"=\")[::2]\n",
" config[name.strip()] = str(var).strip()\n",
" # Initialize Garmin client with credentials\n",
" client = Garmin(config[\"uid\"], config[\"password\"])\n",
" # Login to Garmin Connect portal\n",
" client.login()\n",
" # Get running activities\n",
" allActivities = client.get_activities(0,numberOfActivities) # 0=start, numberOfActivities=limit \n",
"except (GarminConnectConnectionError, GarminConnectAuthenticationError, GarminConnectTooManyRequestsError,) as err:\n",
" print(\"Error occured during Garmin Connect Client init: %s\" % err)\n",
" quit()\n",
"except Exception:\n",
" print(\"Unknown error occured during Garmin Connect Client init.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Print all days with data\n",
"daysWithData = df['occurred_at_day'].unique()\n",
"print(daysWithData)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"# Go through the data and create a plot for the last 5 days\n",
"for y in range(1, 5):\n",
" df1 = df[df['occurred_at_day']==daysWithData[y]]\n",
" day = daysWithData[y]\n",
"\n",
" # Create a datasets just with glucose measurments\n",
" gm = df1[df1['class']=='GlucoseMeasurement']\n",
"\n",
" # Create a dataset for meals and exercise, sort it\n",
" mealsExercise = df1[((df1['class']=='Meal') | (df1['class']=='ExerciseActivity') )]\n",
" mealsExerciseSorted = mealsExercise.sort_values(by=[\"occurred_at\"], ascending=True)\n",
"\n",
" # Get steps of the day from Garmin\n",
" dayOfInterest = datetime.datetime.strptime(day, '%Y-%m-%d').date()\n",
" allDayStepData = client.get_steps_data(dayOfInterest.isoformat())\n",
"\n",
" # convert Garmin data in list form to Pandas dataframe\n",
" dfGarmin = pd.DataFrame.from_dict(allDayStepData)\n",
"\n",
" # manipulate start time so that it is local (And not GMT)\n",
" dfGarmin['time'] = dfGarmin['startGMT'].apply(lambda x: x[:len(x) - 5])\n",
" dfGarmin['time'] = dfGarmin['time'].apply(lambda x: x[11:])\n",
" offset = dfGarmin[\"time\"][0]\n",
" offsetHour = int(offset.split(':')[0])\n",
" offsetMinutes=int(offset.split(':')[1])\n",
" dfGarmin['time'] = pd.to_datetime(dfGarmin['startGMT'])\n",
" dfGarmin['time'] = dfGarmin['time'].apply(lambda x: x - datetime.timedelta(hours=offsetHour, minutes=offsetMinutes))\n",
" dfGarmin = dfGarmin[dfGarmin.steps != 0]\n",
"\n",
" # Create a dataset with just 2 columns, and rename columns\n",
" gm_data = gm.filter(['occurred_at', 'value'])\n",
" gm_data.columns = ['time', 'value']\n",
"\n",
" # turn time column into the index and delete time column\n",
" gm_data['time']= pd.to_datetime(gm_data['time'])\n",
" gm_data.index = gm_data['time']\n",
" del gm_data['time']\n",
"\n",
" # Add rows and interpolate missing data\n",
" gm_data = gm_data.resample('1T').mean()\n",
" gm_data = gm_data.interpolate(method='cubic')\n",
"\n",
" # Calculate a few metrics\n",
" threshold = 120 # this is an arbitrary threshold\n",
" above = gm_data[gm_data['value'] > threshold] # create a dataset with glucose measuremnts over threshold\n",
" minutesAboveThreshold = above.count()\n",
" percentageAboveThreshold = int(round(minutesAboveThreshold/(60*24)*100,0))\n",
" averageGlucose = int(round(gm_data['value'].mean()))\n",
" medianGlucose = int(round(gm_data['value'].median()))\n",
"\n",
" # Calculate statistics on the Garmin data\n",
" numberOfRunningActivitiesToday = 0\n",
" numberOfActivitiesToday = 0\n",
" for i in range(numberOfActivities):\n",
" activity = allActivities[i]\n",
" activityDateTime = activity['startTimeLocal']\n",
" activityDate = datetime.datetime.strptime(activityDateTime, \"%Y-%m-%d %H:%M:%S\")\n",
" if str(activityDate.date()) == day:\n",
" numberOfActivitiesToday = numberOfActivitiesToday + 1\n",
" if activity[\"activityType\"][\"typeKey\"] == \"running\":\n",
" numberOfRunningActivitiesToday = numberOfRunningActivitiesToday + 1\n",
"\n",
" # Retrieve sleep data\n",
" for rowInExcel in range(3,55):\n",
" cell = todaySheet[str(\"A\"+str(rowInExcel))]\n",
" # Skip over \"empty\" cells\n",
" if cell.value is None: continue\n",
" # Assume the cell contains a date value, thus convert it\n",
" cellDate = cell.value.date()\n",
" if str(cellDate) == str(day):\n",
" sleepEnd = todaySheet[str(\"E\"+str(rowInExcel))].value\n",
" sleepBegin = todaySheet[str(\"F\"+str(rowInExcel))].value\n",
" sleepBegin = datetime.datetime.combine(date.min, datetime.datetime.strptime('23:59', '%H:%M').time()) - datetime.datetime.combine(date.min, sleepBegin)\n",
" break\n",
"\n",
" # using subplots here to easily get a secondary y-axis\n",
" fig = make_subplots(specs=[[{\"secondary_y\": True}]])\n",
" # first add the glucose measurement data\n",
" fig.add_trace( go.Scatter(x=gm_data.index, y=gm_data.value, mode='lines',line=dict(color=\"purple\")))\n",
"\n",
" # add meals and exercise to the chart\n",
" yText = 145\n",
" eventColor = \"green\"\n",
" for index, row in mealsExerciseSorted.iterrows():\n",
"\n",
" # If the activity has \"run\" in the description, don't use it as it is a duplicate from Garmin\n",
" if \"run\" in str(row['description']).lower(): continue\n",
"\n",
" # Convert the time in pandas to something that we can use as an index for the x-axis placement\n",
" time = datetime.datetime.strptime(row['occurred_at'], '%Y-%m-%d %H:%M:%S')\n",
"\n",
" # Pick a different color depending on the event\n",
" if (row['class'] == \"Meal\"): eventColor = \"black\"\n",
" else: eventColor = \"green\"\n",
"\n",
" # Alternate text placement so adjacent text doesn't overlap\n",
" if (yText >= 175): yText = 145\n",
" else: yText = yText + 8\n",
"\n",
" # draw a vertical line at the time of the meal/exercise\n",
" gmAtThatTime = gm_data.loc[str(time.replace(second=0))].value\n",
" fig.add_shape(type=\"line\", xref=\"x\", yref=\"y\", x0=time, y0=gmAtThatTime, x1=time , y1=yText-2, line_color=eventColor,)\n",
" \n",
" # Add text\n",
" fig.add_annotation(text=row['description'], xref=\"x\", yref=\"y\", x=time, y=yText, showarrow=False, font=dict(color=eventColor))\n",
"\n",
" # Add Garmin running activities\n",
" for i in range(numberOfActivities):\n",
" activity = allActivities[i]\n",
" # only activities that are of type \"running\"\n",
" if activity[\"activityType\"][\"typeKey\"] == \"running\":\n",
" activityDateTime = activity['startTimeLocal']\n",
" activityDate = datetime.datetime.strptime(activityDateTime, \"%Y-%m-%d %H:%M:%S\")\n",
" if str(activityDate.date()) == day:\n",
" # draw a vertical line at the time of the running activity\n",
" gmAtThatTime = gm_data.loc[str(activityDate.replace(second=0))].value\n",
" fig.add_shape(type=\"line\", xref=\"x\", yref=\"y\", x0=activityDateTime, y0=gmAtThatTime, x1=activityDateTime , y1=133, line_color=\"green\",)\n",
" # Add text... yes this is specific to kilometers. This may need changes for miles.\n",
" textDescr = str(activity['activityName']) + \" \" + str(int(round(activity['distance']/1000))) + \"K run\"\n",
" fig.add_annotation(text=textDescr, xref=\"x\", yref=\"y\", x=activityDateTime, y=135, showarrow=False, font=dict(color=\"green\"))\n",
"\n",
" # Draw a line at the threshold\n",
" fig.add_shape(type=\"line\", xref=\"x\", yref=\"y\",\n",
" x0=gm_data.index[0], y0=threshold, x1=gm_data.index.max(), y1=threshold, line_color=\"red\",)\n",
"\n",
" # Show text box with summary values\n",
" fig.add_annotation(\n",
" text='Glucose Threshold = '+str(threshold)+\n",
" '
Minutes above Threshold = '+str(int(round(minutesAboveThreshold,0)))+\n",
" '
Time above Threshold = '+str(percentageAboveThreshold)+\"%\"+\n",
" '
Average Glucose = '+str(averageGlucose)+\n",
" '
Median Glucose = '+str(medianGlucose)+\n",
" '
Steps Today = '+str(dfGarmin.steps.sum()),\n",
" align='right', showarrow=False,\n",
" xref='paper', yref='paper', x=0.001, y=0.005,\n",
" bordercolor='black', borderwidth=1\n",
" )\n",
"\n",
" # Setting primary and secondary y axis titles and ticks\n",
" fig.update_layout(yaxis = dict(range=[0, 180], tick0=0, dtick=20, title_text='mg/dL'),yaxis2=dict(tick0=0, dtick=500, range=[0,4500], title_text='Steps'))\n",
" # Adding step data to the chart, using the secondary y axis\n",
" fig.add_trace( go.Bar(x=dfGarmin.time, y=dfGarmin.steps), secondary_y=True)\n",
"\n",
" # Set x axis title\n",
" fig.update_xaxes(title_text=str(dayOfInterest.strftime('%A'))+ \", \" +str(day), tickformat='%H:%M')\n",
" # Hide the legend\n",
" fig.update_layout(showlegend=False)\n",
"\n",
" # Draw morning sleep\n",
" fig.add_shape(type=\"rect\", xref=\"x\", yref=\"y\",\n",
" x0=gm_data.index[0], y0=57, x1=datetime.datetime.strptime(str(day) + \" \" + str(sleepEnd), '%Y-%m-%d %H:%M:%S'), y1=150,\n",
" line=dict(color=\"RoyalBlue\"),fillcolor=\"LightSkyBlue\",opacity=0.5,line_width=0,)\n",
" # Draw evening sleep\n",
" fig.add_shape(type=\"rect\", xref=\"x\", yref=\"y\",\n",
" x0=gm_data.index.max(), y0=57, x1=datetime.datetime.strptime(str(day) + \" \" + str(sleepBegin), '%Y-%m-%d %H:%M:%S'), y1=150,\n",
" line=dict(color=\"RoyalBlue\"),fillcolor=\"LightSkyBlue\",opacity=0.5,line_width=0,)\n",
"\n",
" # Size the chart\n",
" fig.update_layout(autosize=False, width=1400, height=400,margin=dict(l=20, r=20, t=40, b=20),)\n",
" fig.show()"
]
}
],
"metadata": {
"interpreter": {
"hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}