Skip to content

Commit 94614ef

Browse files
authored
Merge pull request #2 from staskh/fix_warning
Fix runtime warning and episode_calculation support lv1/lv2 exclusion
2 parents bae4ba6 + be37050 commit 94614ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+256
-76
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Unless noted, iglu-r test is considered successful if it achieves precision of 0
3333
| cv_glu ||
3434
| cv_measures ||
3535
| ea1c ||
36-
| episode_calculation | 🟡 need fix in excl| || no match in lv1_hypo_excl and lv1_hyper_excl|
36+
| episode_calculation | | || no match in lv1_hypo_excl and lv1_hyper_excl|
3737
| gmi ||
3838
| grade_eugly ||
3939
| grade_hyper ||

iglu_python/conga.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ def conga_single(data: pd.DataFrame, hours: int = 1, tz: str = "") -> float:
7777
lag = hourly_readings * hours
7878
diffs = gl_vector[lag:] - gl_vector[:-lag]
7979

80+
# Check if we have sufficient data for std calculation
81+
# Need at least 2 non-NaN values for ddof=1
82+
valid_diffs = diffs[~np.isnan(diffs)]
83+
if len(valid_diffs) < 2:
84+
return np.nan
85+
8086
return float(np.nanstd(diffs, ddof=1))
8187

8288
# Handle Series input

iglu_python/episode_calculation.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,15 @@ def episode_calculation(
168168
subject_episode_data['id'] = subject_id
169169

170170
# Append to main dataframes
171-
episode_data_df = pd.concat([episode_data_df, subject_episode_data], ignore_index=True)
172-
episode_summary_df = pd.concat([episode_summary_df, subject_summary], ignore_index=True)
171+
if episode_data_df.empty:
172+
episode_data_df = subject_episode_data
173+
else:
174+
episode_data_df = pd.concat([episode_data_df, subject_episode_data], ignore_index=True)
175+
176+
if episode_summary_df.empty:
177+
episode_summary_df = subject_summary
178+
else:
179+
episode_summary_df = pd.concat([episode_summary_df, subject_summary], ignore_index=True)
173180

174181

175182

@@ -238,7 +245,7 @@ def episode_single(
238245
day_one = day_one.tz_convert(local_tz)
239246
ndays = len(gd2d_tuple[1])
240247
# generate grid times by starting from day one and cumulatively summing
241-
time_ip = pd.date_range(start=day_one + pd.Timedelta(minutes=dt0), periods=ndays * 24 * 60 /dt0, freq=f"{dt0}min")
248+
time_ip = pd.date_range(start=day_one + pd.Timedelta(minutes=dt0), periods=int(ndays * 24 * 60 /dt0), freq=f"{dt0}min")
242249
data_ip = gd2d_tuple[0].flatten().tolist()
243250
new_data = pd.DataFrame({
244251
"time": time_ip,
@@ -297,29 +304,25 @@ def episode_single(
297304
x, "hypo", lv1_hypo, int(120 / dt0) + 1, end_idx
298305
),
299306
}
300-
)
307+
),
308+
include_groups=False
301309
)
302310
.reset_index()
303311
.drop(columns=['level_1'])
304312
)
305313

306314

