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/__init__.py CHANGED
@@ -1 +1,6 @@
1
+ """
2
+ Glucose360: A Python package that provides Continuous Glucose Monitoring (CGM) data preprocessing, feature extraction, event detection, and plotting utilities.
3
+ """
4
+
5
+ __version__ = "0.0.1"
1
6
  __all__ = ["preprocessing", "features", "events", "plots"]
glucose360/config.ini ADDED
@@ -0,0 +1,10 @@
1
+ [variables]
2
+ id = ID
3
+ glucose = Glucose
4
+ time = Timestamp
5
+ before = Before
6
+ after = After
7
+ type = Type
8
+ description = Description
9
+ interval = 5
10
+
@@ -0,0 +1,47 @@
1
+ {
2
+ "gold": "#ffd700",
3
+ "red": "#ff0000",
4
+ "limegreen": "#32cd32",
5
+ "hotpink": "#ff69b4",
6
+ "dodgerblue": "#1e90ff",
7
+ "darkolivegreen": "#556b2f",
8
+ "sienna": "#a0522d",
9
+ "seagreen": "#2e8b57",
10
+ "maroon2": "#7f0000",
11
+ "darkgreen": "#006400",
12
+ "slategray": "#708090",
13
+ "olive": "#808000",
14
+ "rosybrown": "#bc8f8f",
15
+ "peru": "#cd853f",
16
+ "yellowgreen": "#9acd32",
17
+ "lightseagreen": "#20b2aa",
18
+ "indianred": "#cd5c5c",
19
+ "goldenrod": "#daa520",
20
+ "darkseagreen": "#8fbc8f",
21
+ "purple": "#800080",
22
+ "darkorchid": "#9932cc",
23
+ "darkorange": "#ff8c00",
24
+ "slateblue": "#6a5acd",
25
+ "lime": "#00ff00",
26
+ "mediumspringgreen": "#00fa9a",
27
+ "darksalmon": "#e9967a",
28
+ "crimson": "#dc143c",
29
+ "aqua": "#00ffff",
30
+ "deepskyblue": "#00bfff",
31
+ "purple3": "#a020f0",
32
+ "greenyellow": "#adff2f",
33
+ "tomato": "#ff6347",
34
+ "thistle": "#d8bfd8",
35
+ "fuchsia": "#ff00ff",
36
+ "palevioletred": "#db7093",
37
+ "khaki": "#f0e68c",
38
+ "laserlemon": "#ffff54",
39
+ "plum": "#dda0dd",
40
+ "lightgreen": "#90ee90",
41
+ "deeppink": "#ff1493",
42
+ "paleturquoise": "#afeeee",
43
+ "violet": "#ee82ee",
44
+ "aquamarine": "#7fffd4",
45
+ "peachpuff": "#ffdab9",
46
+ "darkslategray": "#2f4f4f"
47
+ }
glucose360/events.py CHANGED
@@ -4,11 +4,12 @@ from scipy.integrate import trapezoid
4
4
  import configparser
5
5
  import glob, os, zipfile, tempfile
6
6
  import math
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']
@@ -23,8 +24,8 @@ def import_events(
23
24
  name: str = None,
24
25
  day_col: str = "Day",
25
26
  time_col: str = "Time",
26
- before: int = 60,
27
- after: int = 60,
27
+ before: int = 30,
28
+ after: int = 180,
28
29
  type: str = "imported event"
29
30
  ) -> pd.DataFrame:
30
31
  """Bulk imports events from standalone .csv files or from those within a given directory or .zip file
@@ -37,9 +38,9 @@ def import_events(
37
38
  :type day_col: str, optional
38
39
  :param time_col: the name of the column specifying what time during the day the event occurred, defaults to 'Time'
39
40
  :type time_col: str, optional
40
- :param before: the amount of minutes to also look at before the event timestamp, defaults to 60
41
+ :param before: the amount of minutes to also look at before the event timestamp, defaults to 30
41
42
  :type before: int, optional
42
- :param after: the amount of minutes to also look at after the event timestamp, defaults to 60
43
+ :param after: the amount of minutes to also look at after the event timestamp, defaults to 180
43
44
  :type after: int, optional
44
45
  :param type: the type of event to classify all the imported events as, defaults to 'imported event'
45
46
  :type type: str, optional
@@ -79,8 +80,8 @@ def import_events_directory(
79
80
  id: str,
80
81
  day_col: str = "Day",
81
82
  time_col: str = "Time",
82
- before: int = 60,
83
- after: int = 60,
83
+ before: int = 30,
84
+ after: int = 180,
84
85
  type: str = "imported event"
85
86
  ) -> pd.DataFrame:
86
87
  """Bulk imports events from .csv files within a given directory
