glucose360 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
glucose360/features.py CHANGED
@@ -4,11 +4,12 @@ import configparser
4
4
  from multiprocessing import Pool
5
5
  import os
6
6
  from scipy.integrate import trapezoid
7
+ from importlib import resources
8
+ from glucose360.preprocessing import load_config
7
9
 
8
- dir_path = os.path.dirname(os.path.realpath(__file__))
9
- config_path = os.path.join(dir_path, "config.ini")
10
- config = configparser.ConfigParser()
11
- config.read(config_path)
10
+ # Initialize config at module level
11
+ config = load_config()
12
+ INTERVAL = int(config["variables"]["interval"])
12
13
  ID = config['variables']['id']
13
14
  GLUCOSE = config['variables']['glucose']
14
15
  TIME = config['variables']['time']
@@ -266,6 +267,19 @@ def HBGI(df: pd.DataFrame) -> float:
266
267
  BG = np.maximum(0, BG_formula(df[GLUCOSE]))
267
268
  return np.mean(10 * (BG ** 2))
268
269
 
270
+ def BGRI(df: pd.DataFrame) -> float:
271
+ """Calculates the Blood Glucose Risk Index (BGRI).
272
+
273
+ BGRI is the overall glucose-risk score obtained by summing
274
+ the Low Blood Glucose Index (LBGI) and the High Blood Glucose
275
+ Index (HBGI).
276
+
277
+ :param df: Pandas DataFrame with pre-processed CGM data
278
+ :return: BGRI value
279
+ """
280
+ return LBGI(df) + HBGI(df)
281
+
282
+
269
283
  def COGI(df: pd.DataFrame) -> float:
270
284
  """Calculates the Continuous Glucose Monitoring Index (COGI) for the given CGM trace.
271
285
 
@@ -341,6 +355,42 @@ def GRADE_hyper(df: pd.DataFrame) -> float:
341
355
  df_GRADE = GRADE_formula(df)
342
356
  return np.sum(df_GRADE[df_GRADE[GLUCOSE] > 140]["GRADE"]) / np.sum(df_GRADE["GRADE"]) * 100
343
357
 
358
+ def GRADE_eugly_absolute(df: pd.DataFrame) -> float:
359
+ """Returns the absolute GRADE contribution from euglycemic values.
360
+
361
+ This function sums the GRADE scores for readings in the target
362
+ glucose range (70–140 mg/dL) without normalizing to the total GRADE.
363
+
364
+ :param df: a Pandas DataFrame containing preprocessed CGM data
365
+ :type df: 'pandas.DataFrame'
366
+ :return: the absolute euglycemic GRADE for the given CGM trace
367
+ :rtype: float
368
+ """
369
+ df_GRADE = GRADE_formula(df)
370
+ return np.sum(df_GRADE[(df_GRADE[GLUCOSE] >= 70) & (df_GRADE[GLUCOSE] <= 140)]["GRADE"])
371
+
372
+ def GRADE_hypo_absolute(df: pd.DataFrame) -> float:
373
+ """Returns the absolute GRADE contribution from hypoglycemic values.
374
+
375
+ :param df: a Pandas DataFrame containing preprocessed CGM data
376
+ :type df: 'pandas.DataFrame'
377
+ :return: the absolute hypoglycemic GRADE for the given CGM trace
378
+ :rtype: float
379
+ """
380
+ df_GRADE = GRADE_formula(df)
381
+ return np.sum(df_GRADE[df_GRADE[GLUCOSE] < 70]["GRADE"])
382
+
383
+ def GRADE_hyper_absolute(df: pd.DataFrame) -> float:
384
+ """Returns the absolute GRADE contribution from hyperglycemic values.
385
+
386
+ :param df: a Pandas DataFrame containing preprocessed CGM data
387
+ :type df: 'pandas.DataFrame'
388
+ :return: the absolute hyperglycemic GRADE for the given CGM trace
389
+ :rtype: float
390
+ """
391
+ df_GRADE = GRADE_formula(df)
392
+ return np.sum(df_GRADE[df_GRADE[GLUCOSE] > 140]["GRADE"])
393
+
344
394
  def GRADE(df: pd.DataFrame) -> float:
345
395
  """Calculates the Glycaemic Risk Assessment Diabetes Equation (GRADE) for the given CGM trace.
346
396
 
@@ -416,15 +466,27 @@ def hypo_index(df: pd.DataFrame, limit: int = 80, b: float = 2, d: float = 30) -
416
466
  BG = df[GLUCOSE].dropna()