307-
# Add exclusive labels
308-
def hypo_exclusion_logic(group_df):
309-
# group_df is a DataFrame with all columns for the current group
310-
if (group_df['lv2_hypo'] > 0).any():
311-
return pd.Series([0] * len(group_df), index=group_df.index)
312-
else:
313-
return group_df['lv1_hypo']
314-
ep_per_seg['lv1_hypo_excl'] = ep_per_seg.groupby(['segment', 'lv1_hypo']).apply(hypo_exclusion_logic).reset_index(level=[0,1], drop=True).values.flatten()
315-
316-
def hyper_exclusion_logic(group_df):
317-
# group_df is a DataFrame with all columns for the current group
318-
if (group_df['lv2_hyper'] > 0).any():
319-
return pd.Series([0] * len(group_df), index=group_df.index)
320-
else:
321-
return group_df['lv1_hyper']
322-
ep_per_seg['lv1_hyper_excl'] = ep_per_seg.groupby(['segment', 'lv1_hyper']).apply(hyper_exclusion_logic).reset_index(level=[0,1], drop=True).values.flatten()
315+
# Add exclusive labels using the correct original logic without DeprecationWarning
316+
# For hypo exclusion: group by both segment and lv1_hypo, set to 0 if any lv2_hypo > 0 in that group
317+
def calculate_exclusion(df, lv1_col, lv2_col):
318+
"""Calculate exclusion labels for lv1 episodes based on lv2 episodes in same group"""
319+
df = df.copy()
320+
df['group_id'] = df.groupby(['segment', lv1_col]).ngroup()
321+
group_has_lv2 = df.groupby('group_id')[lv2_col].transform(lambda x: (x > 0).any())
322+
return df[lv1_col].where(~group_has_lv2, 0)
323+
324+
ep_per_seg['lv1_hypo_excl'] = calculate_exclusion(ep_per_seg, 'lv1_hypo', 'lv2_hypo')
325+
ep_per_seg['lv1_hyper_excl'] = calculate_exclusion(ep_per_seg, 'lv1_hyper', 'lv2_hyper')
323326

324327
full_segment_df = pd.concat([segment_data, ep_per_seg.drop(["segment"], axis=1)], axis=1)
325328

@@ -402,7 +405,8 @@ def event_class(
402405
else None] + [None]*(len(x)-1)
403406
),
404407
}
405-
)
408+
),
409+
include_groups=False
406410
)
407411
.reset_index()
408412
.drop(columns=['level_1'])
@@ -471,7 +475,8 @@ def lv1_excl(data: pd.DataFrame) -> np.ndarray:
471475
lambda x: pd.DataFrame(
472476
{
473477
"excl":[0 if (x[lv2_first].values > 0).any() else x[lv1_first].iloc[0]]*len(x)
474-
})
478+
}),
479+
include_groups=False
475480
)
476481

477482
excl = excl.reset_index()

iglu_python/grade.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def grade(data: Union[pd.DataFrame, pd.Series]) -> pd.DataFrame:
7979
# Calculate GRADE score for each subject
8080
result = (
8181
data.groupby("id")
82-
.apply(lambda x: np.mean(_grade_formula(x["gl"].dropna())))
82+
.apply(lambda x: np.mean(_grade_formula(x["gl"].dropna())), include_groups=False)
8383
.reset_index()
8484
)
8585
result.columns = ["id", "GRADE"]

iglu_python/lbgi.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,11 @@ def lbgi(data: Union[pd.DataFrame, pd.Series]) -> pd.DataFrame:
109109
raise ValueError("Empty DataFrame provided")
110110

111111
# Calculate LBGI for each subject
112-
result = pd.DataFrame(columns=["id", "LBGI"])
112+
results = []
113113

114114
for subject_id in data["id"].unique():
115115
subject_data = data[data["id"] == subject_id]["gl"]
116116
lbgi_value = calculate_lbgi(subject_data)
117-
result = pd.concat(
118-
[result, pd.DataFrame({"id": [subject_id], "LBGI": [lbgi_value]})],
119-
ignore_index=True,
120-
)
117+
results.append({"id": subject_id, "LBGI": lbgi_value})
121118

122-
return result
119+
return pd.DataFrame(results)

iglu_python/m_value.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def m_value(data: Union[pd.DataFrame, pd.Series], r: float = 90) -> pd.DataFrame
7171
# Calculate M-value for each subject
7272
result = (
7373
data.groupby("id")
74-
.apply(lambda x: 1000 * np.mean(np.abs(np.log10(x["gl"] / r)) ** 3))
74+
.apply(lambda x: 1000 * np.mean(np.abs(np.log10(x["gl"] / r)) ** 3), include_groups=False)
7575
.reset_index()
7676
)
7777
result.columns = ["id", "M_value"]

