| Title: | Advanced Continuous Glucose Monitoring Analysis with High-Performance C++ Backend |
|---|---|
| Description: | Tools for advanced analysis of continuous glucose monitoring (CGM) time-series, implementing GRID (Glucose Rate Increase Detector) and GRID-based algorithms for postprandial peak detection, and detection of hypoglycemic and hyperglycemic episodes (Levels 1/2/Extended) aligned with international consensus CGM metrics. Core algorithms are implemented in optimized C++ using 'Rcpp' to provide accurate and fast analysis on large datasets. |
| Authors: | Sang Ho Park [aut, cre], Rosa Oh [aut, ctb], Sang-Man Jin [aut, ctb] |
| Maintainer: | Sang Ho Park <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.1.0 |
| Built: | 2026-06-09 03:59:12 UTC |
| Source: | https://github.com/shstat1729/cgmguru |
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, and Level 1 excluded events.
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.
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)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)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
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 |
sort_time |
Logical. If |
inter_gap |
Maximum gap in minutes to interpolate across. Defaults to 45; larger gaps split event-detection segments. |
return_interpolated |
Logical. If |
summary_metrics_source |
Character. Source glucose values for CGM
summary metrics. Defaults to |
sensor_wear_ndays |
Number of days for fixed-window
|
A list containing:
subject_summary: One row per subject. CGM summary metric columns
are calculated on the original raw glucose values by default; set
summary_metrics_source = "preprocessed" to use the
event-preprocessed glucose grid. Event summaries are included as wide
*_total_episodes columns only.
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, and
avg_minutes_below_54_per_episode.
interpolated_data: Included when
return_interpolated = TRUE, with columns id, time,
and gl.
subject_summary includes:
id: Subject identifier
TIR: Percent of glucose readings in range 70-180 mg/dL
TITR: Percent of glucose readings in tight range 70-140 mg/dL
TBR70: Percent of glucose readings below 70 mg/dL
TBR54: Percent of glucose readings below 54 mg/dL
TAR180: Percent of glucose readings above 180 mg/dL
TAR250: Percent of glucose readings above 250 mg/dL
CV: Coefficient of variation in percent,
SD: Sample standard deviation of glucose, mg/dL
mean_glucose: Mean glucose, mg/dL
GMI: Glucose Management Indicator,
uGMI: Unitless GMI-style metric,
GRI: Glycemia Risk Index,
, where
VLow is percent time 54 mg/dL, Low is 54-70
mg/dL, VHigh is 250 mg/dL, and High is
180-250 mg/dL
sensor_wear_percent: Percent of expected CGM readings observed,
calculated from the original timestamps using the same automatic range
method as iglu::active_percent() by default. If
sensor_wear_ndays is supplied, this is calculated over the last
N days for each subject.
hypo_lv1_total_episodes: Number of Level 1 hypoglycemia
episodes
hypo_lv2_total_episodes: Number of Level 2 hypoglycemia
episodes
hypo_extended_total_episodes: Number of extended
hypoglycemia episodes
hypo_lv1_excl_total_episodes: Number of Level 1
hypoglycemia episodes that do not overlap a Level 2 episode
hyper_lv1_total_episodes: Number of Level 1 hyperglycemia
episodes
hyper_lv2_total_episodes: Number of Level 2 hyperglycemia
episodes
hyper_extended_total_episodes: Number of extended
hyperglycemia episodes
hyper_lv1_excl_total_episodes: Number of Level 1
hyperglycemia episodes that do not overlap a Level 2 episode
glycemic_event_summary includes:
id: Subject identifier
type: Event direction, either "hypo" or
"hyper"
level: Event level, one of "lv1", "lv2",
"extended", or "lv1_excl"
total_episodes: Number of episodes for the subject, event
direction, and event level
avg_ep_per_day: Average episodes per day for the subject,
event direction, and event level, rounded to two decimals
avg_minutes_below_54_per_episode: For hypoglycemia rows,
average minutes below 54 mg/dL per episode, rounded to two decimals; for
hyperglycemia rows, 0
- Hypoglycemia: lv1 ( 70 mg/dL, 15 min), lv2 ( 54 mg/dL, 15 min), extended ( 70 mg/dL, 120 min).
- Hyperglycemia: lv1 ( 180 mg/dL, 15 min), lv2 ( 250 mg/dL, 15 min), extended ( 250 mg/dL, 90 min in 120 min, end 180 mg/dL for 15 min).
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.
detect_hyperglycemic_events, detect_hypoglycemic_events
# 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) print(all_outputs$glycemic_event_summary) # Detect all events on larger dataset large_outputs <- detect_all_events(example_data_hall) print(paste("Total subjects analyzed:", nrow(large_outputs$subject_summary)))# 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) print(all_outputs$glycemic_event_summary) # Detect all events on larger dataset large_outputs <- detect_all_events(example_data_hall) print(paste("Total subjects analyzed:", nrow(large_outputs$subject_summary)))
Identifies and analyzes events occurring between detected maxima points, providing detailed episode information for GRID analysis. This function helps characterize the glucose dynamics between identified peaks.
detect_between_maxima(df, transform_df)detect_between_maxima(df, transform_df)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
transform_df |
A dataframe containing summary information from previous transformations |
A list containing:
results: Tibble with events between maxima (id, grid_time, grid_gl, maxima_time, maxima_glucose, time_to_peak)
episode_counts: Tibble with episode counts per subject (id, episode_counts)
grid, mod_grid, find_new_maxima, transform_df
Other GRID pipeline:
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Complete pipeline to get transform_df grid_result <- grid(example_data_5_subject, gap = 60, threshold = 130) maxima_result <- find_local_maxima(example_data_5_subject) mod_result <- mod_grid(example_data_5_subject, grid_result$grid_vector, hours = 2, gap = 60) max_after <- find_max_after_hours(example_data_5_subject, mod_result$mod_grid_vector, hours = 2) new_maxima <- find_new_maxima(example_data_5_subject, max_after$max_index, maxima_result$local_maxima_vector) transformed <- transform_df(grid_result$episode_start, new_maxima) # Detect events between maxima between_events <- detect_between_maxima(example_data_5_subject, transformed) print(paste("Events between maxima:", length(between_events))) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 60, threshold = 130) large_maxima <- find_local_maxima(example_data_hall) large_mod <- mod_grid(example_data_hall, large_grid$grid_vector, hours = 2, gap = 60) large_max_after <- find_max_after_hours(example_data_hall, large_mod$mod_grid_vector, hours = 2) large_new_maxima <- find_new_maxima(example_data_hall, large_max_after$max_index, large_maxima$local_maxima_vector) large_transformed <- transform_df(large_grid$episode_start, large_new_maxima) large_between <- detect_between_maxima(example_data_hall, large_transformed) print(paste("Events between maxima in larger dataset:", length(large_between)))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Complete pipeline to get transform_df grid_result <- grid(example_data_5_subject, gap = 60, threshold = 130) maxima_result <- find_local_maxima(example_data_5_subject) mod_result <- mod_grid(example_data_5_subject, grid_result$grid_vector, hours = 2, gap = 60) max_after <- find_max_after_hours(example_data_5_subject, mod_result$mod_grid_vector, hours = 2) new_maxima <- find_new_maxima(example_data_5_subject, max_after$max_index, maxima_result$local_maxima_vector) transformed <- transform_df(grid_result$episode_start, new_maxima) # Detect events between maxima between_events <- detect_between_maxima(example_data_5_subject, transformed) print(paste("Events between maxima:", length(between_events))) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 60, threshold = 130) large_maxima <- find_local_maxima(example_data_hall) large_mod <- mod_grid(example_data_hall, large_grid$grid_vector, hours = 2, gap = 60) large_max_after <- find_max_after_hours(example_data_hall, large_mod$mod_grid_vector, hours = 2) large_new_maxima <- find_new_maxima(example_data_hall, large_max_after$max_index, large_maxima$local_maxima_vector) large_transformed <- transform_df(large_grid$episode_start, large_new_maxima) large_between <- detect_between_maxima(example_data_hall, large_transformed) print(paste("Events between maxima in larger dataset:", length(large_between)))
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: 15 consecutive min of 180 mg/dL, ends with 15 consecutive min 180 mg/dL
Level 2: 15 consecutive min of 250 mg/dL, ends with 15 consecutive min 250 mg/dL
Extended: 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
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.
detect_hyperglycemic_events(df, ..., type = "extended", reading_minutes = NULL, sort_time = FALSE, inter_gap = 45, return_interpolated = TRUE)detect_hyperglycemic_events(df, ..., type = "extended", reading_minutes = NULL, sort_time = FALSE, inter_gap = 45, return_interpolated = TRUE)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
... |
Custom event criteria supplied by name. Prefer
|
type |
Hyperglycemia event definition. One of |
reading_minutes |
Time interval between readings in minutes (optional).
If omitted or |
sort_time |
Logical. If |
inter_gap |
Maximum gap in minutes to interpolate across. Defaults to 45; larger gaps split event-detection segments. |
return_interpolated |
Logical. If |
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_index and end_index are 1-based row positions in the internal interpolated event grid, returned as interpolated_data when return_interpolated = TRUE.
interpolated_data: Included when
return_interpolated = TRUE, with columns id, time,
and gl.
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" uses start_gl = 180, dur_length = 15,
end_length = 15, and end_gl = 180.
type = "lv2" uses start_gl = 250, dur_length = 15,
end_length = 15, and end_gl = 250.
type = "extended" uses start_gl = 250,
dur_length = 120, end_length = 15, and end_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.
- 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.
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.
# 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) # 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) # 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) # 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") cat("Level 2 episodes:", sum(hyper_lv2$events_total$total_episodes), "\n") cat("Extended episodes:", sum(hyper_extended$events_total$total_episodes), "\n") # Analysis on larger dataset with Level 1 criteria large_hyper <- detect_hyperglycemic_events(example_data_hall, type = "lv1") print(large_hyper$events_total) # 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) # Analysis on larger dataset with Extended criteria large_hyper_extended <- detect_hyperglycemic_events(example_data_hall, type = "extended") print(large_hyper_extended$events_total) # 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) }# 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) # 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) # 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) # 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") cat("Level 2 episodes:", sum(hyper_lv2$events_total$total_episodes), "\n") cat("Extended episodes:", sum(hyper_extended$events_total$total_episodes), "\n") # Analysis on larger dataset with Level 1 criteria large_hyper <- detect_hyperglycemic_events(example_data_hall, type = "lv1") print(large_hyper$events_total) # 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) # Analysis on larger dataset with Extended criteria large_hyper_extended <- detect_hyperglycemic_events(example_data_hall, type = "extended") print(large_hyper_extended$events_total) # 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) }
Identifies and segments hypoglycemic 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: 15 consecutive min of 70 mg/dL, ends with 15 consecutive min 70 mg/dL
Level 2: 15 consecutive min of 54 mg/dL, ends with 15 consecutive min 54 mg/dL
Extended: 120 consecutive min of 70 mg/dL, ends with 15 consecutive min 70 mg/dL
Events are counted only after glucose remains at or above the recovery
threshold for the specified end length. In events_detailed,
end_time, end_glucose, and end_index report the last
hypoglycemic reading immediately before that confirmed recovery period starts.
detect_hypoglycemic_events(df, ..., type = "extended", reading_minutes = NULL, sort_time = FALSE, inter_gap = 45, return_interpolated = TRUE)detect_hypoglycemic_events(df, ..., type = "extended", reading_minutes = NULL, sort_time = FALSE, inter_gap = 45, return_interpolated = TRUE)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
... |
Custom event criteria supplied by name. Prefer
|
type |
Hypoglycemia event definition. One of |
reading_minutes |
Time interval between readings in minutes (optional).
If omitted or |
sort_time |
Logical. If |
inter_gap |
Maximum gap in minutes to interpolate across. Defaults to 45; larger gaps split event-detection segments. |
return_interpolated |
Logical. If |
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, duration_below_54_minutes). End fields report the last dysglycemic reading before confirmed recovery starts. start_index and end_index are 1-based row positions in the internal interpolated event grid, returned as interpolated_data when return_interpolated = TRUE.
interpolated_data: Included when
return_interpolated = TRUE, with columns id, time,
and gl.
Hypoglycemic 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
hypoglycemia definitions without manually entering thresholds.
type = "lv1" uses start_gl = 70,
dur_length = 15, and end_length = 15.
type = "lv2" uses start_gl = 54,
dur_length = 15, and end_length = 15.
type = "extended" uses start_gl = 70,
dur_length = 120, and end_length = 15.
type = "lv1_excl" returns Level 1 episodes that do not
overlap Level 2 episodes.
2. Custom criteria method:
Supply start_gl, dur_length, and end_length directly
when using a custom definition, for example
detect_hypoglycemic_events(df, start_gl = 70, dur_length = 15,
end_length = 15) for Level 1 hypoglycemia. 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.
- 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.
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.
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Level 1 Hypoglycemia Event (>=15 consecutive min of <70 mg/dL and event # ends when there is >=15 consecutive min with a CGM sensor value of >=70 mg/dL) hypo_lv1 <- detect_hypoglycemic_events(example_data_5_subject, type = "lv1") print(hypo_lv1$events_total) # Level 2 Hypoglycemia Event (>=15 consecutive min of <54 mg/dL and event # ends when there is >=15 consecutive min with a CGM sensor value of >=54 mg/dL) hypo_lv2 <- detect_hypoglycemic_events(example_data_5_subject, type = "lv2") # Extended Hypoglycemia Event (>120 consecutive min of <70 mg/dL and event # ends when there is >=15 consecutive min with a CGM sensor value of >=70 mg/dL) hypo_extended <- detect_hypoglycemic_events(example_data_5_subject, type = "extended") print(hypo_extended$events_total) # Custom criteria method for the same standard definitions hypo_lv1_custom <- detect_hypoglycemic_events( example_data_5_subject, start_gl = 70, dur_length = 15, end_length = 15 ) hypo_lv2_custom <- detect_hypoglycemic_events( example_data_5_subject, start_gl = 54, dur_length = 15, end_length = 15 ) hypo_extended_custom <- detect_hypoglycemic_events( example_data_5_subject, start_gl = 70, dur_length = 120, end_length = 15 ) # Compare event rates across levels cat("Level 1 episodes:", sum(hypo_lv1$events_total$total_episodes), "\n") cat("Level 2 episodes:", sum(hypo_lv2$events_total$total_episodes), "\n") cat("Extended episodes:", sum(hypo_extended$events_total$total_episodes), "\n") # Analysis on larger dataset with Level 1 criteria large_hypo <- detect_hypoglycemic_events(example_data_hall, type = "lv1") print(large_hypo$events_total) # Analysis on larger dataset with Level 2 criteria large_hypo_lv2 <- detect_hypoglycemic_events(example_data_hall, type = "lv2") print(large_hypo_lv2$events_total) # Analysis on larger dataset with Extended criteria large_hypo_extended <- detect_hypoglycemic_events(example_data_hall, type = "extended") print(large_hypo_extended$events_total)# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Level 1 Hypoglycemia Event (>=15 consecutive min of <70 mg/dL and event # ends when there is >=15 consecutive min with a CGM sensor value of >=70 mg/dL) hypo_lv1 <- detect_hypoglycemic_events(example_data_5_subject, type = "lv1") print(hypo_lv1$events_total) # Level 2 Hypoglycemia Event (>=15 consecutive min of <54 mg/dL and event # ends when there is >=15 consecutive min with a CGM sensor value of >=54 mg/dL) hypo_lv2 <- detect_hypoglycemic_events(example_data_5_subject, type = "lv2") # Extended Hypoglycemia Event (>120 consecutive min of <70 mg/dL and event # ends when there is >=15 consecutive min with a CGM sensor value of >=70 mg/dL) hypo_extended <- detect_hypoglycemic_events(example_data_5_subject, type = "extended") print(hypo_extended$events_total) # Custom criteria method for the same standard definitions hypo_lv1_custom <- detect_hypoglycemic_events( example_data_5_subject, start_gl = 70, dur_length = 15, end_length = 15 ) hypo_lv2_custom <- detect_hypoglycemic_events( example_data_5_subject, start_gl = 54, dur_length = 15, end_length = 15 ) hypo_extended_custom <- detect_hypoglycemic_events( example_data_5_subject, start_gl = 70, dur_length = 120, end_length = 15 ) # Compare event rates across levels cat("Level 1 episodes:", sum(hypo_lv1$events_total$total_episodes), "\n") cat("Level 2 episodes:", sum(hypo_lv2$events_total$total_episodes), "\n") cat("Extended episodes:", sum(hypo_extended$events_total$total_episodes), "\n") # Analysis on larger dataset with Level 1 criteria large_hypo <- detect_hypoglycemic_events(example_data_hall, type = "lv1") print(large_hypo$events_total) # Analysis on larger dataset with Level 2 criteria large_hypo_lv2 <- detect_hypoglycemic_events(example_data_hall, type = "lv2") print(large_hypo_lv2$events_total) # Analysis on larger dataset with Extended criteria large_hypo_extended <- detect_hypoglycemic_events(example_data_hall, type = "extended") print(large_hypo_extended$events_total)
Calculates glucose excursions in CGM data. An excursion is defined as
a 70 mg/dL ( 3.9 mmol/L) rise within 2 hours, not preceded by a value
70 mg/dL ( 3.9 mmol/L).
excursion(df, gap = 15)excursion(df, gap = 15)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
gap |
Gap threshold in minutes for excursion calculation (default: 15). This parameter defines the minimum time interval between consecutive GRID events. |
A list containing:
excursion_vector: Tibble with excursion results (excursion)
episode_counts: Tibble with episode counts per subject (id, episode_counts)
episode_start: Tibble with all episode starts with columns:
id: Subject identifier
time: Timestamp at which the event occurs; equivalent to df$time[index]
gl: Glucose value at the event; equivalent to df$gl[index]
index: R-based (1-indexed) row number(s) in df denoting where the event occurs
- gap is minutes; change to enforce minimum separation between excursions.
- This function operates on the rows supplied in df. It does not use
interpolate_cgm or the full-day event preprocessing grid.
Edwards, S., et al. (2022). Use of connected pen as a diagnostic tool to evaluate missed bolus dosing behavior in people with type 1 and type 2 diabetes. Diabetes Technology & Therapeutics, 24(1), 61-66.
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Calculate glucose excursions excursion_result <- excursion(example_data_5_subject, gap = 15) print(paste("Excursion vector length:", length(excursion_result$excursion_vector))) print(excursion_result$episode_counts) # Excursion analysis with different gap excursion_30min <- excursion(example_data_5_subject, gap = 30) # Analysis on larger dataset large_excursion <- excursion(example_data_hall, gap = 15) print(paste("Excursion vector length in larger dataset:", length(large_excursion$excursion_vector))) print(paste("Total episodes:", sum(large_excursion$episode_counts$episode_counts)))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Calculate glucose excursions excursion_result <- excursion(example_data_5_subject, gap = 15) print(paste("Excursion vector length:", length(excursion_result$excursion_vector))) print(excursion_result$episode_counts) # Excursion analysis with different gap excursion_30min <- excursion(example_data_5_subject, gap = 30) # Analysis on larger dataset large_excursion <- excursion(example_data_hall, gap = 15) print(paste("Excursion vector length in larger dataset:", length(large_excursion$excursion_vector))) print(paste("Total episodes:", sum(large_excursion$episode_counts$episode_counts)))
Identifies local maxima (peaks) in glucose concentration time series data. Uses a difference-based algorithm to detect peaks where glucose values increase or remain constant for two consecutive points before the peak point, and decrease or remain constant for two consecutive points after the peak point.
find_local_maxima(df)find_local_maxima(df)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
A list containing:
local_maxima_vector: Tibble with R-based (1-indexed) row numbers of local maxima (local_maxima). The corresponding occurrence time is df$time[local_maxima] and glucose is df$gl[local_maxima].
merged_results: Tibble with local maxima details (id, time, gl)
grid, mod_grid, find_new_maxima
Other GRID pipeline:
detect_between_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Find local maxima maxima_result <- find_local_maxima(example_data_5_subject) print(paste("Found", nrow(maxima_result$local_maxima_vector), "local maxima")) # Find maxima on larger dataset large_maxima <- find_local_maxima(example_data_hall) print(paste("Found", nrow(large_maxima$local_maxima_vector), "local maxima in larger dataset")) # View first few maxima head(maxima_result$local_maxima_vector) # View merged results head(maxima_result$merged_results)# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Find local maxima maxima_result <- find_local_maxima(example_data_5_subject) print(paste("Found", nrow(maxima_result$local_maxima_vector), "local maxima")) # Find maxima on larger dataset large_maxima <- find_local_maxima(example_data_hall) print(paste("Found", nrow(large_maxima$local_maxima_vector), "local maxima in larger dataset")) # View first few maxima head(maxima_result$local_maxima_vector) # View merged results head(maxima_result$merged_results)
Identifies the maximum glucose value occurring within a specified time window after a given start point. This function is useful for analyzing glucose patterns following specific events or time points.
find_max_after_hours(df, start_point_df, hours)find_max_after_hours(df, start_point_df, hours)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
start_point_df |
A dataframe with column |
hours |
Number of hours to look ahead from the start point |
A list containing:
max_index: Tibble with R-based (1-indexed) row numbers of maximum glucose (max_index).
The corresponding occurrence time is df$time[max_index] and glucose is df$gl[max_index].
episode_counts: Tibble with episode counts per subject (id, episode_counts)
episode_start: Tibble with all episode starts with columns:
id: Subject identifier
time: Timestamp at which the maximum occurs; equivalent to df$time[index]
gl: Glucose value at the maximum; equivalent to df$gl[index]
index: R-based (1-indexed) row number(s) in df denoting where the maximum occurs
- start_index must be valid row numbers in df (1-indexed).
- The search window is (0, hours] hours after each start index.
mod_grid, find_local_maxima, find_new_maxima, transform_df
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find maximum glucose in next 2 hours max_after <- find_max_after_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(max_after$max_index), "maximum points")) # Find maximum glucose in next 1 hour max_after_1h <- find_max_after_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_max_after <- find_max_after_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_max_after$max_index), "maximum points in larger dataset"))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find maximum glucose in next 2 hours max_after <- find_max_after_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(max_after$max_index), "maximum points")) # Find maximum glucose in next 1 hour max_after_1h <- find_max_after_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_max_after <- find_max_after_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_max_after$max_index), "maximum points in larger dataset"))
Identifies the maximum glucose value occurring within a specified time window before a given start point. This function is useful for analyzing glucose patterns preceding specific events or time points.
find_max_before_hours(df, start_point_df, hours)find_max_before_hours(df, start_point_df, hours)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
start_point_df |
A dataframe with column |
hours |
Number of hours to look back from the start point |
A list containing:
max_index: Tibble with R-based (1-indexed) row numbers of maximum glucose (max_index).
The corresponding occurrence time is df$time[max_index] and glucose is df$gl[max_index].
episode_counts: Tibble with episode counts per subject (id, episode_counts)
episode_start: Tibble with all episode starts with columns:
id: Subject identifier
time: Timestamp at which the maximum occurs; equivalent to df$time[index]
gl: Glucose value at the maximum; equivalent to df$gl[index]
index: R-based (1-indexed) row number(s) in df denoting where the maximum occurs
- The search window is [hours, 0) hours before each start index.
mod_grid, find_local_maxima, find_new_maxima
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find maximum glucose in previous 2 hours max_before <- find_max_before_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(max_before$max_index), "maximum points")) # Find maximum glucose in previous 1 hour max_before_1h <- find_max_before_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_max_before <- find_max_before_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_max_before$max_index), "maximum points in larger dataset"))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find maximum glucose in previous 2 hours max_before <- find_max_before_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(max_before$max_index), "maximum points")) # Find maximum glucose in previous 1 hour max_before_1h <- find_max_before_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_max_before <- find_max_before_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_max_before$max_index), "maximum points in larger dataset"))
Identifies the minimum glucose value occurring within a specified time window after a given start point. This function is useful for analyzing glucose patterns following specific events or time points.
find_min_after_hours(df, start_point_df, hours)find_min_after_hours(df, start_point_df, hours)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
start_point_df |
A dataframe with column |
hours |
Number of hours to look ahead from the start point |
A list containing:
min_index: Tibble with R-based (1-indexed) row numbers of minimum glucose (min_index). The corresponding occurrence time is df$time[min_index] and glucose is df$gl[min_index].
episode_counts: Tibble with episode counts per subject (id, episode_counts)
episode_start: Tibble with all episode starts with columns:
id: Subject identifier
time: Timestamp at which the minimum occurs; equivalent to df$time[index]
gl: Glucose value at the minimum; equivalent to df$gl[index]
index: R-based (1-indexed) row number(s) in df denoting where the minimum occurs
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find minimum glucose in next 2 hours min_after <- find_min_after_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(min_after$min_index), "minimum points")) # Find minimum glucose in next 1 hour min_after_1h <- find_min_after_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_min_after <- find_min_after_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_min_after$min_index), "minimum points in larger dataset"))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find minimum glucose in next 2 hours min_after <- find_min_after_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(min_after$min_index), "minimum points")) # Find minimum glucose in next 1 hour min_after_1h <- find_min_after_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_min_after <- find_min_after_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_min_after$min_index), "minimum points in larger dataset"))
Identifies the minimum glucose value occurring within a specified time window before a given start point. This function is useful for analyzing glucose patterns preceding specific events or time points.
find_min_before_hours(df, start_point_df, hours)find_min_before_hours(df, start_point_df, hours)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
start_point_df |
A dataframe with column |
hours |
Number of hours to look back from the start point |
A list containing:
min_index: Tibble with R-based (1-indexed) row numbers of minimum glucose (min_index). The corresponding occurrence time is df$time[min_index] and glucose is df$gl[min_index].
episode_counts: Tibble with episode counts per subject (id, episode_counts)
episode_start: Tibble with all episode starts with columns:
id: Subject identifier
time: Timestamp at which the minimum occurs; equivalent to df$time[index]
gl: Glucose value at the minimum; equivalent to df$gl[index]
index: R-based (1-indexed) row number(s) in df denoting where the minimum occurs
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find minimum glucose in previous 2 hours min_before <- find_min_before_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(min_before$min_index), "minimum points")) # Find minimum glucose in previous 1 hour min_before_1h <- find_min_before_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_min_before <- find_min_before_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_min_before$min_index), "minimum points in larger dataset"))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create start points for demonstration (using row index) start_index <- seq(1, nrow(example_data_5_subject), by = 100) start_points <- data.frame(start_index = start_index) # Find minimum glucose in previous 2 hours min_before <- find_min_before_hours(example_data_5_subject, start_points, hours = 2) print(paste("Found", length(min_before$min_index), "minimum points")) # Find minimum glucose in previous 1 hour min_before_1h <- find_min_before_hours(example_data_5_subject, start_points, hours = 1) # Analysis on larger dataset large_start_index <- seq(1, nrow(example_data_hall), by = 200) large_start_points <- data.frame(start_index = large_start_index) large_min_before <- find_min_before_hours(example_data_hall, large_start_points, hours = 2) print(paste("Found", length(large_min_before$min_index), "minimum points in larger dataset"))
Identifies new maxima in the vicinity of previously identified grid points, useful for refining maxima detection in GRID analysis. This function helps improve the accuracy of peak detection by searching around known event points.
find_new_maxima(df, mod_grid_max_point_df, local_maxima_df)find_new_maxima(df, mod_grid_max_point_df, local_maxima_df)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
mod_grid_max_point_df |
A dataframe with column |
local_maxima_df |
A dataframe with column |
A tibble with updated maxima information containing columns (id, time, gl, index)
The index column contains R-based (1-indexed) row number(s) in df; thus, time == df$time[index] and gl == df$gl[index].
find_local_maxima, find_max_after_hours, transform_df
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
grid(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # First, get grid points and local maxima grid_result <- grid(example_data_5_subject, gap = 15, threshold = 130) maxima_result <- find_local_maxima(example_data_5_subject) # Create modified grid points (simplified for example) mod_grid_indices <- data.frame(index = grid_result$episode_start$index[1:10]) # Find new maxima around grid points new_maxima <- find_new_maxima(example_data_5_subject, mod_grid_indices, maxima_result$local_maxima_vector) print(paste("Found", nrow(new_maxima), "new maxima")) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 15, threshold = 130) large_maxima <- find_local_maxima(example_data_hall) large_mod_grid <- data.frame(index = large_grid$episode_start$index[1:20]) large_new_maxima <- find_new_maxima(example_data_hall, large_mod_grid, large_maxima$local_maxima_vector) print(paste("Found", nrow(large_new_maxima), "new maxima in larger dataset"))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # First, get grid points and local maxima grid_result <- grid(example_data_5_subject, gap = 15, threshold = 130) maxima_result <- find_local_maxima(example_data_5_subject) # Create modified grid points (simplified for example) mod_grid_indices <- data.frame(index = grid_result$episode_start$index[1:10]) # Find new maxima around grid points new_maxima <- find_new_maxima(example_data_5_subject, mod_grid_indices, maxima_result$local_maxima_vector) print(paste("Found", nrow(new_maxima), "new maxima")) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 15, threshold = 130) large_maxima <- find_local_maxima(example_data_hall) large_mod_grid <- data.frame(index = large_grid$episode_start$index[1:20]) large_new_maxima <- find_new_maxima(example_data_hall, large_mod_grid, large_maxima$local_maxima_vector) print(paste("Found", nrow(large_new_maxima), "new maxima in larger dataset"))
Implements the GRID (Glucose Rate Increase Detector) algorithm for detecting rapid glucose rate increases in continuous glucose monitoring (CGM) data.
This algorithm identifies rapid glucose changes using specific rate-based criteria, and is commonly applied for meal detection.
Meals are detected when the CGM value is 7.2 mmol/L ( 130 mg/dL) and the rate-of-change is 5.3 mmol/L/h [ 95 mg/dL/h] for the last two consecutive readings, or 5.0 mmol/L/h [ 90 mg/dL/h] for two of the last three readings.
grid(df, gap = 15, threshold = 130)grid(df, gap = 15, threshold = 130)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
gap |
Gap threshold in minutes for event detection (default: 15). This parameter defines the minimum time interval between consecutive GRID events. For example, if gap is set to 60, only one GRID event can be detected within any one-hour window; subsequent events within the gap interval are not counted as new events. |
threshold |
GRID slope threshold in mg/dL/hour for event classification (default: 130) |
A list containing:
grid_vector: A tibble with the results of the GRID analysis. Contains a grid column (0/1 values; 1 denotes a detected GRID event) and all relevant input columns.
episode_counts: A tibble summarizing the number of GRID events per subject (id) as episode_counts.
episode_start: A tibble listing the start of each GRID episode, with columns:
id: Subject ID.
time: The timestamp (POSIXct) at which the GRID event was detected.
gl: The glucose value (mg/dL; integer or numeric) at the GRID event.
index: R-based (1-indexed) row number(s) in the original dataframe where the GRID event occurs. The occurrence time equals df$time[index] and glucose equals df$gl[index].
- Flags points where gl >= 130 mg/dL and rate-of-change meets the GRID criteria (see references).
- Enforces a minimum gap in minutes between detected events to avoid duplicates.
- gl is mg/dL; time is POSIXct; gap is minutes.
- The effective sampling interval is derived from time deltas.
- This function operates on the rows supplied in df. It does not use
interpolate_cgm or the full-day event preprocessing grid.
Harvey, R. A., et al. (2014). Design of the glucose rate increase detector: a meal detection module for the health monitoring system. Journal of Diabetes Science and Technology, 8(2), 307-320.
Adolfsson, Peter, et al. "Increased time in range and fewer missed bolus injections after introduction of a smart connected insulin pen." Diabetes technology & therapeutics 22.10 (2020): 709-718.
mod_grid, maxima_grid, find_local_maxima, detect_between_maxima
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
maxima_grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Basic GRID analysis on smaller dataset grid_result <- grid(example_data_5_subject, gap = 15, threshold = 130) print(grid_result$episode_counts) print(grid_result$episode_start) print(grid_result$grid_vector) # More sensitive GRID analysis sensitive_result <- grid(example_data_5_subject, gap = 10, threshold = 120) # GRID analysis on larger dataset large_grid <- grid(example_data_hall, gap = 15, threshold = 130) print(paste("Detected", sum(large_grid$episode_counts$episode_counts), "episodes")) print(large_grid$episode_start) print(large_grid$grid_vector)# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Basic GRID analysis on smaller dataset grid_result <- grid(example_data_5_subject, gap = 15, threshold = 130) print(grid_result$episode_counts) print(grid_result$episode_start) print(grid_result$grid_vector) # More sensitive GRID analysis sensitive_result <- grid(example_data_5_subject, gap = 10, threshold = 120) # GRID analysis on larger dataset large_grid <- grid(example_data_hall, gap = 15, threshold = 130) print(paste("Detected", sum(large_grid$episode_counts$episode_counts), "episodes")) print(large_grid$episode_start) print(large_grid$grid_vector)
Interpolates continuous glucose monitoring (CGM) data onto the same
iglu-compatible, midnight-aligned full-day grid used internally by
cgmguru's event-detection functions. The interpolation is implemented in C++
and is intended for users who want to inspect or reuse the preprocessed grid
behind detect_all_events,
detect_hyperglycemic_events, and
detect_hypoglycemic_events.
For each subject, interpolate_cgm() builds an equally spaced grid at
reading_minutes intervals. If reading_minutes is omitted, it is
inferred per subject from the median positive timestamp difference. Glucose
values are linearly interpolated only across gaps up to inter_gap;
larger gaps are treated as missing and removed from the returned data,
preserving segment boundaries used by event calculation.
The GRID-family functions grid, maxima_grid, and
excursion do not call this helper automatically; they operate
on the rows supplied by the user unless the caller explicitly passes an
interpolated dataset.
interpolate_cgm(df, reading_minutes = NULL, sort_time = FALSE, inter_gap = 45)interpolate_cgm(df, reading_minutes = NULL, sort_time = FALSE, inter_gap = 45)
df |
A dataframe containing CGM data with columns:
|
reading_minutes |
Time interval for the interpolation grid in minutes.
If omitted or |
sort_time |
Logical. If |
inter_gap |
Maximum gap in minutes to interpolate across. Defaults to 45; larger gaps split the event-detection grid. |
A tibble with columns id, interpolated time, and
interpolated gl. Rows inside gaps larger than inter_gap are
omitted.
detect_all_events, detect_hyperglycemic_events, detect_hypoglycemic_events
df <- data.frame( id = "A", time = as.POSIXct(c("2026-01-01 00:15:00", "2026-01-01 00:25:00"), tz = "UTC"), gl = c(100, 120) ) interpolate_cgm(df)df <- data.frame( id = "A", time = as.POSIXct(c("2026-01-01 00:15:00", "2026-01-01 00:25:00"), tz = "UTC"), gl = c(100, 120) ) interpolate_cgm(df)
Fast method for postprandial glucose peak detection combining GRID algorithm with local maxima analysis. Detects meal-induced glucose peaks by identifying GRID events (rapid glucose increases) and mapping them to corresponding local maxima within a search window. Local maxima are defined as points where glucose values increase or remain constant for two consecutive points before the peak, and decrease or remain constant for two consecutive points after the peak.
The 7-step algorithm: (1) finds GRID points indicating meal starts (2) identifies modified GRID points after minimum duration (3) locates maximum glucose within the subsequent time window (4) detects all local maxima using the two-consecutive-point criteria (5) refines peaks from local maxima candidates (6) maps GRID points to peaks within 4-hour constraint (7) redistributes overlapping peaks.
maxima_grid(df, threshold = 130, gap = 60, hours = 2)maxima_grid(df, threshold = 130, gap = 60, hours = 2)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
threshold |
GRID slope threshold in mg/dL/hour for event classification (default: 130) |
gap |
Gap threshold in minutes for event detection (default: 60). This parameter defines the minimum time interval between consecutive GRID events. |
hours |
Time window in hours for maxima analysis (default: 2) |
A list containing:
results: Tibble with combined maxima and GRID analysis results, with columns:
id: Subject identifier
grid_time: Timestamp of GRID event detection (POSIXct)
grid_gl: Glucose value at GRID event (mg/dL)
maxima_time: Timestamp of peak glucose (POSIXct)
maxima_glucose: Peak glucose value (mg/dL)
time_to_peak_min: Time from GRID event to peak in minutes
grid_index: R-based (1-indexed) row number of GRID event; grid_time == df$time[grid_index], grid_gl == df$gl[grid_index]
maxima_index: R-based (1-indexed) row number of peak; maxima_time == df$time[maxima_index], maxima_glucose == df$gl[maxima_index]
episode_counts: Tibble with episode counts per subject (id, episode_counts)
1) GRID -> 2) modified GRID -> 3) window maxima -> 4) local maxima -> 5) refine peaks ->
6) map GRID to peaks ( 4h) -> 7) redistribute overlapping peaks.
This function operates on the rows supplied in df. It does not use
interpolate_cgm or the full-day event preprocessing grid unless
the caller explicitly supplies interpolated data.
Park, Sang Ho, et al. "Identification of clinically meaningful automatically detected postprandial glucose excursions in individuals with type 1 diabetes using personal continuous glucose monitoring." Diabetes Research and Clinical Practice (2025): 112951.
Park, Soojin, et al. "High-Amplitude and Prolonged Glucose Excursions as a Key Determinant of Discordance Between Glucose Management Indicator and Glycated Hemoglobin in Type 1 Diabetes." Diabetes Care (2026): dc252820. https://doi.org/10.2337/dc25-2820
grid, mod_grid, find_local_maxima, find_new_maxima, transform_df
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
mod_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Combined analysis on smaller dataset maxima_result <- maxima_grid(example_data_5_subject, threshold = 130, gap = 60, hours = 2) print(maxima_result$episode_counts) print(maxima_result$results) # More sensitive analysis sensitive_maxima <- maxima_grid(example_data_5_subject, threshold = 120, gap = 30, hours = 1) print(sensitive_maxima$episode_counts) print(sensitive_maxima$results) # Analysis on larger dataset large_maxima <- maxima_grid(example_data_hall, threshold = 130, gap = 60, hours = 2) print(large_maxima$episode_counts) print(large_maxima$results)# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Combined analysis on smaller dataset maxima_result <- maxima_grid(example_data_5_subject, threshold = 130, gap = 60, hours = 2) print(maxima_result$episode_counts) print(maxima_result$results) # More sensitive analysis sensitive_maxima <- maxima_grid(example_data_5_subject, threshold = 120, gap = 30, hours = 1) print(sensitive_maxima$episode_counts) print(sensitive_maxima$results) # Analysis on larger dataset large_maxima <- maxima_grid(example_data_hall, threshold = 130, gap = 60, hours = 2) print(large_maxima$episode_counts) print(large_maxima$results)
Constructs a modified GRID series by reapplying the GRID logic with a designated gap (e.g., 60 minutes) and analysis window in hours (e.g., 2 hours). It reassigns GRID events under these constraints to produce a modified grid suitable for downstream maxima mapping and episode analysis.
mod_grid(df, grid_point_df, hours = 2, gap = 15)mod_grid(df, grid_point_df, hours = 2, gap = 15)
df |
A dataframe containing continuous glucose monitoring (CGM) data. Must include columns:
|
grid_point_df |
A dataframe with column |
hours |
Time window in hours for analysis (default: 2) |
gap |
Gap threshold in minutes for event detection (default: 15). This parameter defines the minimum time interval between consecutive GRID events. |
A list containing:
mod_grid_vector: Tibble with modified GRID results (mod_grid)
episode_counts: Tibble with episode counts per subject (id, episode_counts)
episode_start: Tibble with all episode starts with columns:
id: Subject identifier
time: Timestamp at which the event occurs; equivalent to df$time[index]
gl: Glucose value at the event; equivalent to df$gl[index]
index: R-based (1-indexed) row number(s) in df denoting where the event occurs
- gap is minutes; hours is hours; time is POSIXct.
Park, Sang Ho, et al. "Identification of clinically meaningful automatically detected postprandial glucose excursions in individuals with type 1 diabetes using personal continuous glucose monitoring." Diabetes Research and Clinical Practice (2025): 112951.
Park, Soojin, et al. "High-Amplitude and Prolonged Glucose Excursions as a Key Determinant of Discordance Between Glucose Management Indicator and Glycated Hemoglobin in Type 1 Diabetes." Diabetes Care (2026): dc252820. https://doi.org/10.2337/dc25-2820
grid, find_max_after_hours, find_new_maxima
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
start_finder(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # First, get grid points grid_result <- grid(example_data_5_subject, gap = 60, threshold = 130) # Perform modified GRID analysis mod_result <- mod_grid(example_data_5_subject, grid_result$grid_vector, hours = 2, gap = 60) print(paste("Modified grid points:", nrow(mod_result$mod_grid_vector))) # Modified analysis with different parameters mod_result_1h <- mod_grid(example_data_5_subject, grid_result$grid_vector, hours = 1, gap = 40) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 60, threshold = 130) large_mod_result <- mod_grid(example_data_hall, large_grid$grid_vector, hours = 2, gap = 60) print(paste("Modified grid points in larger dataset:", nrow(large_mod_result$mod_grid_vector)))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # First, get grid points grid_result <- grid(example_data_5_subject, gap = 60, threshold = 130) # Perform modified GRID analysis mod_result <- mod_grid(example_data_5_subject, grid_result$grid_vector, hours = 2, gap = 60) print(paste("Modified grid points:", nrow(mod_result$mod_grid_vector))) # Modified analysis with different parameters mod_result_1h <- mod_grid(example_data_5_subject, grid_result$grid_vector, hours = 1, gap = 40) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 60, threshold = 130) large_mod_result <- mod_grid(example_data_hall, large_grid$grid_vector, hours = 2, gap = 60) print(paste("Modified grid points in larger dataset:", nrow(large_mod_result$mod_grid_vector)))
Orders a dataframe by id and time columns using a C++
std::sort backend. Optimized for large CGM datasets, it returns the
input with rows sorted by subject then timestamp while preserving all columns.
Orders a dataframe by id and time columns using the C++ backend
orderfast(df)orderfast(df)
df |
A dataframe with 'id' and 'time' columns |
A dataframe ordered by id and time
A dataframe ordered by id and time
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Shuffle without replacement, then order and compare to baseline set.seed(123) shuffled <- example_data_5_subject[sample(seq_len(nrow(example_data_5_subject)), replace = FALSE), ] baseline <- orderfast(example_data_5_subject) ordered_shuffled <- orderfast(shuffled) # Compare results print(paste("Identical after ordering:", identical(baseline, ordered_shuffled))) head(baseline[, c("id", "time", "gl")]) head(ordered_shuffled[, c("id", "time", "gl")]) # Order larger dataset ordered_large <- orderfast(example_data_hall) print(paste("Ordered", nrow(ordered_large), "rows in larger dataset")) df <- data.frame(id = c("b", "a", "a"), time = as.POSIXct( c("2024-01-01 01:00:00", "2024-01-01 00:00:00", "2024-01-01 01:00:00"), tz = "UTC" )) orderfast(df)# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Shuffle without replacement, then order and compare to baseline set.seed(123) shuffled <- example_data_5_subject[sample(seq_len(nrow(example_data_5_subject)), replace = FALSE), ] baseline <- orderfast(example_data_5_subject) ordered_shuffled <- orderfast(shuffled) # Compare results print(paste("Identical after ordering:", identical(baseline, ordered_shuffled))) head(baseline[, c("id", "time", "gl")]) head(ordered_shuffled[, c("id", "time", "gl")]) # Order larger dataset ordered_large <- orderfast(example_data_hall) print(paste("Ordered", nrow(ordered_large), "rows in larger dataset")) df <- data.frame(id = c("b", "a", "a"), time = as.POSIXct( c("2024-01-01 01:00:00", "2024-01-01 00:00:00", "2024-01-01 01:00:00"), tz = "UTC" )) orderfast(df)
Calculates the percent of expected CGM readings observed. By default, the
calculation uses each subject's original timestamp span from first valid
reading to last valid reading. If ndays is supplied, valid readings
in [end_date - ndays, end_date] are divided by the expected number of
readings over that fixed retrospective window.
For fixed-window calculations, if end_date = NULL, each subject's last
valid timestamp defines that subject's retrospective window. If end_date
is supplied, the same endpoint is used for all subjects, which is useful for a
common study cutoff or report date. Duplicate timestamps within a subject are
de-duplicated after sorting, and rows with missing time or gl
do not count as observed readings.
sensor_wear(df, end_date = NULL, ndays = NULL, reading_minutes = NULL)sensor_wear(df, end_date = NULL, ndays = NULL, reading_minutes = NULL)
df |
A dataframe containing CGM data with columns:
|
end_date |
End timestamp for a fixed-window calculation. Requires
|
ndays |
Number of days in the fixed retrospective window. Defaults to
|
reading_minutes |
Reading interval in minutes. If |
A tibble with columns id, sensor_wear_percent,
sensor_wear, ndays, start_date, and
end_date. sensor_wear is retained as a backward-compatible
alias.
detect_all_events, iglu::active_percent
library(iglu) data(example_data_5_subject) sensor_wear(example_data_5_subject, reading_minutes = 5) sensor_wear(example_data_5_subject, ndays = 90, reading_minutes = 5)library(iglu) data(example_data_5_subject) sensor_wear(example_data_5_subject, reading_minutes = 5) sensor_wear(example_data_5_subject, ndays = 90, reading_minutes = 5)
Finds R-based (1-indexed) positions where the value is 1 in an integer vector of 0s and 1s, specifically identifying episode start points. This function looks for positions where a 1 follows a 0 or is at the beginning of the vector, which is useful for identifying the start of glycemic events or episodes.
start_finder(df)start_finder(df)
df |
A dataframe with the first column containing an integer vector of 0s and 1s |
A tibble containing start_index with R-based (1-indexed) positions where episodes start
Note: These index refer to positions in the provided input vector/dataframe, not necessarily rows of the original CGM df unless that vector was derived directly from df in row order.
- Returns R-based start_index positions relative to the provided input vector/dataframe.
- If used on vectors derived from a CGM df, index map directly to df rows.
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
transform_df()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create a binary vector indicating episode starts binary_vector <- c(0, 0, 1, 1, 0, 1, 0, 0, 1, 1) df <- data.frame(episode_starts = binary_vector) # Find R-based index where episodes start start_points <- start_finder(df) print(paste("Start index:", paste(start_points$start_index, collapse = ", "))) # Use with actual GRID results grid_result <- grid(example_data_5_subject, gap = 15, threshold = 130) grid_starts <- start_finder(grid_result$grid_vector) print(paste("GRID episode starts:", length(grid_starts$start_index))) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 15, threshold = 130) large_starts <- start_finder(large_grid$grid_vector) print(paste("GRID episode starts in larger dataset:", length(large_starts$start_index)))# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Create a binary vector indicating episode starts binary_vector <- c(0, 0, 1, 1, 0, 1, 0, 0, 1, 1) df <- data.frame(episode_starts = binary_vector) # Find R-based index where episodes start start_points <- start_finder(df) print(paste("Start index:", paste(start_points$start_index, collapse = ", "))) # Use with actual GRID results grid_result <- grid(example_data_5_subject, gap = 15, threshold = 130) grid_starts <- start_finder(grid_result$grid_vector) print(paste("GRID episode starts:", length(grid_starts$start_index))) # Analysis on larger dataset large_grid <- grid(example_data_hall, gap = 15, threshold = 130) large_starts <- start_finder(large_grid$grid_vector) print(paste("GRID episode starts in larger dataset:", length(large_starts$start_index)))
Performs data transformations required for GRID analysis, including mapping GRID episode starts to maxima within a 4-hour window and merging grid and maxima information. This function prepares data for downstream analysis by combining these results.
transform_df(grid_df, maxima_df)transform_df(grid_df, maxima_df)
grid_df |
A dataframe containing grid analysis results |
maxima_df |
A dataframe containing maxima detection results |
A tibble with transformed data containing columns (id, grid_time, grid_gl, maxima_time, maxima_gl)
grid, find_new_maxima, detect_between_maxima
Other GRID pipeline:
detect_between_maxima(),
find_local_maxima(),
find_max_after_hours(),
find_max_before_hours(),
find_min_after_hours(),
find_min_before_hours(),
find_new_maxima(),
grid(),
maxima_grid(),
mod_grid(),
start_finder()
# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Complete pipeline example with smaller dataset threshold <- 130 gap <- 60 hours <- 2 # 1) Find GRID points grid_result <- grid(example_data_5_subject, gap = gap, threshold = threshold) # 2) Find modified GRID points before 2 hours minimum mod_grid <- mod_grid(example_data_5_subject, start_finder(grid_result$grid_vector), hours = hours, gap = gap) # 3) Find maximum point 2 hours after mod_grid point mod_grid_maxima <- find_max_after_hours(example_data_5_subject, start_finder(mod_grid$mod_grid_vector), hours = hours) # 4) Identify local maxima around episodes/windows local_maxima <- find_local_maxima(example_data_5_subject) # 5) Among local maxima, find maximum point after two hours final_maxima <- find_new_maxima(example_data_5_subject, mod_grid_maxima$max_index, local_maxima$local_maxima_vector) # 6) Map GRID points to maximum points (within 4 hours) transform_maxima <- transform_df(grid_result$episode_start, final_maxima) # 7) Redistribute overlapping maxima between GRID points final_between_maxima <- detect_between_maxima(example_data_5_subject, transform_maxima) # Complete pipeline example with larger dataset (example_data_hall) # This demonstrates the same workflow on a more comprehensive dataset hall_threshold <- 130 hall_gap <- 60 hall_hours <- 2 # 1) Find GRID points on larger dataset hall_grid_result <- grid(example_data_hall, gap = hall_gap, threshold = hall_threshold) # 2) Find modified GRID points hall_mod_grid <- mod_grid(example_data_hall, start_finder(hall_grid_result$grid_vector), hours = hall_hours, gap = hall_gap) # 3) Find maximum points after mod_grid hall_mod_grid_maxima <- find_max_after_hours(example_data_hall, start_finder(hall_mod_grid$mod_grid_vector), hours = hall_hours) # 4) Identify local maxima hall_local_maxima <- find_local_maxima(example_data_hall) # 5) Find new maxima hall_final_maxima <- find_new_maxima(example_data_hall, hall_mod_grid_maxima$max_index, hall_local_maxima$local_maxima_vector) # 6) Transform data hall_transform_maxima <- transform_df(hall_grid_result$episode_start, hall_final_maxima) # 7) Detect between maxima hall_final_between_maxima <- detect_between_maxima(example_data_hall, hall_transform_maxima)# Load sample data library(iglu) data(example_data_5_subject) data(example_data_hall) # Complete pipeline example with smaller dataset threshold <- 130 gap <- 60 hours <- 2 # 1) Find GRID points grid_result <- grid(example_data_5_subject, gap = gap, threshold = threshold) # 2) Find modified GRID points before 2 hours minimum mod_grid <- mod_grid(example_data_5_subject, start_finder(grid_result$grid_vector), hours = hours, gap = gap) # 3) Find maximum point 2 hours after mod_grid point mod_grid_maxima <- find_max_after_hours(example_data_5_subject, start_finder(mod_grid$mod_grid_vector), hours = hours) # 4) Identify local maxima around episodes/windows local_maxima <- find_local_maxima(example_data_5_subject) # 5) Among local maxima, find maximum point after two hours final_maxima <- find_new_maxima(example_data_5_subject, mod_grid_maxima$max_index, local_maxima$local_maxima_vector) # 6) Map GRID points to maximum points (within 4 hours) transform_maxima <- transform_df(grid_result$episode_start, final_maxima) # 7) Redistribute overlapping maxima between GRID points final_between_maxima <- detect_between_maxima(example_data_5_subject, transform_maxima) # Complete pipeline example with larger dataset (example_data_hall) # This demonstrates the same workflow on a more comprehensive dataset hall_threshold <- 130 hall_gap <- 60 hall_hours <- 2 # 1) Find GRID points on larger dataset hall_grid_result <- grid(example_data_hall, gap = hall_gap, threshold = hall_threshold) # 2) Find modified GRID points hall_mod_grid <- mod_grid(example_data_hall, start_finder(hall_grid_result$grid_vector), hours = hall_hours, gap = hall_gap) # 3) Find maximum points after mod_grid hall_mod_grid_maxima <- find_max_after_hours(example_data_hall, start_finder(hall_mod_grid$mod_grid_vector), hours = hall_hours) # 4) Identify local maxima hall_local_maxima <- find_local_maxima(example_data_hall) # 5) Find new maxima hall_final_maxima <- find_new_maxima(example_data_hall, hall_mod_grid_maxima$max_index, hall_local_maxima$local_maxima_vector) # 6) Transform data hall_transform_maxima <- transform_df(hall_grid_result$episode_start, hall_final_maxima) # 7) Detect between maxima hall_final_between_maxima <- detect_between_maxima(example_data_hall, hall_transform_maxima)