Identifies and segments hyperglycemic events in CGM data based on international consensus
CGM metrics (Battelino et al., 2023). Use type to select one of
three event definitions:
Level 1: \(\geq\) 15 consecutive min of \(>\) 180 mg/dL, ends with \(\geq\) 15 consecutive min \(\leq\) 180 mg/dL
Level 2: \(\geq\) 15 consecutive min of \(>\) 250 mg/dL, ends with \(\geq\) 15 consecutive min \(\leq\) 250 mg/dL
Extended: \(>\) 250 mg/dL lasting \(\geq\) 90 cumulative min within a 120-min period, ends when glucose returns to \(\leq\) 180 mg/dL for \(\geq\) 15 consecutive min after
Events are counted only after glucose remains at or below the end threshold
for the specified end length. In events_detailed, end_time,
end_glucose, and end_index report the last hyperglycemic
reading immediately before that confirmed recovery period starts.
Usage
detect_hyperglycemic_events(df, ..., type = "extended",
reading_minutes = NULL, sort_time = FALSE, inter_gap = 45,
return_interpolated = TRUE)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)
- ...
Custom event criteria supplied by name. Prefer
typefor standard Level 1, Level 2, and Extended events. Supported custom criteria are:dur_length: Minimum event duration in minutes required for an event to qualify.end_length: Required recovery duration in minutes before event termination is confirmed.start_gl: Glucose threshold in mg/dL used to qualify hyperglycemic readings. Hyperglycemic readings are above this value.end_gl: Glucose threshold in mg/dL used to confirm recovery. Hyperglycemic events end after glucose remains at or below this value forend_lengthminutes.
- type
Hyperglycemia event definition. One of
"extended"(default),"lv1","lv2", or"lv1_excl".- reading_minutes
Time interval between readings in minutes (optional). If omitted or
NULL, 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 interpolated grid data used for event detection in the returned list. Defaults toTRUE.
Value
A list containing:
events_total: Tibble with summary statistics per subject (id, total_episodes, avg_ep_per_day)events_detailed: Tibble with detailed event information (id, start_time, start_glucose, end_time, end_glucose, start_index, end_index). End fields report the last dysglycemic reading before confirmed recovery starts.start_indexandend_indexare 1-based row positions in the internal interpolated event grid, returned asinterpolated_datawhenreturn_interpolated = TRUE.interpolated_data: Included whenreturn_interpolated = TRUE, with columnsid,time, andgl.
Methods
Hyperglycemic events can be detected using either the recommended
type argument or named custom threshold and duration criteria.
1. Preset method using type (recommended):
Use type when you want the standard Level 1, Level 2, or Extended
hyperglycemia definitions without manually entering thresholds.
type = "lv1"usesstart_gl = 180,dur_length = 15,end_length = 15, andend_gl = 180.type = "lv2"usesstart_gl = 250,dur_length = 15,end_length = 15, andend_gl = 250.type = "extended"usesstart_gl = 250,dur_length = 120,end_length = 15, andend_gl = 180.type = "lv1_excl"returns Level 1 episodes that do not overlap Level 2 episodes.
2. Custom criteria method:
Supply start_gl, dur_length, end_length, and
end_gl directly when using a custom definition, for example
detect_hyperglycemic_events(df, start_gl = 180, dur_length = 15,
end_length = 15, end_gl = 180) for Level 1 hyperglycemia. If an explicit
type is supplied together with custom numeric criteria, the function
returns results based on type; the custom criteria are ignored and a
warning is issued.
Units and sampling
- reading_minutes can be a scalar (all rows) or a vector per-row.
- If reading_minutes is omitted or NULL, it is calculated
automatically per id from timestamp spacing.
- Event classification uses cgmguru's independent C++ implementation of an
iglu-compatible, midnight-aligned full-day grid. Data are linearly
interpolated at the id-specific interval up to inter_gap; larger
gaps are masked, removed from the event-classification data, and split
segments.
- This preprocessing is specific to event calculation and does not affect
grid, maxima_grid, or excursion.
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.
Examples
# Load sample data
library(iglu)
data(example_data_5_subject)
data(example_data_hall)
# Level 1 Hyperglycemia Event (>=15 consecutive min of >180 mg/dL and event
# ends when there is >=15 consecutive min with a CGM sensor value of <=180 mg/dL)
hyper_lv1 <- detect_hyperglycemic_events(example_data_5_subject, type = "lv1")
print(hyper_lv1$events_total)
#> # A tibble: 5 × 3
#> id total_episodes avg_ep_per_day
#> <chr> <int> <dbl>
#> 1 Subject 1 16 1.44
#> 2 Subject 2 21 2.13
#> 3 Subject 3 9 1.64
#> 4 Subject 4 13 1.02
#> 5 Subject 5 38 3.72
# Level 2 Hyperglycemia Event (>=15 consecutive min of >250 mg/dL and event
# ends when there is >=15 consecutive min with a CGM sensor value of <=250 mg/dL)
hyper_lv2 <- detect_hyperglycemic_events(example_data_5_subject, type = "lv2")
print(hyper_lv2$events_total)
#> # A tibble: 5 × 3
#> id total_episodes avg_ep_per_day
#> <chr> <int> <dbl>
#> 1 Subject 1 2 0.18
#> 2 Subject 2 19 1.93
#> 3 Subject 3 4 0.73
#> 4 Subject 4 0 0
#> 5 Subject 5 18 1.76
# Extended Hyperglycemia Event (>250 mg/dL lasting >=90 cumulative min within a
# 120-min period, ends when glucose returns to <=180 mg/dL for >=15 consecutive
# min after)
hyper_extended <- detect_hyperglycemic_events(example_data_5_subject, type = "extended")
print(hyper_extended$events_total)
#> # A tibble: 5 × 3
#> id total_episodes avg_ep_per_day
#> <chr> <int> <dbl>
#> 1 Subject 1 0 0
#> 2 Subject 2 10 1.02
#> 3 Subject 3 2 0.36
#> 4 Subject 4 0 0
#> 5 Subject 5 10 0.98
# Custom criteria method for the same standard definitions
hyper_lv1_custom <- detect_hyperglycemic_events(
example_data_5_subject,
start_gl = 180,
dur_length = 15,
end_length = 15,
end_gl = 180
)
hyper_lv2_custom <- detect_hyperglycemic_events(
example_data_5_subject,
start_gl = 250,
dur_length = 15,
end_length = 15,
end_gl = 250
)
hyper_extended_custom <- detect_hyperglycemic_events(
example_data_5_subject,
start_gl = 250,
dur_length = 120,
end_length = 15,
end_gl = 180
)
# Compare event rates across levels
cat("Level 1 episodes:", sum(hyper_lv1$events_total$total_episodes), "\n")
#> Level 1 episodes: 97
cat("Level 2 episodes:", sum(hyper_lv2$events_total$total_episodes), "\n")
#> Level 2 episodes: 43
cat("Extended episodes:", sum(hyper_extended$events_total$total_episodes), "\n")
#> Extended episodes: 22
# Analysis on larger dataset with Level 1 criteria
large_hyper <- detect_hyperglycemic_events(example_data_hall, type = "lv1")
print(large_hyper$events_total)
#> # A tibble: 19 × 3
#> id total_episodes avg_ep_per_day
#> <chr> <int> <dbl>
#> 1 1636-69-001 4 0.62
#> 2 1636-69-026 1 0.16
#> 3 1636-69-032 1 0.16
#> 4 1636-69-090 3 0.46
#> 5 1636-69-091 0 0
#> 6 1636-69-114 0 0
#> 7 1636-70-1005 3 0.46
#> 8 1636-70-1010 1 0.16
#> 9 2133-004 5 0.81
#> 10 2133-015 3 0.46
#> 11 2133-017 1 0.16
#> 12 2133-018 12 1.94
#> 13 2133-019 0 0
#> 14 2133-021 9 1.44
#> 15 2133-024 0 0
#> 16 2133-027 0 0
#> 17 2133-035 1 0.15
#> 18 2133-036 2 0.28
#> 19 2133-039 2 0.27
# Analysis on larger dataset with Level 2 criteria
large_hyper_lv2 <- detect_hyperglycemic_events(example_data_hall, type = "lv2")
print(large_hyper_lv2$events_total)
#> # A tibble: 19 × 3
#> id total_episodes avg_ep_per_day
#> <chr> <int> <dbl>
#> 1 1636-69-001 0 0
#> 2 1636-69-026 0 0
#> 3 1636-69-032 0 0
#> 4 1636-69-090 0 0
#> 5 1636-69-091 0 0
#> 6 1636-69-114 0 0
#> 7 1636-70-1005 0 0
#> 8 1636-70-1010 0 0
#> 9 2133-004 0 0
#> 10 2133-015 0 0
#> 11 2133-017 0 0
#> 12 2133-018 2 0.32
#> 13 2133-019 0 0
#> 14 2133-021 0 0
#> 15 2133-024 0 0
#> 16 2133-027 0 0
#> 17 2133-035 0 0
#> 18 2133-036 0 0
#> 19 2133-039 0 0
# Analysis on larger dataset with Extended criteria
large_hyper_extended <- detect_hyperglycemic_events(example_data_hall, type = "extended")
print(large_hyper_extended$events_total)
#> # A tibble: 19 × 3
#> id total_episodes avg_ep_per_day
#> <chr> <int> <dbl>
#> 1 1636-69-001 0 0
#> 2 1636-69-026 0 0
#> 3 1636-69-032 0 0
#> 4 1636-69-090 0 0
#> 5 1636-69-091 0 0
#> 6 1636-69-114 0 0
#> 7 1636-70-1005 0 0
#> 8 1636-70-1010 0 0
#> 9 2133-004 0 0
#> 10 2133-015 0 0
#> 11 2133-017 0 0
#> 12 2133-018 1 0.16
#> 13 2133-019 0 0
#> 14 2133-021 0 0
#> 15 2133-024 0 0
#> 16 2133-027 0 0
#> 17 2133-035 0 0
#> 18 2133-036 0 0
#> 19 2133-039 0 0
# View detailed events for specific subject
if(nrow(hyper_lv1$events_detailed) > 0) {
first_subject <- hyper_lv1$events_detailed$id[1]
subject_events <- hyper_lv1$events_detailed[hyper_lv1$events_detailed$id == first_subject, ]
head(subject_events)
}
#> # A tibble: 6 × 7
#> id start_time start_glucose end_time end_glucose
#> <chr> <dttm> <dbl> <dttm> <dbl>
#> 1 Subject 1 2015-06-11 15:45:00 193. 2015-06-11 16:50:00 187.
#> 2 Subject 1 2015-06-11 17:25:00 195. 2015-06-11 19:00:00 183.
#> 3 Subject 1 2015-06-11 19:20:00 181. 2015-06-11 19:45:00 187.
#> 4 Subject 1 2015-06-11 22:35:00 187. 2015-06-11 23:45:00 185.
#> 5 Subject 1 2015-06-12 07:50:00 181. 2015-06-12 09:15:00 181.
#> 6 Subject 1 2015-06-13 16:55:00 180. 2015-06-13 18:25:00 186.
#> # ℹ 2 more variables: start_index <int>, end_index <int>