Cgm
CGM experiment 2023-01-01¶
For 2 weeks, I've used Freestyle Libre 2 to continuously monitor my blood clucose and had some extremely interesting learnings.
import pandas as pd
import numpy as np
from plotnine import *
from datetime import datetime, timedelta
from mizani.formatters import date_format
DATE_FORMAT = "%Y-%m-%d"
df = pd.read_csv("./LuvsandondovLkhamsuren_glucose_1-19-2023.csv")
df = df.rename(columns={"Historic Glucose mg/dL": "glucose"})
df["ts"] = pd.to_datetime(df["Device Timestamp"])
df["day"] = pd.to_datetime(df["ts"].dt.date)
df["time"] = df["ts"].dt.time
a1c_df = pd.DataFrame.from_records([
{"date": "2019-12-05", "value": 5.4},
{"date": "2022-05-21", "value": 5.9},
{"date": "2022-08-29", "value": 5.7},
{"date": "2022-11-30", "value": 5.8},
])
?geom_area
(
a1c_df
.pipe(ggplot)
+aes(x = "date", y = "value", group = 1)
+geom_line()
+geom_hline(yintercept = 5.7, linetype = "dashed")
+labs(
title = "A1C level",
)
+scale_x_date(breaks = "1 year")
)
<ggplot: (356467466)>
def plot_glucose(_df: pd.DataFrame, start: str):
end_date = datetime.strptime(start, DATE_FORMAT) + timedelta(days=1) + timedelta(hours=6)
segment_df = _df.query(f"'{start}' <= ts & ts <= '{end_date}'")
label_df = (
pd.melt(
segment_df.query("~food.isnull() | ~drink.isnull() | ~exercise.isnull()", engine = "python"),
id_vars=["ts", "day", "time", "glucose"],
value_vars=["food", "drink", "exercise"],
)
.query("~value.isnull()", engine = "python")
)
avg_df = (
segment_df
.query(f"ts <= '{end_date.strftime(DATE_FORMAT)}'")
.groupby("Device")
.mean()
.reset_index()
.assign(x = lambda _: np.max(segment_df["ts"]))
.assign(y = lambda _: np.max(segment_df["glucose"]))
)
return (
segment_df
.pipe(ggplot)
+aes(x = "ts", y = "glucose")
+geom_line()
+geom_point(data = label_df, fill='none')
+geom_label(
data = label_df,
mapping = aes(label = "value", color = "variable"),
size=8,
adjust_text={
'arrowprops': {'arrowstyle': '->'}
}
)
+geom_text(data = avg_df, mapping = aes(x = "x", y = "y", label = "np.round(glucose, 1)"))
+geom_hline(yintercept = 130, linetype = "dotted")
+labs(
title = f"2d glucose for {start}",
x = "Time"
)
+theme(legend_position="none")
+scale_x_timedelta(
breaks=lambda x: pd.to_timedelta(
pd.Series(range(0, 10)),
unit='h'
)
)
)
plot_glucose(df, "2023-01-06")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356634308)>
Notes:
- Bread with peanut butter does have a small spike, but nothing to be concerned
- Light dinners with low carb (such as omellete, salmon) results in nice drop in glucose overnight
plot_glucose(df, "2023-01-07")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356687598)>
Notes:
- Ideally I can combine my food, otherwise latte by itself after lunch spiked me to 150
- Carb heavy food (such as bread, rice) is the only time I have gone over 170 (100 -> 170)
- Heavy spike for dinner disrupted my sleep because my glucose drops sharply & have to rebound during sleep period
plot_glucose(df, "2023-01-08")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356741533)>
Notes:
- Heavy workout sometime can spike BG, but have nice effect of reducing spikes for the rest of the day
- Good dinner is highly suggestive of good sleep & low BG
plot_glucose(df, "2023-01-09")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356744343)>
Notes
- Starbucks coffee early in the day spiked my BG and it never came down for the rest of the day
plot_glucose(df, "2023-01-10")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356845915)>
Notes:
- Brown rice was only OK after OTF
plot_glucose(df, "2023-01-11")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356905897)>
- Latte by itself spiked BG every single time
- morning brew veggie scramble with rice went from 110 -> 160 and stayed there for a long time
- Salad with good protein (fish, avocado) has minimal impact
plot_glucose(df, "2023-01-12")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356963940)>
- Brown rice bowl before workout had effect of 105 -> 155
plot_glucose(df, "2023-01-13")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356965563)>
- Veggie chili after workout had no spike in BG
plot_glucose(df, "2023-01-14")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (357041140)>
plot_glucose(df, "2023-01-15")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (357136887)>
- Olay was not that bad if we avoid rice, most likely the spike is due to the sweet salad dressing
plot_glucose(df, "2023-01-16")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356961392)>
- Omellete, salmon brocolli, salad are the best foods so far
plot_glucose(df, "2023-01-17")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (356633122)>
plot_glucose(df, "2023-01-18")
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/2396277746.py:17: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
<ggplot: (357000626)>
Did I have higher GB on days when I didn't workout?¶
daily_df = (
df
.assign(had_otf = lambda r: r["exercise"] == "OTF")
.groupby("day")
.mean()
.reset_index()
.assign(had_otf = lambda r: r["had_otf"] > 0)
)
(
daily_df
.pipe(ggplot)
+aes(x = "day", y = "glucose")
+geom_line()
+geom_smooth()
+scale_x_date(breaks = "3 day")
)
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/3912590285.py:2: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function. /usr/local/lib/python3.8/site-packages/plotnine/stats/smoothers.py:321: PlotnineWarning: Confidence intervals are not yet implemented for lowess smoothings.
<ggplot: (357190223)>
from scipy.stats import ttest_ind
cat1 = daily_df[(daily_df['had_otf'])]
cat2 = daily_df[(~daily_df['had_otf'])]
ttest_ind(cat1['glucose'], cat2['glucose'])
Ttest_indResult(statistic=-2.8595327029299726, pvalue=0.013409623308817542)
df.mean()
/var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/3698961737.py:1: FutureWarning: DataFrame.mean and DataFrame.median with numeric_only=None will include datetime64 and datetime64tz columns in a future version. /var/folders/t9/20c3hj1d4h1fps2l73ybqgyr0000gn/T/ipykernel_91402/3698961737.py:1: FutureWarning: The default value of numeric_only in DataFrame.mean is deprecated. In a future version, it will default to False. In addition, specifying 'numeric_only=None' is deprecated. Select only valid columns or specify the value of numeric_only to silence this warning.
Record Type 0.000000 glucose 108.953077 dtype: float64