---
title: "cgmguru: Practical CGM Analysis Guide"
output:
  rmarkdown::html_vignette:
    toc: true
    toc_depth: 2
vignette: >
  %\VignetteIndexEntry{cgmguru: Practical CGM Analysis Guide}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4,
  message = FALSE,
  warning = FALSE
)

library(cgmguru)

iglu_available <- requireNamespace("iglu", quietly = TRUE)
ggplot2_available <- requireNamespace("ggplot2", quietly = TRUE)
```

## Overview

`cgmguru` provides high-performance tools for continuous glucose monitoring
(CGM) analysis. It combines two complementary workflows:

- consensus-style glycemic event detection for hypo- and hyperglycemia
- GRID-based detection of rapid glucose rises and postprandial maxima

This guide expands the package-level vignette into a practical workflow. It is
based on the longer `vignette("intro", package = "cgmguru")` source and focuses
on how to move from a raw CGM table to subject summaries, event tables, GRID
starts, postprandial peaks, and simple visual checks.

## Data Contract

Most `cgmguru` functions expect a data frame with these columns:

| Column | Meaning |
| --- | --- |
| `id` | Subject identifier |
| `time` | POSIXct timestamp |
| `gl` | Glucose value in mg/dL |

Rows may contain multiple subjects. For speed and reproducibility, it is good
practice to order by `id` and `time` before analysis, especially when chaining
several functions.

```{r load-data, eval = iglu_available}
data(example_data_5_subject, package = "iglu")
data(example_data_hall, package = "iglu")

cgm_data <- orderfast(example_data_5_subject)

data.frame(
  rows = nrow(cgm_data),
  subjects = length(unique(cgm_data$id)),
  first_time = min(cgm_data$time),
  last_time = max(cgm_data$time),
  min_glucose = min(cgm_data$gl, na.rm = TRUE),
  max_glucose = max(cgm_data$gl, na.rm = TRUE)
)
```

```{r missing-iglu, eval = !iglu_available}
cat("The 'iglu' package is not available, so example-data chunks are skipped.\n")
```

## Two Preprocessing Models

The package intentionally separates event-grid analysis from GRID-family
analysis.

Glycemic event functions use an iglu-compatible event grid:

- `detect_hypoglycemic_events()`
- `detect_hyperglycemic_events()`
- `detect_all_events()`
- `interpolate_cgm()`

These functions can infer `reading_minutes` per subject from the median
positive timestamp spacing. They align to a full-day grid, interpolate gaps up
to `inter_gap`, remove larger gap-masked rows, and classify events by
contiguous segments.

GRID-family functions operate on the rows you pass in:

- `grid()`
- `mod_grid()`
- `maxima_grid()`
- `find_local_maxima()`
- `excursion()`

They do not automatically call the event-grid interpolation pipeline. If you
want GRID analysis on an interpolated series, explicitly pass the interpolated
data.

## Subject-Level Overview

`sensor_wear()` calculates how much observed CGM data are present. By default it
uses each subject's original timestamp span. Supplying `ndays` switches to a
fixed retrospective window ending at each subject's last valid timestamp, or at
a common `end_date` if you provide one.

```{r sensor-wear, eval = iglu_available}
sensor_wear(cgm_data, reading_minutes = 5)

sensor_wear(cgm_data, ndays = 14, reading_minutes = 5)
```

For a broader one-row-per-subject summary, use `detect_all_events()`.

```{r all-events, eval = iglu_available}
all_events <- detect_all_events(
  cgm_data,
  reading_minutes = 5,
  sensor_wear_ndays = 14
)

names(all_events)
head(all_events$subject_summary)
head(all_events$glycemic_event_summary)
```

The `subject_summary` table includes CGM summary metrics such as time in range,
time below range, time above range, mean glucose, variability, GMI/uGMI, GRI,
and `sensor_wear_percent`. By default, these summary glucose metrics are
calculated from the original raw glucose values. Set
`summary_metrics_source = "preprocessed"` to calculate them from the internal
event grid after interpolation and gap masking.

```{r all-events-preprocessed, eval = iglu_available}
all_events_preprocessed <- detect_all_events(
  cgm_data,
  reading_minutes = 5,
  summary_metrics_source = "preprocessed"
)