@@ -93,9 +94,9 @@ def import_events_directory(
93
94
  :type day_col: str, optional
94
95
  :param time_col: the name of the column specifying what time during the day the event occurred, defaults to 'Time'
95
96
  :type time_col: str, optional
96
- :param before: the amount of minutes to also look at before the event timestamp, defaults to 60
97
+ :param before: the amount of minutes to also look at before the event timestamp, defaults to 30
97
98
  :type before: int, optional
98
- :param after: the amount of minutes to also look at after the event timestamp, defaults to 60
99
+ :param after: the amount of minutes to also look at after the event timestamp, defaults to 180
99
100
  :type after: int, optional
100
101
  :param type: the type of event to classify all the imported events as, defaults to 'imported event'
101
102
  :type type: str, optional
@@ -114,8 +115,8 @@ def import_events_csv(
114
115
  id: str,
115
116
  day_col: str = "Day",
116
117
  time_col: str = "Time",
117
- before: int = 60,
118
- after: int = 60,
118
+ before: int = 30,
119
+ after: int = 180,
119
120
  type: str = "imported event"
120
121
  ) -> pd.DataFrame:
121
122
  """Bulk imports events from a single .csv file
@@ -128,9 +129,9 @@ def import_events_csv(
128
129
  :type day_col: str, optional
129
130
  :param time_col: the name of the column specifying what time during the day the event occurred, defaults to 'Time'
130
131
  :type time_col: str, optional
131
- :param before: the amount of minutes to also look at before the event timestamp, defaults to 60
132
+ :param before: the amount of minutes to also look at before the event timestamp, defaults to 30
132
133
  :type before: int, optional
133
- :param after: the amount of minutes to also look at after the event timestamp, defaults to 60
134
+ :param after: the amount of minutes to also look at after the event timestamp, defaults to 180
134
135
  :type after: int, optional
135
136
  :param type: the type of event to classify all the imported events as, defaults to 'imported event'
136
137
  :type type: str, optional
@@ -263,10 +264,11 @@ def get_episodes(
263
264
  _episodes_helper(data, id, "hypo", hypo_lvl1, 1, min_length, end_length),
264
265
  _episodes_helper(data, id, "hypo", hypo_lvl2, 2, min_length, end_length)])
265
266
 
266
- episodes.sort_values(by=[TIME], inplace=True)
267
+ if not episodes.empty:
268
+ episodes = episodes.sort_values(by=[TIME])
267
269
  output = pd.concat([output, episodes])
268
270
 
269
- return output
271
+ return output
270
272
 
271
273
  def get_excursions(
272
274
  df: pd.DataFrame,
@@ -388,7 +390,7 @@ def retrieve_event_data(
388
390
  final = datetime + pd.Timedelta(row[AFTER], "m")
389
391
 
390
392
  patient_data = df.loc[id]
391
- data = patient_data[(patient_data[TIME] >= initial) & (patient_data[TIME] <= final)].copy()
393
+ data = patient_data[(patient_data[TIME] >= initial) & (patient_data[TIME] <= final)].copy().reset_index(drop=True)
392
394
 
393
395
  data[ID] = id
394
396
  data[DESCRIPTION] = row[DESCRIPTION]
@@ -562,7 +564,7 @@ def event_metrics(
562
564
  final = datetime + pd.Timedelta(event[AFTER], "m")
563
565
 
564
566
  patient_data = df.loc[id]
565
- data = patient_data[(patient_data[TIME] >= initial) & (patient_data[TIME] <= final)].copy()
567
+ data = patient_data[(patient_data[TIME] >= initial) & (patient_data[TIME] <= final)].copy().reset_index(drop=True)
566
568
 
567
569
  metrics = pd.Series()
568
570
  metrics["Baseline"] = baseline(data)
@@ -610,8 +612,8 @@ def create_event_features(
610
612
  event_features = {}
611
613
  for id in df.index.unique():
612
614
  sub_features = {}
613
- for type, sub_events in events[events[ID] == id].groupby(TYPE):
614
- sub_features.update(create_event_features_helper(df.loc[id], sub_events, type))
615
+ for event_type, sub_events in events[events[ID] == id].groupby(TYPE):
616
+ sub_features.update(create_event_features_helper(df.loc[id], sub_events, event_type))
615
617
  event_features[id] = sub_features
616
618
 
617
619
  return pd.DataFrame(event_features).T
@@ -619,7 +621,7 @@ def create_event_features(
619
621
  def create_event_features_helper(
620
622
  df: pd.DataFrame,
621
623
  sub_events: pd.DataFrame,
622
- type: str,
624
+ event_type: str,
623
625
  ) -> dict[str, float]:
624
626
  """Calculates aggregate event-based metrics for a single patient and type of event. Helper method for 'create_event_features()'.
625
627
 
@@ -627,32 +629,32 @@ def create_event_features_helper(
627
629
  :type df: 'pandas.DataFrame'
628
630
  :param sub_events: Pandas DataFrame containing events of only one type solely for the patient whose CGM trace is also given
629
631
  :type sub_events: 'pandas.DataFrame'
630
- :param type: the type of event that 'sub_events' contains
631
- :type type: str
632
+ :param event_type: the type of event that 'sub_events' contains
633
+ :type event_type: str
632
634
  :return: a dictionary with str-type keys that refer to the name of the calculated features and float-type values
633
635
  :rtype: dict[str, float]
634
636
  """
635
637
 
636
638
  features = {
637
- f"Mean {type} Duration": [],
638
- f"Mean Glucose During {type}s": [],
639
- f"Mean Upwards Slope of {type}s (mg/dL per min)": [],
640
- f"Mean Downwards Slope of {type}s (mg/dL per min)": [],
641
- f"Mean Minimum Glucose of {type}s": [],
642
- f"Mean Maximum Glucose of {type}s": [],
643
- f"Mean Amplitude of {type}s": [],
644
- f"Mean iAUC of {type}s": []
639
+ f"Mean {event_type} Duration": [],
640
+ f"Mean Glucose During {event_type}s": [],
641
+ f"Mean Upwards Slope of {event_type}s (mg/dL per min)": [],
642
+ f"Mean Downwards Slope of {event_type}s (mg/dL per min)": [],
643
+ f"Mean Minimum Glucose of {event_type}s": [],
644
+ f"Mean Maximum Glucose of {event_type}s": [],
645
+ f"Mean Amplitude of {event_type}s": [],
646
+ f"Mean iAUC of {event_type}s": []
645
647
  }
646
648
 
647
649
  for _, event in sub_events.iterrows():
648
650
  event_data = retrieve_event_data(df, event)
649
651
 
650
- duration = event[AFTER] - event[BEFORE]
651
- features[f"Mean {type} Duration"].append(duration)
652
+ duration = event[AFTER] + event[BEFORE]
653
+ features[f"Mean {event_type} Duration"].append(duration)
652
654
 
653
- features[f"Mean Glucose During {type}s"].append(event_data[GLUCOSE].mean())
654
- features[f"Mean Minimum Glucose of {type}s"] = nadir(event_data)
655
- features[f"Mean Maximum Glucose of {type}s"] = peak(event_data)
655
+ features[f"Mean Glucose During {event_type}s"].append(event_data[GLUCOSE].mean())
656
+ features[f"Mean Minimum Glucose of {event_type}s"].append(nadir(event_data))
657
+ features[f"Mean Maximum Glucose of {event_type}s"].append(peak(event_data))
656
658
 
657
659
  event_time = event[TIME]
658
660
  closest_idx = (event_data[TIME] - event_time).abs().idxmin()
@@ -661,20 +663,20 @@ def create_event_features_helper(
661
663
  peak_glucose = peak(event_data)
662
664
  peak_time = event_data.loc[event_data[GLUCOSE].idxmax(), TIME]
663
665
  amplitude = peak_glucose - event_glucose
664
- features[f"Mean Amplitude of {type}s"].append(abs(amplitude))
666
+ features[f"Mean Amplitude of {event_type}s"].append(abs(amplitude))
665
667
 
666
668
  time_diff_to_peak = (peak_time - event_time).total_seconds() / 60.0
667
669
  slope_to_peak = (peak_glucose - event_glucose) / time_diff_to_peak if time_diff_to_peak != 0 else np.nan
668
- features[f"Mean Upwards Slope of {type}s (mg/dL per min)"].append(slope_to_peak)
670
+ features[f"Mean Upwards Slope of {event_type}s (mg/dL per min)"].append(slope_to_peak)
669
671
 
670
672
  end_time = event_data[TIME].iloc[-1]
671
673
  end_glucose = event_data[GLUCOSE].iloc[-1]
672
674
  time_diff_peak_to_end = (end_time - peak_time).total_seconds() / 60.0
673
675
  slope_peak_to_end = (end_glucose - peak_glucose) / time_diff_peak_to_end if time_diff_peak_to_end != 0 else np.nan
674
- features[f"Mean Downwards Slope of {type}s (mg/dL per min)"].append(slope_peak_to_end)
676
+ features[f"Mean Downwards Slope of {event_type}s (mg/dL per min)"].append(slope_peak_to_end)
675
677
 
676
- features[f"Mean iAUC of {type}s"].append(iAUC(event_data, event_glucose))
678
+ features[f"Mean iAUC of {event_type}s"].append(iAUC(event_data, event_glucose))
677
679
 
678
680
  features = {k: np.mean(v) for k, v in features.items()}
679
- features[f"Mean # of {type}s per day"] = sub_events.shape[0] / len(df[TIME].dt.date.unique())
681
+ features[f"Mean # of {event_type}s per day"] = sub_events.shape[0] / len(df[TIME].dt.date.unique())
680
682
  return features