Skip to content

Commit 3614774

Browse files
committed
support for Series, list and ndarray -> return float or dict
1 parent c2645cb commit 3614774

File tree

11 files changed

+194
-180
lines changed

11 files changed

+194
-180
lines changed

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ Unless noted, IGLU-R test compatability is considered successful if it achieves
2525

2626
| Function | Description | IGLU-R test compatibility | list /ndarray /Series input | TZ | Comments |
2727
|----------|-------------|-------------|-------------------|----|----------|
28-
| above_percent | percentage of values above target thresholds||✅ returns dict |||
29-
| active_percent | percentage of time CGM was active | ✅ | ✅ only Series(DatetimeIndex) returns dict[str:float]
28+
| above_percent | percentage of values above target thresholds||✅ returns Dict[str,float] |||
29+
| active_percent | percentage of time CGM was active || ✅ only Series(DatetimeIndex) returns Dict[str,float]|
3030
| adrr | average daily risk range ||✅ only Series(DatetimeIndex) returns float |
3131
| auc| Area Under Curve | 🟡 (0.01 precision) |✅ only Series(DatetimeIndex) returns float || see [auc_evaluation.ipynb](https://github.com/staskh/iglu_python/blob/main/notebooks/auc_evaluation.ipynb)|
32-
| below_percent| percentage of values below target thresholds| ✅ | ✅ returns dict
32+
| below_percent| percentage of values below target thresholds|| ✅ returns Dict[str,float]|
3333
| cogi |Coefficient of Glucose Irregularity | ✅ | ✅ returns float
3434
| conga | Continuous Overall Net Glycemic Action |✅ | ✅ only Series(DatetimeIndex) returns float
3535
| cv_glu | Coefficient of Variation || ✅ returns float |
36-
| cv_measures |Coefficient of Variation subtypes (CVmean and CVsd) ||✅ only Series(DatetimeIndex) returns dict| |
36+
| cv_measures |Coefficient of Variation subtypes (CVmean and CVsd) ||✅ only Series(DatetimeIndex) returns Dict[str,float]| |
3737
| ea1c |estimated A1C (eA1C) values|| ✅ returns float |
3838
| episode_calculation | Hypo/Hyperglycemic episodes with summary statistics|| 🟡 always returns DataFrame(s)|| |
3939
| gmi | Glucose Management Indicator || ✅ returns float |
@@ -47,16 +47,16 @@ Unless noted, IGLU-R test compatability is considered successful if it achieves
4747
| hyper_index |Hyperglycemia Index||✅ returns float |
4848
| hypo_index |Hypoglycemia Index||✅ returns float |
4949
| igc |Index of Glycemic Control||✅ returns float |
50-
| in_range_percent |percentage of values within target ranges| ✅ | ✅ returns dict
50+
| in_range_percent |percentage of values within target ranges|| ✅ returns Dict[str,float]|
5151
| iqr_glu |glucose level interquartile range||✅ returns float |
5252
| j_index |J-Index score for glucose measurements||✅ returns float |
53-
| lbgi | Low Blood Glucose Index||
54-
| m_value | M-value of Schlichtkrull et al ||
55-
| mad_glu | Median Absolute Deviation ||
56-
| mag | Mean Absolute Glucose|| || IMHO, Original R implementation has an error |
53+
| lbgi | Low Blood Glucose Index||✅ returns float |
54+
| m_value | M-value of Schlichtkrull et al ||✅ returns float |
55+
| mad_glu | Median Absolute Deviation ||✅ returns float |
56+
| mag | Mean Absolute Glucose|| ✅ only Series(DatetimeIndex) returns float ||| IMHO, Original R implementation has an error |
5757
| mage | Mean Amplitude of Glycemic Excursions||✅ only Series(DatetimeIndex) returns float || See algorithm at [MAGE](https://irinagain.github.io/iglu/articles/MAGE.html) |
5858
| mean_glu | Mean glucose value || ✅ returns float|
59-
| median_glu |Median glucose value||
59+
| median_glu |Median glucose value||✅ returns float |
6060
| modd | Mean of Daily Differences||
6161
| pgs | Personal Glycemic State || || |
6262
| quantile_glu |glucose level quantiles||

iglu_python/lbgi.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .utils import check_data_columns
77

88

9-
def lbgi(data: Union[pd.DataFrame, pd.Series]) -> pd.DataFrame:
9+
def lbgi(data: Union[pd.DataFrame, pd.Series, np.ndarray, list]) -> pd.DataFrame|float:
1010
r"""
1111
Calculate the Low Blood Glucose Index (LBGI) for each subject.
1212
@@ -21,15 +21,15 @@ def lbgi(data: Union[pd.DataFrame, pd.Series]) -> pd.DataFrame:
2121
2222
Parameters
2323
----------
24-
data : Union[pd.DataFrame, pd.Series]
25-
DataFrame with columns ['id', 'time', 'gl'] or Series of glucose values
24+
data : Union[pd.DataFrame, pd.Series, np.ndarray, list]
25+
DataFrame with columns ['id', 'time', 'gl'] or Series of glucose values, or a numpy array or list of glucose values
2626
in mg/dL
2727
2828
Returns
2929
-------
30-
pd.DataFrame
30+
pd.DataFrame|float
3131
DataFrame with columns ['id', 'LBGI'] containing LBGI values for each subject
32-
If input is a Series, returns DataFrame with single row and column 'LBGI'
32+
If input is a Series, returns a float.
3333
3434
References
3535
----------
@@ -63,26 +63,18 @@ def lbgi(data: Union[pd.DataFrame, pd.Series]) -> pd.DataFrame:
6363
LBGI
6464
0 0.123456
6565
"""
66-
if isinstance(data, pd.Series):
67-
lbgi_value = calculate_lbgi(data)
68-
return pd.DataFrame({"LBGI": [lbgi_value]})
66+
if isinstance(data, (pd.Series,list, np.ndarray)):
67+
if isinstance(data, (np.ndarray, list)):
68+
data = pd.Series(data)
69+
return calculate_lbgi(data)
6970

7071
# Check DataFrame format
7172
check_data_columns(data)
7273

73-
if len(data) == 0:
74-
raise ValueError("Empty DataFrame provided")
75-
76-
# Calculate LBGI for each subject
77-
results = []
78-
79-
for subject_id in data["id"].unique():
80-
subject_data = data[data["id"] == subject_id]["gl"]
81-
lbgi_value = calculate_lbgi(subject_data)
82-
results.append({"id": subject_id, "LBGI": lbgi_value})
83-
84-
return pd.DataFrame(results)
85-
74+
out = data.groupby('id').agg(
75+
LBGI = ("gl", lambda x: calculate_lbgi(x))
76+
).reset_index()
77+
return out
8678

8779
def calculate_lbgi(glucose_values: pd.Series) -> float:
8880
"""

iglu_python/m_value.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .utils import check_data_columns
77

88

9-
def m_value(data: Union[pd.DataFrame, pd.Series], r: float = 90) -> pd.DataFrame:
9+
def m_value(data: Union[pd.DataFrame, pd.Series, np.ndarray, list], r: float = 90) -> pd.DataFrame|float:
1010
r"""
1111
Calculate the M-value of Schlichtkrull et al. (1965) for each subject.
1212
@@ -22,17 +22,16 @@ def m_value(data: Union[pd.DataFrame, pd.Series], r: float = 90) -> pd.DataFrame
2222
2323
Parameters
2424
----------
25-
data : Union[pd.DataFrame, pd.Series]
26-
DataFrame with columns 'id', 'time', and 'gl', or a Series of glucose values
25+
data : Union[pd.DataFrame, pd.Series, np.ndarray, list]
26+
DataFrame with columns 'id', 'time', and 'gl', or a Series of glucose values, or a numpy array or list of glucose values
2727
r : float, default=90
2828
A reference value corresponding to basal glycemia in normal subjects
2929
3030
Returns
3131
-------
32-
pd.DataFrame
32+
pd.DataFrame|float
3333
DataFrame with 1 row for each subject, a column for subject id and a column
34-
for M-value. If a Series of glucose values is passed, then a DataFrame
35-
without the subject id is returned.
34+
for M-value. If a Series of glucose values is passed, then a float is returned.
3635
3736
References
3837
----------
@@ -60,20 +59,26 @@ def m_value(data: Union[pd.DataFrame, pd.Series], r: float = 90) -> pd.DataFrame
6059
0 111.11
6160
"""
6261
# Handle Series input
63-
if isinstance(data, pd.Series):
64-
return pd.DataFrame(
65-
{"M_value": [1000 * np.mean(np.abs(np.log10(data / r)) ** 3)]}
66-
)
62+
if isinstance(data, (pd.Series,list, np.ndarray)):
63+
if isinstance(data, (np.ndarray, list)):
64+
data = pd.Series(data)
65+
return m_value_single(data, r)
6766

6867
# Handle DataFrame input
6968
data = check_data_columns(data)
7069

71-
# Calculate M-value for each subject
72-
result = (
73-
data.groupby("id")
74-
.apply(lambda x: 1000 * np.mean(np.abs(np.log10(x["gl"] / r)) ** 3), include_groups=False)
75-
.reset_index()
76-
)
77-
result.columns = ["id", "M_value"]
70+
out = data.groupby('id').agg(
71+
M_value = ("gl", lambda x: m_value_single(x, r))
72+
).reset_index()
73+
return out
74+
75+
def m_value_single(gl: pd.Series, r: float = 90) -> float:
76+
"""
77+
Calculate the M-value of Schlichtkrull et al. (1965) for a single subject.
78+
"""
79+
gl = gl.dropna()
80+
if len(gl) == 0:
81+
return np.nan
82+
m_value = 1000 * np.mean(np.abs(np.log10(gl / r)) ** 3)
83+
return m_value
7884

79-
return result

iglu_python/mad_glu.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88

99
def mad_glu(
10-
data: Union[pd.DataFrame, pd.Series], constant: float = 1.4826
11-
) -> pd.DataFrame:
10+
data: Union[pd.DataFrame, pd.Series, np.ndarray, list], constant: float = 1.4826
11+
) -> pd.DataFrame|float:
1212
"""
1313
Calculate Median Absolute Deviation (MAD) of glucose values.
1414
@@ -18,19 +18,21 @@ def mad_glu(
1818
1919
Parameters
2020
----------
21-
data : Union[pd.DataFrame, pd.Series]
22-
DataFrame with columns 'id', 'time', and 'gl', or a Series of glucose values
21+
data : Union[pd.DataFrame, pd.Series, np.ndarray, list]
22+
DataFrame with columns 'id', 'time', and 'gl', or a Series of glucose values,
23+
or a numpy array or list of glucose values
2324
constant : float, default=1.4826
2425
Scaling factor to multiply the MAD value. The default value of 1.4826
2526
makes the MAD consistent with the standard deviation for normally
2627
distributed data.
2728
2829
Returns
2930
-------
30-
pd.DataFrame
31+
pd.DataFrame|float
3132
DataFrame with columns:
3233
- id: subject identifier (if DataFrame input)
33-
- MAD: MAD value (median absolute deviation of glucose values)
34+
- MAD: MAD value (median absolute deviation of glucose values).
35+
If a Series of glucose values is passed, then a float is returned.
3436
3537
Examples
3638
--------
@@ -51,26 +53,25 @@ def mad_glu(
5153
0 27.5
5254
"""
5355
# Handle Series input
54-
if isinstance(data, pd.Series):
55-
# Calculate MAD for the Series
56-
mad_val = np.median(np.abs(data - np.median(data))) * constant
57-
return pd.DataFrame({"MAD": [mad_val]})
56+
if isinstance(data, (pd.Series,list, np.ndarray)):
57+
if isinstance(data, (np.ndarray, list)):
58+
data = pd.Series(data)
59+
return mad_glu_single(data, constant)
5860

5961
# Handle DataFrame input
6062
data = check_data_columns(data)
6163

62-
# Calculate MAD for each subject
63-
result = []
64-
for subject in data["id"].unique():
65-
subject_data = data[data["id"] == subject]
66-
if len(subject_data.dropna(subset=["gl"])) == 0:
67-
continue
64+
out = data.groupby('id').agg(
65+
MAD = ("gl", lambda x: mad_glu_single(x, constant))
66+
).reset_index()
67+
return out
6868

69-
# Calculate MAD for this subject
70-
mad_val = (
71-
np.median(np.abs(subject_data["gl"] - np.median(subject_data["gl"])))
72-
* constant
73-
)
74-
result.append({"id": subject, "MAD": mad_val})
75-
76-
return pd.DataFrame(result)
69+
def mad_glu_single(gl: pd.Series, constant: float = 1.4826) -> float:
70+
"""
71+
Calculate Median Absolute Deviation (MAD) of glucose values for a single subject.
72+
"""
73+
gl = gl.dropna()
74+
if len(gl) == 0:
75+
return np.nan
76+
mad_val = np.median(np.abs(gl - np.median(gl))) * constant
77+
return mad_val

0 commit comments

Comments
 (0)