head(all_events_preprocessed$subject_summary)
```

## Inspecting the Event Grid

When you need to audit event boundaries, use `return_interpolated = TRUE`. The
returned `interpolated_data` is the grid used internally for event
classification.

```{r interpolated-grid, eval = iglu_available}
all_events_with_grid <- detect_all_events(
  cgm_data,
  reading_minutes = 5,
  return_interpolated = TRUE
)

names(all_events_with_grid)
head(all_events_with_grid$interpolated_data)
```

The standalone helper `interpolate_cgm()` is useful when you want to inspect
preprocessing before running event detectors.

```{r interpolate-helper, eval = iglu_available}
event_grid <- interpolate_cgm(cgm_data, reading_minutes = 5)
head(event_grid)
```

## Hypoglycemia and Hyperglycemia Events

Use the standalone event functions when you need detailed event boundaries for
one event type. Presets are available through `type = "lv1"`, `"lv2"`,
`"extended"`, and `"lv1_excl"`.

```{r standalone-events, eval = iglu_available}
hyper_lv1 <- detect_hyperglycemic_events(
  cgm_data,
  type = "lv1",
  reading_minutes = 5,
  return_interpolated = FALSE
)

hypo_lv1 <- detect_hypoglycemic_events(
  cgm_data,
  type = "lv1",
  reading_minutes = 5,
  return_interpolated = FALSE
)

hyper_lv1$events_total
hypo_lv1$events_total
head(hyper_lv1$events_detailed)
```

`detect_all_events()` is usually the most convenient interface for reporting,
because it aggregates hypo- and hyperglycemia definitions in one call.

```{r event-summary-nonzero, eval = iglu_available}
nonzero_events <- all_events$glycemic_event_summary[
  all_events$glycemic_event_summary$total_episodes > 0,
]
head(nonzero_events)
```

## GRID Analysis

The GRID (Glucose Rate Increase Detector) workflow detects rapid glucose rises,
often used as candidate meal or postprandial start points.

```{r grid-analysis, eval = iglu_available}
grid_result <- grid(cgm_data, gap = 15, threshold = 130)

grid_result$episode_counts
head(grid_result$episode_start)
head(grid_result$grid_vector)
```

Lowering the `threshold` or `gap` generally makes detection more sensitive.

```{r grid-sensitive, eval = iglu_available}
sensitive_grid <- grid(cgm_data, gap = 10, threshold = 120)
head(sensitive_grid$episode_counts)
```

## Postprandial Maxima

`maxima_grid()` combines GRID starts with local maxima to identify likely
postprandial peaks within a time window.

```{r maxima-grid, eval = iglu_available}
maxima_result <- maxima_grid(
  cgm_data,
  threshold = 130,
  gap = 60,
  hours = 2
)

maxima_result$episode_counts
head(maxima_result$results)
```

For a more explicit step-by-step version of the workflow, chain the lower-level
helpers.

```{r postprandial-pipeline, eval = iglu_available}
grid_starts <- start_finder(grid_result$grid_vector)

mod_grid_result <- mod_grid(
  cgm_data,
  grid_starts,
  hours = 2,
  gap = 60
)

mod_grid_starts <- start_finder(mod_grid_result$mod_grid_vector)

max_after_result <- find_max_after_hours(
  cgm_data,
  mod_grid_starts,
  hours = 2
)

local_maxima <- find_local_maxima(cgm_data)

new_maxima <- find_new_maxima(
  cgm_data,
  max_after_result$max_index,
  local_maxima$local_maxima_vector
)

mapped_maxima <- transform_df(grid_result$episode_start, new_maxima)
between_maxima <- detect_between_maxima(cgm_data, mapped_maxima)

head(mapped_maxima)
head(between_maxima$results)
```

## Excursion Analysis

`excursion()` identifies glucose excursions, defined as rises greater than
70 mg/dL within 2 hours, excluding starts preceded by hypoglycemia.

```{r excursion, eval = iglu_available}
excursion_result <- excursion(cgm_data, gap = 15)

