Comprehensive function to detect all types of glycemic events aligned with
international consensus CGM metrics (Battelino et al., 2023). This function
provides a unified interface for detecting multiple event types including
Level 1/2/Extended hypo- and hyperglycemia, Level 1 excluded events, and
rebound hypo-/hyperglycemia summaries.
Rebound hypoglycemia and hyperglycemia definitions follow Hansen and Bibby
(2024).
Events are counted only after the required recovery condition is confirmed;
duration summaries use the event boundary immediately before recovery starts.
Event preprocessing uses cgmguru's independent C++ implementation of an
iglu-compatible day-based grid: each subject is interpolated from the first
observed day's midnight plus one reading interval, rather than from the first
observed timestamp. Larger gaps are masked and removed before event
classification, preserving gap-based segment boundaries. This preprocessing
is specific to event calculation and does not affect grid,
maxima_grid, or excursion.
CGM summary metrics in subject_summary are calculated from the original
raw glucose values by default. Set
summary_metrics_source = "preprocessed" to calculate them from the
internal event-preprocessed grid. Numeric summary outputs are rounded to
summary_digits decimal places; set summary_digits = NULL or
summary_digits = "none" to return unrounded values.
Usage
detect_all_events(df, reading_minutes = NULL, sort_time = FALSE,
inter_gap = 45, return_interpolated = FALSE,
summary_metrics_source = c("raw", "preprocessed"),
sensor_wear_ndays = NULL, summary_digits = 2)Arguments
- df
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
id: Subject identifier (string or factor)time: Time of measurement (POSIXct)gl: Glucose value (integer or numeric, mg/dL)
- reading_minutes
Time interval between readings in minutes (optional). Can be a single integer/numeric value (applied to all subjects), a vector matching data length, or
NULL. If omitted orNULL, the interval is calculated automatically per id as the median positive time difference in the data.- sort_time
Logical. If
TRUE, sort rows within each id bytimein C++ before interpolation. Defaults toFALSE.- inter_gap
Maximum gap in minutes to interpolate across. Defaults to 45; larger gaps split event-detection segments.
- return_interpolated
Logical. If
TRUE, include the internal event-preprocessed grid asinterpolated_data. Defaults toFALSE.- summary_metrics_source
Character. Source glucose values for CGM summary metrics. Defaults to
"raw"for original observed data; use"preprocessed"for the internal event-preprocessed grid after interpolation and gap masking.sensor_wear_percentis always calculated from the original timestamps and glucose readings.- sensor_wear_ndays
Number of days for fixed-window
sensor_wear_percentcalculation. Defaults toNULL, which uses the original timestamp span. Set to a positive number such as90to calculate observed readings over the last 90 days for each subject divided by the expected number of readings in 90 days.- summary_digits
Number of decimal places for numeric summary outputs in
subject_summaryand rate/duration columns inglycemic_event_summary. Defaults to2. UseNULLor"none"to return unrounded values.
Value
A list containing:
subject_summary: One row per subject. CGM summary metric columns are calculated on the original raw glucose values by default; setsummary_metrics_source = "preprocessed"to use the event-preprocessed glucose grid. Event summaries are included as wide*_total_episodescolumns only. Numeric summary columns are rounded according tosummary_digits.glycemic_event_summary: One row per subject, event type, and event level. Contains the full event summary:id,type,level,total_episodes,avg_ep_per_day, andavg_minutes_below_54_per_episode.interpolated_data: Included whenreturn_interpolated = TRUE, with columnsid,time, andgl.
subject_summary includes:
id: Subject identifierTIR: Percent of glucose readings in range 70-180 mg/dLTITR: Percent of glucose readings in tight range 70-140 mg/dLTBR70: Percent of glucose readings below 70 mg/dLTBR54: Percent of glucose readings below 54 mg/dLTAR180: Percent of glucose readings above 180 mg/dLTAR250: Percent of glucose readings above 250 mg/dLCV: Coefficient of variation in percent, \(100 * SD / mean_glucose\)SD: Sample standard deviation of glucose, mg/dLmean_glucose: Mean glucose, mg/dLGMI: Glucose Management Indicator, \(3.31 + 0.02392 * mean_glucose\)uGMI: Unitless GMI-style metric, \(1 / (15.36 / mean_glucose + 0.0425)\)GRI: Glycemia Risk Index, \(3.0 * VLow + 2.4 * Low + 1.6 * VHigh + 0.8 * High\), whereVLowis percent time \(<\)54 mg/dL,Lowis 54-\(<\)70 mg/dL,VHighis \(>\)250 mg/dL, andHighis \(>\)180-\(\leq\)250 mg/dLsensor_wear_percent: Percent of expected CGM readings observed, calculated from the original timestamps using the same automatic range method asiglu::active_percent()by default. Ifsensor_wear_ndaysis supplied, this is calculated over the last N days for each subject.hypo_lv1_total_episodes: Number of Level 1 hypoglycemia episodeshypo_lv2_total_episodes: Number of Level 2 hypoglycemia episodeshypo_extended_total_episodes: Number of extended hypoglycemia episodeshypo_lv1_excl_total_episodes: Number of Level 1 hypoglycemia episodes that do not overlap a Level 2 episodehypo_rebound_total_episodes: Number of rebound hypoglycemia episodeshyper_lv1_total_episodes: Number of Level 1 hyperglycemia episodeshyper_lv2_total_episodes: Number of Level 2 hyperglycemia episodeshyper_extended_total_episodes: Number of extended hyperglycemia episodeshyper_lv1_excl_total_episodes: Number of Level 1 hyperglycemia episodes that do not overlap a Level 2 episodehyper_rebound_total_episodes: Number of rebound hyperglycemia episodes
glycemic_event_summary includes:
id: Subject identifiertype: Event direction, either"hypo"or"hyper"level: Event level, one of"lv1","lv2","extended","lv1_excl", or"rebound"total_episodes: Number of episodes for the subject, event direction, and event levelavg_ep_per_day: Average episodes per day for the subject, event direction, and event level, rounded according tosummary_digitsavg_minutes_below_54_per_episode: For hypoglycemia rows, average minutes below 54 mg/dL per episode, rounded according tosummary_digits; for hyperglycemia rows, 0
Event types
- Hypoglycemia: lv1 (\(<\) 70 mg/dL, \(\geq\) 15 min), lv2 (\(<\) 54 mg/dL, \(\geq\) 15 min), extended (\(<\) 70 mg/dL, \(\geq\) 120 min). - Hyperglycemia: lv1 (\(>\) 180 mg/dL, \(\geq\) 15 min), lv2 (\(>\) 250 mg/dL, \(\geq\) 15 min), extended (\(>\) 250 mg/dL, \(\geq\) 90 min in 120 min, end \(\leq\) 180 mg/dL for \(\geq\) 15 min). - Rebound: hypo rebound is Level 1 hyperglycemia followed by \(<\)70 mg/dL within 120 minutes; hyper rebound is Level 1 hypoglycemia followed by \(>\)180 mg/dL within 120 minutes.
References
Battelino, T., et al. (2023). Continuous glucose monitoring and metrics for clinical trials: an international consensus statement. The Lancet Diabetes & Endocrinology, 11(1), 42-57.
Hansen, K. W., and Bibby, B. M. (2024). Rebound hypoglycemia and hyperglycemia in type 1 diabetes. Journal of Diabetes Science and Technology, 18(6), 1392-1398.
Examples
# Load sample data
library(iglu)
data(example_data_5_subject)
data(example_data_hall)
# Detect all glycemic events; reading_minutes is calculated automatically
# from the timestamp spacing when omitted
all_outputs <- detect_all_events(example_data_5_subject)
print(all_outputs$subject_summary)
#> # A tibble: 5 × 24
#> id TIR TITR TBR70 TBR54 TAR180 TAR250 CV SD mean_glucose GMI
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Subject 1 91.7 73.7 0.14 0 8.2 0.38 26.9 33.3 124. 6.27
#> 2 Subject 2 26.4 3.36 0 0 73.6 26.1 24.0 52.4 218. 8.54
#> 3 Subject 3 81.3 49.8 0.33 0 18.3 5.68 29.1 44.8 154. 6.99
#> 4 Subject 4 95.1 67.7 0.27 0.05 4.61 0 22.4 29.1 130. 6.41
#> 5 Subject 5 62.1 30.1 0.1 0 37.8 11.3 33.6 58.6 175. 7.49
#> # ℹ 13 more variables: uGMI <dbl>, GRI <dbl>, sensor_wear_percent <dbl>,
#> # hypo_lv1_total_episodes <int>, hypo_lv2_total_episodes <int>,
#> # hypo_extended_total_episodes <int>, hypo_lv1_excl_total_episodes <int>,
#> # hypo_rebound_total_episodes <int>, hyper_lv1_total_episodes <int>,
#> # hyper_lv2_total_episodes <int>, hyper_extended_total_episodes <int>,
#> # hyper_lv1_excl_total_episodes <int>, hyper_rebound_total_episodes <int>
print(all_outputs$glycemic_event_summary)
#> # A tibble: 50 × 6
#> id type level total_episodes avg_ep_per_day avg_minutes_below_54…¹
#> <chr> <chr> <chr> <int> <dbl> <dbl>
#> 1 Subject 1 hypo lv1 1 0.09 0
#> 2 Subject 1 hypo lv2 0 0 0
#> 3 Subject 1 hypo extended 0 0 0
#> 4 Subject 1 hypo lv1_excl 1 0.09 0
#> 5 Subject 1 hypo rebound 0 0 0
#> 6 Subject 1 hyper lv1 16 1.44 0
#> 7 Subject 1 hyper lv2 2 0.18 0
#> 8 Subject 1 hyper extended 0 0 0
#> 9 Subject 1 hyper lv1_excl 14 1.26 0
#> 10 Subject 1 hyper rebound 0 0 0
#> # ℹ 40 more rows
#> # ℹ abbreviated name: ¹avg_minutes_below_54_per_episode
# Detect all events on larger dataset
large_outputs <- detect_all_events(example_data_hall)
print(paste("Total subjects analyzed:", nrow(large_outputs$subject_summary)))
#> [1] "Total subjects analyzed: 19"