iglu_python/mage.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,10 @@ def mage_ma_single(data: pd.DataFrame, short_ma: int, long_ma: int,
180180
return_val = pd.DataFrame(columns=["start", "end", "mage", "plus_or_minus", "first_excursion"])
181181
for segment in dfs:
182182
ret = mage_atomic(segment,short_ma,long_ma)
183-
return_val = pd.concat([return_val, ret], ignore_index=True)
183+
if return_val.empty:
184+
return_val = ret
185+
else:
186+
return_val = pd.concat([return_val, ret], ignore_index=True)
184187

185188
if return_type == 'df':
186189
return return_val
@@ -195,9 +198,8 @@ def mage_ma_single(data: pd.DataFrame, short_ma: int, long_ma: int,
195198
res = return_val[return_val['MAGE'].notna()].copy()
196199
elif direction == 'max':
197200
# Group by start,end and keep max mage in each group
198-
res = (return_val.groupby(['start', 'end'])
199-
.apply(lambda x: x[x['MAGE'] == x['MAGE'].max()])
200-
.reset_index(drop=True))
201+
idx = return_val.groupby(['start', 'end'])['MAGE'].idxmax()
202+
res = return_val.loc[idx].reset_index(drop=True)
201203
else: # default: first excursions only
202204
res = return_val[return_val['first_excursion'] == True].copy()
203205

@@ -220,13 +222,13 @@ def mage_atomic(data, short_ma,long_ma):
220222
data["MA_Long"] = data["gl"].rolling(window=long_ma, min_periods=1).mean()
221223
# Fill leading NAs (forward fill first valid value)
222224
if short_ma > len(data):
223-
data['MA_Short'].iloc[:short_ma] = data['MA_Short'].iloc[-1]
225+
data.loc[data.index[:short_ma], 'MA_Short'] = data['MA_Short'].iloc[-1]
224226
else:
225-
data['MA_Short'].iloc[:short_ma] = data['MA_Short'].iloc[short_ma-1]
227+
data.loc[data.index[:short_ma], 'MA_Short'] = data['MA_Short'].iloc[short_ma-1]
226228
if long_ma > len(data):
227-
data['MA_Long'].iloc[:long_ma] = data['MA_Long'].iloc[-1]
229+
data.loc[data.index[:long_ma], 'MA_Long'] = data['MA_Long'].iloc[-1]
228230
else:
229-
data['MA_Long'].iloc[:long_ma] = data['MA_Long'].iloc[long_ma-1]
231+
data.loc[data.index[:long_ma], 'MA_Long'] = data['MA_Long'].iloc[long_ma-1]
230232
# Calculate difference
231233
data['DELTA_SHORT_LONG'] = data['MA_Short'] - data['MA_Long']
232234
data = data.reset_index(drop=True)

iglu_python/modd.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ def modd_single(data: pd.DataFrame) -> float:
7272
abs_diffs = abs_diffs[~np.isnan(abs_diffs)] # Remove NaNs
7373

7474
# Calculate mean of absolute differences, ignoring NaN values
75-
modd_val = np.nanmean(abs_diffs)
75+
if len(abs_diffs) == 0:
76+
modd_val = np.nan
77+
else:
78+
modd_val = np.nanmean(abs_diffs)
7679

7780
return float(modd_val) if not pd.isna(modd_val) else np.nan
7881

iglu_python/pgs.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,12 @@ def pgs_single(subj_data: pd.DataFrame) -> float:
127127

128128
return pgs_score
129129

130-
# Calculate PGS for each subject
131-
result = data.groupby("id").apply(lambda x: pgs_single(x)).reset_index()
132-
result.columns = ["id", "PGS"]
133130

134-
return result
131+
# Calculate PGS for each subject
132+
results = []
133+
for subject_id in data["id"].unique():
134+
subject_data = data[data["id"] == subject_id].copy()
135+
pgs_value = pgs_single(subject_data)
136+
results.append({"id": subject_id, "PGS": pgs_value})
137+
138+
return pd.DataFrame(results)

iglu_python/roc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def roc_single(data: pd.DataFrame, timelag: int, dt0: int = None , inter_gap: in
123123
{
124124
"id": ["subject1"] * len(data),
125125
"time": pd.date_range(
126-
start="2020-01-01", periods=len(data), freq=f"{dt0}T"
126+
start="2020-01-01", periods=len(data), freq=f"{dt0}min"
127127
),
128128
"gl": data.values,
129129
}

0 commit comments

Comments
 (0)