417
467
  return np.sum(np.power(limit - BG[BG < limit], b)) / (BG.size * d)
418
468
 
419
- def IGC(df: pd.DataFrame) -> float:
469
+ def IGC(df: pd.DataFrame, hyper_limit: int = 140, hypo_limit: int = 80, hyper_a: float = 1.1, hyper_c: float = 30, hypo_b: float = 2, hypo_d: float = 30) -> float:
420
470
  """Calculates the Index of Glycemic Control (IGC) for the given CGM trace.
421
471
 
422
472
  :param df: a Pandas DataFrame containing preprocessed CGM data
423
473
  :type df: 'pandas.DataFrame'
474
+ :param hyper_limit: upper limit of target range (above which would hyperglycemia), defaults to 140 mg/dL
475
+ :type hyper_limit: int, optional
476
+ :param hypo_limit: lower limit of target range (above which would hypoglycemia), defaults to 80 mg/dL
477
+ :type hypo_limit: int, optional
478
+ :param hyper_a: exponent utilized for Hyperglycemia Index calculation, defaults to 1.1
479
+ :type hyper_a: float, optional
480
+ :param hyper_c: constant to help scale Hyperglycemia Index the same as other metrics (e.g. LBGI, HBGI, and GRADE), defaults to 30
481
+ :type hyper_c: float, optional
482
+ :param hypo_b: exponent utilized for Hypoglycemia Index calculation, defaults to 2
483
+ :type hypo_b: float, optional
484
+ :param hypo_d: constant to help scale Hypoglycemia Index the same as other metrics (e.g. LBGI, HBGI, and GRADE), defaults to 30
485
+ :type hypo_d: float, optional
424
486
  :return: the IGC for the given CGM trace
425
487
  :rtype: float
426
488
  """
427
- return hyper_index(df) + hypo_index(df)
489
+ return hyper_index(df, hyper_limit, hyper_a, hyper_c) + hypo_index(df, hypo_limit, hypo_b, hypo_d)
428
490
 
429
491
  def j_index(df: pd.DataFrame) -> float:
430
492
  """Calculates the J-Index for the given CGM trace.
@@ -446,9 +508,7 @@ def CONGA(df: pd.DataFrame, n: int = 24) -> float:
446
508
  :return: the CONGA for the given CGM trace
447
509
  :rtype: float
448
510
  """
449
- config.read('config.ini')
450
- interval = int(config["variables"]["interval"])
451
- period = n * (60 / interval)
511
+ period = n * (60 / INTERVAL)
452
512
  return np.std(df[GLUCOSE].diff(periods=period))
453
513
 
454
514
  # lag is in days
@@ -462,10 +522,7 @@ def MODD(df: pd.DataFrame, lag: int = 1) -> float:
462
522
  :return: the MODD for the given CGM trace
463
523
  :rtype: float
464
524
  """
465
- config.read('config.ini')
466
- interval = int(config["variables"]["interval"])
467
- period = lag * 24 * (60 / interval)
468
-
525
+ period = lag * 24 * (60 / INTERVAL)
469
526
  return np.mean(np.abs(df[GLUCOSE].diff(periods=period)))
470
527
 
471
528
  def mean_absolute_differences(df: pd.DataFrame) -> float:
@@ -504,6 +561,7 @@ def MAG(df: pd.DataFrame) -> float:
504
561
 
505
562
  def MAGE(df: pd.DataFrame, short_ma: int = 5, long_ma: int = 32, max_gap: int = 180) -> float:
506
563
  """Calculates the Mean Amplitude of Glycemic Excursions (MAGE) for the given CGM trace.
564
+ Algorithm for calculating MAGE is based on iglu's implementation (please cite their papers found in the README).
507
565
 
508
566
  :param df: a Pandas DataFrame containing preprocessed CGM data
509
567
  :type df: 'pandas.DataFrame'
