77
88
99def modd (
10- data : Union [pd .DataFrame , pd .Series ], lag : int = 1 , tz : str = ""
11- ) -> pd .DataFrame :
10+ data : Union [pd .DataFrame , pd .Series , np . ndarray , list ], lag : int = 1 , tz : str = ""
11+ ) -> pd .DataFrame | float :
1212 """
1313 Calculate Mean of Daily Differences (MODD).
1414
@@ -18,19 +18,21 @@ def modd(
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 lag : int, default=1
2425 Integer indicating which lag (# days) to use. Default is 1.
2526 tz : str, default=""
2627 Time zone to use for datetime conversion. Empty string means use local time zone.
2728
2829 Returns
2930 -------
30- pd.DataFrame
31+ pd.DataFrame|float
3132 DataFrame with columns:
3233 - id: subject identifier (if DataFrame input)
33- - MODD: Mean of Daily Differences value
34+ - MODD: Mean of Daily Differences value.
35+ If a Series of glucose values is passed, then a float is returned.
3436
3537 References
3638 ----------
@@ -56,55 +58,40 @@ def modd(
5658 0 45.0
5759 """
5860
59- def modd_single (data : pd .DataFrame ) -> float :
60- """Calculate MODD for a single subject"""
61- # Convert data to day-by-day format
62- data_ip = CGMS2DayByDay (data , tz = tz )
63- gl_by_id_ip = data_ip [0 ].flatten () # Get interpolated glucose values
64- dt0 = data_ip [2 ] # Get time frequency
65-
66- # Calculate absolute differences with specified lag
67- # lag is in days, so we need to convert to minutes and divide of dt0 frequency
68- shift = int (lag * 24 * 60 / dt0 ) # Convert lag to minutes and divide by dt0
69- # Shift array by lag and calculate differences
70- abs_diffs = np .abs (gl_by_id_ip [shift :] - gl_by_id_ip [:- shift ])
71- # Remove NaNs
72- abs_diffs = abs_diffs [~ np .isnan (abs_diffs )] # Remove NaNs
73-
74- # Calculate mean of absolute differences, ignoring NaN values
75- if len (abs_diffs ) == 0 :
76- modd_val = np .nan
77- else :
78- modd_val = np .nanmean (abs_diffs )
79-
80- return float (modd_val ) if not pd .isna (modd_val ) else np .nan
81-
8261 # Handle Series input
8362 if isinstance (data , pd .Series ):
8463 if not isinstance (data .index , pd .DatetimeIndex ):
8564 raise ValueError ("Series must have a DatetimeIndex" )
86- data_df = pd .DataFrame (
87- {
88- "id" : ["subject1" ] * len (data .values ),
89- "time" : data .index ,
90- "gl" : data .values ,
91- }
92- )
93-
94- modd_val = modd_single (data_df )
95- return pd .DataFrame ({"MODD" : [modd_val ]})
65+ return modd_single (data , lag , tz )
9666
9767 # Handle DataFrame input
9868 data = check_data_columns (data )
9969
100- # Calculate MODD for each subject
101- result = []
102- for subject in data ["id" ].unique ():
103- subject_data = data [data ["id" ] == subject ].copy ()
104- if len (subject_data .dropna (subset = ["gl" ])) == 0 :
105- continue
106-
107- modd_val = modd_single (subject_data )
108- result .append ({"id" : subject , "MODD" : modd_val })
109-
110- return pd .DataFrame (result )
70+ data .set_index ('time' , drop = True , inplace = True )
71+ out = data .groupby ('id' ).agg (
72+ MODD = ("gl" , lambda x : modd_single (x , lag , tz ))
73+ ).reset_index ()
74+ return out
75+
76+ def modd_single (data : pd .Series , lag : int = 1 , tz : str = "" ) -> float :
77+ """Calculate MODD for a single subject"""
78+ # Convert data to day-by-day format
79+ data_ip = CGMS2DayByDay (data , tz = tz )
80+ gl_by_id_ip = data_ip [0 ].flatten () # Get interpolated glucose values
81+ dt0 = data_ip [2 ] # Get time frequency
82+
83+ # Calculate absolute differences with specified lag
84+ # lag is in days, so we need to convert to minutes and divide of dt0 frequency
85+ shift = int (lag * 24 * 60 / dt0 ) # Convert lag to minutes and divide by dt0
86+ # Shift array by lag and calculate differences
87+ abs_diffs = np .abs (gl_by_id_ip [shift :] - gl_by_id_ip [:- shift ])
88+ # Remove NaNs
89+ abs_diffs = abs_diffs [~ np .isnan (abs_diffs )] # Remove NaNs
90+
91+ # Calculate mean of absolute differences, ignoring NaN values
92+ if len (abs_diffs ) == 0 :
93+ modd_val = np .nan
94+ else :
95+ modd_val = np .nanmean (abs_diffs )
96+
97+ return float (modd_val ) if not pd .isna (modd_val ) else np .nan
0 commit comments