excursion_result$episode_counts
head(excursion_result$episode_start)
```

## Quick Visualization

Visual checks are useful after tuning thresholds. The plot below shows one
subject's glucose trace, clinical thresholds, and GRID episode starts.

```{r plot-grid, eval = iglu_available && ggplot2_available}
subject_id <- unique(cgm_data$id)[1]
subject_data <- cgm_data[cgm_data$id == subject_id, ]
subject_grid_starts <- grid_result$episode_start[
  grid_result$episode_start$id == subject_id,
]

ggplot2::ggplot(subject_data, ggplot2::aes(x = time, y = gl)) +
  ggplot2::geom_line(linewidth = 0.3, color = "steelblue") +
  ggplot2::geom_hline(yintercept = c(54, 70), linetype = "dashed",
                      color = "darkorange") +
  ggplot2::geom_hline(yintercept = c(180, 250), linetype = "dashed",
                      color = "firebrick") +
  ggplot2::geom_point(
    data = subject_grid_starts,
    ggplot2::aes(x = time, y = gl),
    color = "black",
    size = 1.4
  ) +
  ggplot2::labs(
    title = paste("CGM Trace and GRID Starts:", subject_id),
    x = "Time",
    y = "Glucose (mg/dL)"
  ) +
  ggplot2::theme_minimal()
```

```{r plot-grid-missing, eval = iglu_available && !ggplot2_available}
cat("The 'ggplot2' package is not available, so the plot is skipped.\n")
```

## Scaling Up

The same workflow applies to larger multi-subject datasets. The example below
uses `example_data_hall` from `iglu`.

```{r larger-data, eval = iglu_available}
hall_data <- orderfast(example_data_hall)

hall_summary <- detect_all_events(hall_data, reading_minutes = 5)
hall_grid <- grid(hall_data, gap = 15, threshold = 130)

data.frame(
  subjects = length(unique(hall_data$id)),
  rows = nrow(hall_data),
  summary_rows = nrow(hall_summary$subject_summary),
  grid_rows = nrow(hall_grid$grid_vector)
)
```

## Function Map

| Task | Main function |
| --- | --- |
| Order CGM rows | `orderfast()` |
| Calculate observed data coverage | `sensor_wear()` |
| Build an event preprocessing grid | `interpolate_cgm()` |
| Report all event summaries | `detect_all_events()` |
| Detect one hypo/hyper event type | `detect_hypoglycemic_events()`, `detect_hyperglycemic_events()` |
| Detect rapid glucose rises | `grid()` |
| Detect local peaks | `find_local_maxima()` |
| Combine GRID starts and peaks | `maxima_grid()` |
| Find peaks/minima around starts | `find_max_after_hours()`, `find_max_before_hours()`, `find_min_after_hours()`, `find_min_before_hours()` |
| Refine and map postprandial peaks | `find_new_maxima()`, `transform_df()`, `detect_between_maxima()` |
| Detect large glucose excursions | `excursion()` |

## Next Steps

For more detailed examples, open the focused vignettes:

```{r browse-vignettes, eval = FALSE}
browseVignettes("cgmguru")
vignette("intro", package = "cgmguru")
vignette("detect_all_events", package = "cgmguru")
vignette("grid", package = "cgmguru")
vignette("maxima_grid", package = "cgmguru")
```

## References

- Harvey RA, Dassau E, Bevier WC, et al. Design of the glucose rate increase
  detector: a meal detection module for the health monitoring system. *Journal
  of Diabetes Science and Technology*. 2014;8(2):307-320.
- Broll S, Urbanek J, Buchanan D, Chun E, Muschelli J, Punjabi N, Gaynanova I.
  Interpreting blood glucose data with R package iglu. *PLoS One*.
  2021;16(4):e0248560.
- Chun E, Fernandes JN, Gaynanova I. An Update on the iglu Software Package for
  Interpreting Continuous Glucose Monitoring Data. *Diabetes Technology &
  Therapeutics*. 2024;26(12):939-950.
- Battelino T, Alexander CM, Amiel SA, et al. Continuous glucose monitoring and
  metrics for clinical trials: an international consensus statement. *The
  Lancet Diabetes & Endocrinology*. 2023;11(1):42-57.
