Continuous Glucose Monitoring (CGM) systems provide real-time measurements of glucose levels and are increasingly used in diabetes research and clinical practice to assess glycemic control and glucose variability.
Several CGM-derived metrics are commonly used to evaluate: - sensor accuracy, - short-term glycemic variability, - glucose excursions, - and inter-day glycemic stability.
This document presents the methodological framework for computing the following CGM metrics in R:
For each metric, the following are presented: - definition and purpose, - feasibility of computation, - required variables, - data structure, - formula, - R packages and functions, - and example R code.
| Variable | Description | Required For |
|---|---|---|
| id | Participant identifier | All metrics |
| datetime | Date and time of CGM measurement | All metrics |
| glucose | CGM glucose measurement | All metrics |
| reference_glucose | Reference blood glucose value | MARD |
| day | Day extracted from datetime | MODD |
The preferred structure is a longitudinal long-format dataset.
| id | datetime | glucose | reference_glucose |
|---|---|---|---|
| 001 | 2026-05-01 08:00:00 | 145 | 150 |
| 001 | 2026-05-01 08:05:00 | 148 | 147 |
| 001 | 2026-05-01 08:10:00 | 152 | 154 |
Mean Absolute Relative Difference (MARD) is a widely used metric for evaluating the analytical accuracy of Continuous Glucose Monitoring (CGM) systems.
It measures the average relative difference between CGM glucose measurements and corresponding reference glucose values obtained from laboratory or capillary measurements.
Lower MARD values indicate better agreement between CGM readings and reference glucose values, reflecting higher sensor accuracy.
Yes.
MARD can be computed when paired CGM and reference glucose measurements are available for the same observation time points.
| Variable | Description |
|---|---|
| glucose | CGM glucose measurement |
| reference_glucose | Reference glucose measurement |
| id | datetime | glucose | reference_glucose |
|---|---|---|---|
| 001 | 2026-05-01 08:00:00 | 145 | 150 |
| 001 | 2026-05-01 08:05:00 | 148 | 147 |
\[ MARD = \frac{1}{n}\sum_{i=1}^{n}\left|\frac{CGM_i - REF_i}{REF_i}\right| \times 100 \]
Where:
| Package | Function |
|---|---|
| base | mean() |
| base | abs() |
| dplyr | group_by(), summarize() |
base::mean()base::abs()dplyr::group_by()dplyr::summarize()This approach calculates the MARD metric directly from two synchronized clinical data vectors.
# Sample paired tracking data
cgm <- c(100, 150, 200, 120, 80)
ref <- c(105, 145, 190, 130, 85)
# Calculate Absolute Relative Differences (ARD)
ard <- abs((cgm - ref) / ref)
# Calculate Mean ARD and convert to percentage
mard <- mean(ard) * 100
print(paste0("MARD: ", round(mard, 2), "%"))## [1] "MARD: 5.41%"
This production-grade wrapper adds logical handling to prevent mathematical errors such as division by zero.
calculate_mard <- function(cgm_data, ref_data) {
# Prevent division by zero
if (any(ref_data == 0)) {
stop("Reference data cannot contain zero.")
}
# Compute MARD
mard_val <- mean(abs((cgm_data - ref_data) / ref_data)) * 100
return(mard_val)
}
# Apply function to paired vectors
result <- calculate_mard(cgm, ref)
print(paste0("MARD: ", round(result, 2), "%"))## [1] "MARD: 5.41%"
This structure calculates individual MARD values for multiple sensors stored sequentially within a unified long-format dataset.
## Warning: package 'dplyr' was built under R version 4.5.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
# Example long-format dataset
df_long <- data.frame(
sensor_id = rep(c("Sensor_A", "Sensor_B", "Sensor_C"), each = 4),
reading = c(105, 110, 95, 120,
102, 107, 98, 122,
108, 115, 92, 128),
reference = rep(c(100, 108, 100, 125), 3)
)
# Compute MARD for each sensor
mard_long_results <- df_long %>%
group_by(sensor_id) %>%
summarize(
MARD = mean(abs((reading - reference) / reference)) * 100,
.groups = "drop"
)
print(mard_long_results)## # A tibble: 3 × 2
## sensor_id MARD
## <chr> <dbl>
## 1 Sensor_A 3.96
## 2 Sensor_B 1.83
## 3 Sensor_C 6.22
This approach handles datasets where multiple sensors are stored in separate columns, allowing simultaneous MARD computation across sensors.
# Example wide-format dataset
df_wide <- data.frame(
ref = c(100, 110, 120, 130),
sensor1 = c(105, 108, 125, 128),
sensor2 = c(95, 115, 118, 135),
sensor3 = c(102, 112, 119, 131)
)
# Define sensor columns
sensor_cols <- c("sensor1", "sensor2", "sensor3")
# Compute MARD for each sensor column
mards_wide_results <- sapply(
df_wide[sensor_cols],
function(sensor_val) {
mean(abs((sensor_val - df_wide$ref) / df_wide$ref)) * 100
}
)
print(mards_wide_results)## sensor1 sensor2 sensor3
## 3.130828 3.764569 1.355186
Continuous Overall Net Glycemic Action (CONGA) is a CGM-derived metric used to quantify short-term glycemic variability.
It evaluates glucose fluctuations over a predefined time interval by calculating the standard deviation of glucose differences between measurements separated by a fixed lag period.
CONGA is particularly useful for assessing intra-day glucose instability and short-term glycemic oscillations.
Yes.
CONGA can be computed using longitudinal CGM data containing sequential glucose measurements associated with timestamps.
| Variable | Description |
|---|---|
| id | Participant identifier |
| datetime | Timestamp of CGM measurement |
| glucose | CGM glucose measurement |
| id | datetime | glucose |
|---|---|---|
| Subject_01 | 2026-05-14 08:00:00 | 100 |
| Subject_01 | 2026-05-14 08:30:00 | 110 |
| Subject_01 | 2026-05-14 09:00:00 | 130 |
CONGA calculates the standard deviation of the differences between glucose values separated by a predefined time lag \(n\).
For example: - CONGA1 evaluates variability over 1 hour, - CONGA2 evaluates variability over 2 hours.
Higher CONGA values indicate greater short-term glycemic variability.
\[ CONGA_n = SD(G_t - G_{t-n}) \]
Where:
| Package | Function |
|---|---|
| iglu | conga() |
| dplyr | data manipulation |
iglu::conga()dplyr::mutate()dplyr::arrange()## Warning: package 'iglu' was built under R version 4.5.3
library(dplyr)
# 1. Generate compliant CGM dataset
cgm_data <- data.frame(
id = rep("Subject_01", 9),
time = as.POSIXct(c(
"2026-05-14 08:00:00",
"2026-05-14 08:30:00",
"2026-05-14 09:00:00",
"2026-05-14 09:30:00",
"2026-05-14 10:00:00",
"2026-05-14 10:30:00",
"2026-05-14 11:00:00",
"2026-05-14 11:30:00",
"2026-05-14 12:00:00"
), tz = "GMT"),
gl = c(100, 110, 130, 140, 150, 160, 145, 135, 120)
)
# 2. Inspect input data structure
print("Structured Input Data Frame:")## [1] "Structured Input Data Frame:"
## id time gl
## 1 Subject_01 2026-05-14 08:00:00 100
## 2 Subject_01 2026-05-14 08:30:00 110
## 3 Subject_01 2026-05-14 09:00:00 130
## 4 Subject_01 2026-05-14 09:30:00 140
## 5 Subject_01 2026-05-14 10:00:00 150
## 6 Subject_01 2026-05-14 10:30:00 160
## 7 Subject_01 2026-05-14 11:00:00 145
## 8 Subject_01 2026-05-14 11:30:00 135
## 9 Subject_01 2026-05-14 12:00:00 120
# 3. Compute CONGA using a 2-hour lag window
conga_result <- iglu::conga(
cgm_data,
n = 2,
tz = "GMT"
)
# 4. Display output results
print("CONGA Output Tibble:")## [1] "CONGA Output Tibble:"
## # A tibble: 1 × 2
## id CONGA
## <chr> <dbl>
## 1 Subject_01 34.9
Mean Amplitude of Glycemic Excursions (MAGE) is a CGM-derived metric used to quantify major glucose fluctuations occurring over time.
It measures the average amplitude of significant upward and downward glucose excursions that exceed a predefined variability threshold, typically one standard deviation of the mean glucose profile.
MAGE is commonly used to assess glycemic instability and detect clinically meaningful glucose swings.
Yes.
MAGE can be computed using longitudinal CGM datasets containing sequential glucose measurements and timestamps.
| Variable | Description |
|---|---|
| id | Participant identifier |
| datetime | Timestamp of CGM measurement |
| glucose | CGM glucose measurement |
| id | datetime | glucose |
|---|---|---|
| Subject_01 | 2026-05-14 06:00:00 | 95 |
| Subject_01 | 2026-05-14 06:30:00 | 90 |
| Subject_01 | 2026-05-14 07:00:00 | 160 |
MAGE identifies significant glucose excursions by detecting peaks and troughs in the glucose profile that exceed one standard deviation of the mean glucose level.
Only major excursions are retained in the computation process.
Higher MAGE values indicate greater glycemic instability and larger glucose fluctuations.
| Package | Function |
|---|---|
| iglu | mage() |
| dplyr | data manipulation |
iglu::mage()dplyr::mutate()dplyr::arrange()The iglu package calculates MAGE using the same
structured layout format as the CONGA function (id,
time, gl).
library(iglu)
library(dplyr)
# 1. Structure a single-subject dataset
cgm_data_mage <- data.frame(
id = rep("Subject_01", 12),
time = as.POSIXct(c(
"2026-05-14 06:00:00",
"2026-05-14 06:30:00",
"2026-05-14 07:00:00",
"2026-05-14 07:30:00",
"2026-05-14 08:00:00",
"2026-05-14 08:30:00",
"2026-05-14 09:00:00",
"2026-05-14 09:30:00",
"2026-05-14 10:00:00",
"2026-05-14 10:30:00",
"2026-05-14 11:00:00",
"2026-05-14 11:30:00"
), tz = "GMT"),
gl = c(95, 90, 160, 185, 175, 110, 85, 90, 145, 160, 130, 100)
)
# 2. Inspect the glucose excursion profile
print("Input Data Frame for MAGE:")## [1] "Input Data Frame for MAGE:"
## id time gl
## 1 Subject_01 2026-05-14 06:00:00 95
## 2 Subject_01 2026-05-14 06:30:00 90
## 3 Subject_01 2026-05-14 07:00:00 160
## 4 Subject_01 2026-05-14 07:30:00 185
## 5 Subject_01 2026-05-14 08:00:00 175
## 6 Subject_01 2026-05-14 08:30:00 110
## 7 Subject_01 2026-05-14 09:00:00 85
## 8 Subject_01 2026-05-14 09:30:00 90
## 9 Subject_01 2026-05-14 10:00:00 145
## 10 Subject_01 2026-05-14 10:30:00 160
## 11 Subject_01 2026-05-14 11:00:00 130
## 12 Subject_01 2026-05-14 11:30:00 100
# 3. Compute MAGE
mage_result <- iglu::mage(cgm_data_mage)
# 4. Display output results
print("MAGE Output Tibble:")## [1] "MAGE Output Tibble:"
## # A tibble: 1 × 2
## # Rowwise:
## id MAGE
## <chr> <dbl>
## 1 Subject_01 82.5
By setting plot = TRUE, the iglu package
produces a graphical representation of the glucose profile used during
MAGE computation.
The visualization: - plots the CGM time-series profile, - identifies major peaks and troughs, - and highlights the excursion threshold band corresponding to one standard deviation.
Mean of Daily Differences (MODD) is a CGM-derived metric used to evaluate inter-day glycemic stability.
It measures the average absolute difference between glucose values recorded at similar time points on consecutive days.
MODD is particularly useful for assessing day-to-day consistency in glucose profiles and identifying temporal glycemic drift across repeated daily cycles.
Yes.
MODD can be computed when CGM measurements span at least two consecutive days with sufficiently aligned timestamps.
| Variable | Description |
|---|---|
| id | Participant identifier |
| datetime | Timestamp of CGM measurement |
| glucose | CGM glucose measurement |
| id | datetime | glucose |
|---|---|---|
| Subject_01 | 2026-05-14 08:00:00 | 100 |
| Subject_01 | 2026-05-14 08:15:00 | 110 |
| Subject_01 | 2026-05-15 08:00:00 | 115 |
MODD compares glucose measurements recorded at equivalent time points across consecutive days and computes the mean absolute difference between those paired observations.
Higher MODD values indicate greater day-to-day glucose variability and lower inter-day stability.
\[ MODD = \frac{1}{n}\sum_{i=1}^{n}|G_{day1,i} - G_{day2,i}| \]
Where:
| Package | Function |
|---|---|
| iglu | modd() |
| iglu | plot_glu() |
| dplyr | data manipulation |
iglu::modd()iglu::plot_glu()dplyr::mutate()dplyr::arrange()To compute MODD, the input dataset must span multiple days with glucose readings aligned across similar time blocks.
library(iglu)
library(dplyr)
# 1. Structure a dataset spanning two consecutive days
cgm_data_modd <- data.frame(
id = rep("Subject_01", 10),
time = as.POSIXct(c(
# Day 1 readings
"2026-05-14 08:00:00",
"2026-05-14 08:15:00",
"2026-05-14 08:30:00",
"2026-05-14 08:45:00",
"2026-05-14 09:00:00",
# Day 2 readings (same time points)
"2026-05-15 08:00:00",
"2026-05-15 08:15:00",
"2026-05-15 08:30:00",
"2026-05-15 08:45:00",
"2026-05-15 09:00:00"
), tz = "GMT"),
gl = c(
100, 110, 130, 150, 120, # Day 1 values
115, 120, 125, 170, 110 # Day 2 values
)
)
# 2. Compute MODD
modd_result <- iglu::modd(cgm_data_modd)
# 3. Display output results
print("MODD Output Tibble:")## [1] "MODD Output Tibble:"
## # A tibble: 1 × 2
## id MODD
## <chr> <dbl>
## 1 Subject_01 12
To visually assess the inter-day differences contributing to the MODD
score, glucose trajectories from multiple days can be overlaid using
plot_glu().
This visualization: - displays longitudinal glucose profiles, - compares repeated daily trajectories, - and highlights inter-day glucose drift across matching time intervals.
# Generate inter-day glucose trajectory visualization
iglu::plot_glu(
cgm_data_modd,
plottype = "tsplot",
inter_gap = 60,
LLTR = 70,
ULTR = 180,
static_or_gui = "ggplot"
)## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the iglu package.
## Please report the issue at <https://github.com/irinagain/iglu/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
The four CGM metrics presented in this document can be computed in R when appropriately structured CGM datasets are available.
The essential requirements include: - properly formatted timestamps, - longitudinal CGM measurements, - consistent glucose units, - and paired reference glucose values for MARD.
The iglu package provides a practical framework for
computing CGM variability metrics in R, while maintaining
reproducibility and methodological traceability.
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=English_India.utf8 LC_CTYPE=English_India.utf8
## [3] LC_MONETARY=English_India.utf8 LC_NUMERIC=C
## [5] LC_TIME=English_India.utf8
##
## time zone: Asia/Calcutta
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] iglu_4.2.2 dplyr_1.2.1
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 utf8_1.2.6 generics_0.1.4 tidyr_1.3.1
## [5] rstatix_0.7.3 lattice_0.22-7 digest_0.6.39 magrittr_2.0.5
## [9] evaluate_1.0.5 grid_4.5.1 timechange_0.4.0 RColorBrewer_1.1-3
## [13] fastmap_1.2.0 jsonlite_2.0.0 backports_1.5.1 Formula_1.2-5
## [17] purrr_1.2.2 scales_1.4.0 jquerylib_0.1.4 abind_1.4-8
## [21] cli_3.6.6 rlang_1.2.0 cowplot_1.2.0 withr_3.0.2
## [25] cachem_1.1.0 yaml_2.3.10 otel_0.2.0 tools_4.5.1
## [29] ggsignif_0.6.4 ggplot2_4.0.2 DT_0.34.0 ggpubr_0.6.3
## [33] broom_1.0.12 vctrs_0.7.1 R6_2.6.1 zoo_1.8-14
## [37] lifecycle_1.0.5 lubridate_1.9.5 car_3.1-5 htmlwidgets_1.6.4
## [41] pkgconfig_2.0.3 pillar_1.11.1 bslib_0.10.0 gtable_0.3.6
## [45] glue_1.8.1 xfun_0.52 tibble_3.3.0 tidyselect_1.2.1
## [49] rstudioapi_0.18.0 knitr_1.51 farver_2.1.2 htmltools_0.5.9
## [53] patchwork_1.3.2 labeling_0.4.3 carData_3.0-6 rmarkdown_2.31
## [57] compiler_4.5.1 S7_0.2.1