@@ -518,16 +576,13 @@ def MAGE(df: pd.DataFrame, short_ma: int = 5, long_ma: int = 32, max_gap: int =
518
576
  """
519
577
  data = df.reset_index(drop=True)
520
578
 
521
- config.read('config.ini')
522
- interval = int(config["variables"]["interval"])
523
-
524
579
  missing = data[GLUCOSE].isnull()
525
580
  # create groups of consecutive missing values
526
581
  groups = missing.ne(missing.shift()).cumsum()
527
582
  # group by the created groups and count the size of each group, then apply it where values are missing
528
583
  size_of_groups = data.groupby([groups, missing])[GLUCOSE].transform('size').where(missing, 0)
529
584
  # filter groups where size is greater than 0 and take their indexes
530
- indexes = size_of_groups[size_of_groups.diff() > (max_gap / interval)].index.tolist()
585
+ indexes = size_of_groups[size_of_groups.diff() > (max_gap / INTERVAL)].index.tolist()
531
586
 
532
587
  if not indexes: # no gaps in data larger than max_gap
533
588
  return MAGE_helper(df, short_ma, long_ma)
@@ -544,7 +599,7 @@ def MAGE(df: pd.DataFrame, short_ma: int = 5, long_ma: int = 32, max_gap: int =
544
599
 
545
600
  def MAGE_helper(df: pd.DataFrame, short_ma: int = 5, long_ma: int = 32) -> float:
546
601
  """Calculates the Mean Amplitude of Glycemic Excursions (MAGE) for a specific segment of a CGM trace.
547
- Algorithm for calculating MAGE is based on iglu's implementation, and this method is a helper for the MAGE() function.
602
+ Algorithm for calculating MAGE is based on iglu's implementation (please cite their papers found in the README), and this method is a helper for the MAGE() function.
548
603
 
549
604
  :param df: a Pandas DataFrame containing preprocessed CGM data without significant gaps (as defined in the MAGE() function)
550
605
  :type df: 'pandas.DataFrame'
@@ -573,8 +628,9 @@ def MAGE_helper(df: pd.DataFrame, short_ma: int = 5, long_ma: int = 32) -> float
573
628
  averages["MA_Long"] = averages[GLUCOSE].rolling(window=long_ma, min_periods=1).mean()
574
629
 
575
630
  # fill in leading NaNs due to moving average calculation
576
- averages["MA_Short"].iloc[:short_ma-1] = averages["MA_Short"].iloc[short_ma-1]
577
- averages["MA_Long"].iloc[:long_ma-1] = averages["MA_Long"].iloc[long_ma-1]
631
+ averages.loc[:(short_ma - 2), "MA_Short"] = averages.at[short_ma - 1, "MA_Short"]
632
+ averages.loc[:(long_ma - 2), "MA_Long"] = averages.at[long_ma - 1, "MA_Long"]
633
+
578
634
  averages["DELTA_SL"] = averages["MA_Short"] - averages["MA_Long"]
579
635
 
580
636
  # get crossing points
@@ -694,12 +750,10 @@ def ROC(df: pd.DataFrame, timedelta: int = 15) -> pd.Series:
694
750
  :return: a Pandas Series with the rate of change in glucose values at every data point
695
751
  :rtype: 'pandas.Series'
696
752
  """
697
- config.read('config.ini')
698
- interval = int(config["variables"]["interval"])
699
- if timedelta < interval:
753
+ if timedelta < INTERVAL:
700
754
  raise Exception("Given timedelta must be greater than resampling interval.")
701
755
 
702
- positiondelta = round(timedelta / interval)
756
+ positiondelta = round(timedelta / INTERVAL)
703
757
  return df[GLUCOSE].diff(periods=positiondelta) / timedelta
704
758
 
705
759
  def number_readings(df: pd.DataFrame):
@@ -957,6 +1011,231 @@ def nocturnal_auc(df: pd.DataFrame) -> float:
957
1011
 
958
1012
  return np.nan if not daily_aucs else np.mean(daily_aucs)
959
1013
 
1014
+ def daily_range_helper(df: pd.DataFrame) -> float:
1015
+ """
1016
+ Mean of the daily glucose ranges (max – min) across all complete days.
1017
+ Helper function for Q-Score.
1018
+
1019
+ Returns NaN if no valid data are available.
1020
+ """
1021
+ valid = df.dropna(subset=[GLUCOSE]).copy()
1022
+ if valid.empty:
1023
+ return np.nan
1024
+
1025
+ valid['date'] = valid[TIME].dt.date
1026
+ day_ranges = valid.groupby('date')[GLUCOSE].apply(lambda x: x.max() - x.min())
1027
+ return day_ranges.mean()
1028
+
1029
+ def Q_score(df: pd.DataFrame, hyper_limit: int = 180) -> float:
1030
+ """
1031
+ Composite Q-Score.
1032
+
1033
+ Q = 8
1034
+ + (MBG − 140.4) / 30.6
1035
+ + (daily_range − 135) / 52.2
1036
+ + (t_hypo − 0.6) / 1.2
1037
+ + (t_hyper − 6.2) / 5.7
1038
+ + (MODD − 32.4) / 16.2
1039
+
1040
+ All glucose values and constants are in mg/dL; t_hypo and t_hyper are
1041
+ hours/day spent < 70 mg/dL and > 160 mg/dL, respectively.
1042
+
1043
+ :param df: a Pandas DataFrame containing preprocessed CGM data
1044
+ :type df: 'pandas.DataFrame'
1045
+ :param hyper_limit: upper limit of target range (above which would hyperglycemia), defaults to 180 mg/dL, previously 160 mg/dL
1046
+ :type hyper_limit: int, optional
1047
+ :return: the Q-Score for the given CGM trace
1048
+ :rtype: float
1049
+ """
1050
+ mbg = mean(df)
1051
+ drange = daily_range_helper(df)
1052
+ modd = MODD(df)
1053
+
1054
+ # Convert %-of-time to hours/day
1055
+ t_hypo = percent_time_below_range(df) / 100 * 24
1056
+ t_hyper = percent_time_above_range(df, limit=hyper_limit) / 100 * 24
1057
+
1058
+ # If any component is missing, return NaN
1059
+ if np.isnan([mbg, drange, modd, t_hypo, t_hyper]).any():
1060
+ return np.nan
1061
+
1062
+ return (
1063
+ 8
1064
+ + (mbg - 140.4) / 30.6
1065
+ + (drange - 135) / 52.2
1066
+ + (t_hypo - 0.6) / 1.2
1067
+ + (t_hyper - 6.2) / 5.7
1068
+ + (modd - 32.4) / 16.2
1069
+ )
1070
+
1071
+ def _n_days(df: pd.DataFrame) -> float:
1072
+ """
1073
+ Decimal number of (calendar) days covered by the dataframe.
1074
+ Any partial day counts fractionally; e.g. 36 h = 1.5 days.
1075
+ """
1076
+ t0, t1 = df[TIME].min(), df[TIME].max()
1077
+ if pd.isna(t0) or pd.isna(t1):
1078
+ return np.nan
1079
+ return (t1 - t0).total_seconds() / 86_400.0
1080
+
1081
+ def _axis_linear(val: float,
1082
+ ref: float,
1083
+ max_val: float,
1084
+ r0: float = 14.0,
1085
+ r_max: float = 76.0) -> float:
1086
+ """
1087
+ Maps ref → r0 and max_val → r_max with linear scaling.
1088
+ Values ≤ ref are clamped to r0; values ≥ max_val are clamped to r_max.
1089
+ """
1090
+ if np.isnan(val):
1091
+ return np.nan
1092
+ if val <= ref:
1093
+ return r0
1094
+ if val >= max_val:
1095
+ return r_max
1096
+ return r0 + (r_max - r0) * (val - ref) / (max_val - ref)
1097
+
1098
+
1099
+ def _auc_above_helper(df: pd.DataFrame, thr: int = 180) -> float:
1100
+ """Total AUC (mg · min dL⁻¹) **above** *thr* over the *full* time axis."""
1101
+ bg = df[[TIME, GLUCOSE]].dropna().copy()
1102
+ if bg.empty:
1103
+ return 0.0
1104
+ excess = np.maximum(bg[GLUCOSE] - thr, 0.0)
1105
+ if excess.eq(0).all():
1106
+ return 0.0
1107
+ t = (bg[TIME] - bg[TIME].iloc[0]).dt.total_seconds() / 60.0 # minutes
1108
+ return trapezoid(excess, x=t)
1109
+
1110
+
1111
+ def _auc_below_helper(df: pd.DataFrame, thr: int = 70) -> float:
1112
+ """Total AUC (mg · min dL⁻¹) **below** *thr* over the *full* time axis."""
1113
+ bg = df[[TIME, GLUCOSE]].dropna().copy()
1114
+ if bg.empty:
1115
+ return 0.0
1116
+ deficit = np.maximum(thr - bg[GLUCOSE], 0.0)
1117
+ if deficit.eq(0).all():
1118
+ return 0.0
1119
+ t = (bg[TIME] - bg[TIME].iloc[0]).dt.total_seconds() / 60.0
1120
+ return trapezoid(deficit, x=t)
1121
+
1122
+
1123
+ def _intensity_helper(time_min: float, auc: float) -> float:
1124
+ """Euclidean intensity of an excursion (minutes, mg · min dL⁻¹)."""
1125
+ return float(np.hypot(time_min, auc))
1126
+
1127
+
1128
+
1129
+ _GP_MAX = {
1130
+ "HbA1c": 14.0,
1131
+ "Mean": 250.0,
1132
+ "SD": 150.0,
1133
+ "TAbove": 1440.0,
1134
+ "AUCAbove": 432_000.0
1135
+ }
1136
+
1137
+
1138
+ def glucose_pentagon(df: pd.DataFrame,
1139
+ hbA1c: float | None = None,
1140
+ hyper_thr: int = 160) -> dict[str, float]:
1141
+
1142
+ days = _n_days(df)
1143
+ if np.isnan(days) or days == 0:
1144
+ return {"Glucose_Pentagon_Area": np.nan,
1145
+ "Glucose_Pentagon_GRP": np.nan}
1146
+
1147
+ # ----- core metrics -------------------------------------------------
1148
+ a1c = hbA1c if hbA1c is not None else eA1c(df)
1149
+ mean_glu = mean(df)
1150
+ sd_glu = SD(df)
1151
+
1152
+ # convert to *minutes per day* and *AUC per day*
1153
+ t_above = (percent_time_above_range(df, hyper_thr) / 100 * 1440)
1154
+ auc_above = _auc_above_helper(df, hyper_thr) / days
1155
+
1156
+ axes = np.array([
1157
+ _axis_linear(a1c, 5.5, _GP_MAX["HbA1c"]),
1158
+ _axis_linear(mean_glu, 90.0, _GP_MAX["Mean"]),
1159
+ _axis_linear(sd_glu, 10.0, _GP_MAX["SD"]),
1160
+ _axis_linear(t_above, 0.0, _GP_MAX["TAbove"]),
1161
+ _axis_linear(auc_above, 0.0, _GP_MAX["AUCAbove"])
1162
+ ])
1163
+
1164
+ theta = np.deg2rad(72)
1165
+ area = 0.5 * np.sum(axes * np.roll(axes, -1) * np.sin(theta))
1166
+ grp = area / 466.0
1167
+
1168
+ return {"Glucose_Pentagon_Area": area,
1169
+ "Glucose_Pentagon_GRP": grp}
1170
+
1171
+
1172
+
1173
+ _CGP_MAX = {
1174
+ "Mean": 250.0,
1175
+ "CV": 60.0,
1176
+ "ToR": 1440.0,
1177
+ "Intensity": 432_002.0
1178
+ }
1179
+
1180
+ def _axis_cgp(val: float, kind: str, r0: float = 14.0, r_max: float = 76.0) -> float:
1181
+ """
1182
+ Returns the radial length (mm) for the selected CGP axis,
1183
+ with hard clamping to the range [14, 76] to prevent
1184
+ negative or oversized radii.
1185
+ """
1186
+ if np.isnan(val):
1187
+ return np.nan
1188
+ match kind:
1189
+ case "Mean":
1190
+ r = ((val - 90.)*0.0217)**2.63 + 14.
1191
+ case "CV":
1192
+ r = (val - 17.)*0.92 + 14.
1193
+ case "ToR":
1194
+ r = (val*0.00614)**1.581 + 14.
1195
+ case "IntHyper":
1196
+ r = (val*0.000115)**1.51 + 14.
1197
+ case "IntHypo":
1198
+ r = np.exp(val*0.00057) + 13.
1199
+ case _:
1200
+ raise ValueError(kind)
1201
+
1202
+ return max(r0, min(r_max,r))
1203
+
1204
+ def comprehensive_glucose_pentagon(df: pd.DataFrame) -> dict[str, float]:
1205
+
1206
+ days = _n_days(df)
1207
+ if np.isnan(days) or days == 0:
1208
+ return {"Comprehensive_Glucose_Pentagon_Area": np.nan,
1209
+ "Comprehensive_Glucose_Pentagon_PGR": np.nan}
1210
+
1211
+ mean_glu = mean(df)
1212
+ cv_ = CV(df)
1213
+
1214
+ tir_min = percent_time_in_range(df) / 100 * 1440 * days
1215
+ tor_min = (1440*days - tir_min) / days
1216
+
1217
+ t_above = percent_time_above_range(df, 180) / 100 * 1440
1218
+ t_below = percent_time_below_range(df, 70) / 100 * 1440
1219
+ auc_above = _auc_above_helper(df, 180) / days
1220
+ auc_below = _auc_below_helper(df, 70) / days
1221
+
1222
+ int_hyper = _intensity_helper(t_above, auc_above)
1223
+ int_hypo = _intensity_helper(t_below, auc_below)
1224
+
1225
+ axes = np.array([
1226
+ _axis_cgp(mean_glu, "Mean"),
1227
+ _axis_cgp(cv_, "CV"),
1228
+ _axis_cgp(tor_min, "ToR"),
1229
+ _axis_cgp(int_hyper, "IntHyper"),
1230
+ _axis_cgp(int_hypo, "IntHypo")
1231
+ ])
1232
+
1233
+ theta = np.deg2rad(72)
1234
+ area = 0.5 * np.sum(axes * np.roll(axes, -1) * np.sin(theta))
1235
+ pgr = area / 466.0
1236
+
1237
+ return {"Comprehensive_Glucose_Pentagon_Area": area,
1238
+ "Comprehensive_Glucose_Pentagon_PGR": pgr}
960
1239
 
961
1240
  def compute_features(id: str, data: pd.DataFrame) -> dict[str, any]:
962
1241
  """Calculates statistics and metrics for a single patient within the given DataFrame
@@ -969,11 +1248,16 @@ def compute_features(id: str, data: pd.DataFrame) -> dict[str, any]:
969
1248
  :rtype: dict[str, any]
970
1249
  """
971
1250
  summary = summary_stats(data)
1251
+ gp = glucose_pentagon(data)
1252
+ cgp = comprehensive_glucose_pentagon(data)
972
1253
 
973
1254
  features = {
974
1255
  ID: id,
975
1256
  "ADRR": ADRR(data),
1257
+ "BGRI": BGRI(data),
976
1258
  "COGI": COGI(data),
1259
+ "Comprehensive Glucose Pentagon Area": cgp["Comprehensive_Glucose_Pentagon_Area"],
1260
+ "Comprehensive Glucose Pentagon PGR": cgp["Comprehensive_Glucose_Pentagon_PGR"],
977
1261
  "CONGA": CONGA(data),
978
1262
  "CV": CV(data),
979
1263
  "Daytime AUC": auc_daytime(data),
@@ -981,10 +1265,15 @@ def compute_features(id: str, data: pd.DataFrame) -> dict[str, any]:
981
1265
  "FBG": FBG(data),
982
1266
  "First Quartile": summary[1],
983
1267
  "GMI": GMI(data),
1268
+ "Glucose Pentagon Area": gp["Glucose_Pentagon_Area"],
1269
+ "Glucose Pentagon GRP": gp["Glucose_Pentagon_GRP"],
984
1270
  "GRADE": GRADE(data),
985
1271
  "GRADE (euglycemic)": GRADE_eugly(data),
1272
+ "GRADE (euglycemic absolute)": GRADE_eugly_absolute(data),
986
1273
  "GRADE (hyperglycemic)": GRADE_hyper(data),
1274
+ "GRADE (hyperglycemic absolute)": GRADE_hyper_absolute(data),
987
1275
  "GRADE (hypoglycemic)": GRADE_hypo(data),
1276
+ "GRADE (hypoglycemic absolute)": GRADE_hypo_absolute(data),
988
1277
  "GRI": GRI(data),
989
1278
  "GVP": GVP(data),
990
1279
  "HBGI": HBGI(data),
@@ -1021,6 +1310,7 @@ def compute_features(id: str, data: pd.DataFrame) -> dict[str, any]:
1021
1310
  "Percent Time in Hypoglycemia (level 2)": percent_time_in_level_2_hypoglycemia(data),
1022
1311
  "Percent Time In Range (70-180)": percent_time_in_range(data),
1023
1312
  "Percent Time In Tight Range (70-140)": percent_time_in_tight_range(data),
1313
+ "Q-Score": Q_score(data),
1024
1314
  "SD": SD(data),
1025
1315
  "Third Quartile": summary[3],
1026
1316
  }
glucose360/main.py ADDED
@@ -0,0 +1,13 @@
1
+ import pandas as pd
2
+ from glucose360.preprocessing import *
3
+ from glucose360.features import *
4
+ from glucose360.events import *
5
+ from glucose360.plots import *
6
+
7
+ def main():
8
+ df = import_data("datasets")
9
+ for index, row in create_features(df).iterrows():
10
+ print(row)
11
+
12
+ if __name__ == "__main__":
13
+ main()