ggchangepoint provides a unified, tidy,
ggplot2-native interface to changepoint detection in R: one
dispatcher (cpt_detect()) covering 31 methods across six
methodological families, one result class (ggcpt) with a
stable tidy contract, and one visualisation entry point
(autoplot()) that draws everything a method reports —
including confidence intervals and posterior probabilities (Wickham 2016; Robinson 2017). This vignette is
the feature tour: it visits every exported
function in the package at the point where it belongs in the
workflow, so a reader can map the full surface in one sitting. The
companion vignettes develop the methodology in depth
(vignette("introduction")) and treat method comparison and
evaluation (vignette("comparison")).
Every detector returns a ggcpt object: a list carrying
the tidy changepoints tibble (cp = last index
of the left segment, cp_value = series value there, plus
any method-specific columns), a segments table, the
data, the raw engine fit, and metadata
(method, change_in, penalty, convention, runtime).
new_ggcpt() is the low-level constructor and
is_ggcpt() the class test; most users never call either
directly.
res <- cpt_detect(x, method = "pelt", change_in = "mean")
is_ggcpt(res)
#> [1] TRUE
print(res)
#> ggcpt (changepoint detection result)
#> Method: pelt
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: MBIC
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369manual <- new_ggcpt(
changepoints = tibble::tibble(cp = 100L, cp_value = x[100]),
data = tibble::tibble(index = seq_along(x), value = x),
method = "manual"
)
is_ggcpt(manual)
#> [1] TRUEThe broom verbs give one row per changepoint
(tidy()), a one-row model summary (glance()),
and the data augmented with segment ids, fitted levels, residuals, and a
changepoint flag (augment()):
tidy(res)
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369
glance(res)
#> # A tibble: 1 × 9
#> n n_changepoints method change_in penalty_type penalty_value cp_convention
#> <int> <int> <chr> <chr> <chr> <dbl> <chr>
#> 1 200 1 pelt mean MBIC NA left
#> # ℹ 2 more variables: total_cost <dbl>, runtime <dbl>
head(augment(res))
#> # A tibble: 6 × 6
#> index value seg_id .fitted .resid is_changepoint
#> <int> <dbl> <int> <dbl> <dbl> <lgl>
#> 1 1 0.521 1 -0.0980 0.619 FALSE
#> 2 2 -1.08 1 -0.0980 -0.982 FALSE
#> 3 3 0.139 1 -0.0980 0.237 FALSE
#> 4 4 -0.0847 1 -0.0980 0.0133 FALSE
#> 5 5 -0.667 1 -0.0980 -0.569 FALSE
#> 6 6 -2.52 1 -0.0980 -2.42 FALSEThe remaining S3 surface: a human-readable digest, tibble/data-frame
coercion, a one-line format, and a base-plot() fallback
that delegates to autoplot().
summary(res)
#> ggcpt Summary
#> Method: pelt
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Series length: 200
#> Penalty: MBIC
#> Runtime (seconds): 0.008
#>
#> Segments:
#> # A tibble: 2 × 5
#> seg_id start end n param_estimate
#> <int> <int> <int> <int> <dbl>
#> 1 1 1 100 100 -0.0980
#> 2 2 101 200 100 6.12
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369
as_tibble(res)
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369
head(as.data.frame(res))
#> cp cp_value
#> 1 100 0.3694259
format(res)
#> [1] "ggcpt [pelt] 1 changepoint(s) on 200 observations"cpt_detect() is the recommended entry point: pick a
method, say what the change is in (change_in =
"mean", "var", "meanvar",
"slope", or "distribution"), and optionally
set a penalty. Incompatible
method/change_in combinations error — they are
never silently substituted.
cpt_detect(x, method = "binseg", change_in = "mean")
#> ggcpt (changepoint detection result)
#> Method: binseg
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: MBIC
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369Anything else in ... reaches the underlying wrapper, and
takes precedence over the value the dispatcher would otherwise derive
from change_in — here the NOT contrast is set directly
rather than inherited:
tidy(cpt_detect(x, method = "not", contrast = "pcwsLinMean"))
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369cpt_methods() is the live capability table: every method
the package knows, its engine package, what it can detect, and whether
the engine is installed. Four rows carry status "planned"
rather than "available": their engines (gfpop,
robseg, FOCuS, hdbinseg) are not
on CRAN, so they are documented as future work and are not
wired to cpt_detect().
print(cpt_methods(), n = Inf)
#> # A tibble: 35 × 6
#> method change_in engine status target_release installed
#> <chr> <chr> <chr> <chr> <chr> <lgl>
#> 1 pelt mean, var, meanvar chang… avail… <NA> TRUE
#> 2 binseg mean, var, meanvar chang… avail… <NA> TRUE
#> 3 segneigh mean, var, meanvar chang… avail… <NA> TRUE
#> 4 amoc mean, var, meanvar chang… avail… <NA> TRUE
#> 5 np distribution chang… avail… <NA> TRUE
#> 6 ecp distribution (multivariat… ecp avail… <NA> TRUE
#> 7 fpop mean fpop avail… <NA> TRUE
#> 8 wbs mean wbs avail… <NA> TRUE
#> 9 wbs2 mean break… avail… <NA> TRUE
#> 10 not mean, var, slope not avail… <NA> TRUE
#> 11 mosum mean mosum avail… <NA> TRUE
#> 12 idetect mean IDete… avail… <NA> TRUE
#> 13 tguh mean break… avail… <NA> TRUE
#> 14 smuce mean (with CIs) stepR avail… <NA> FALSE
#> 15 hsmuce mean (heteroskedastic, wi… stepR avail… <NA> FALSE
#> 16 cpop slope cpop avail… <NA> FALSE
#> 17 bcp mean (Bayesian) bcp avail… <NA> FALSE
#> 18 bocpd mean (Bayesian online) ocp avail… <NA> FALSE
#> 19 beast mean/trend (Bayesian) Rbeast avail… <NA> FALSE
#> 20 cpm distribution (sequential) cpm avail… <NA> FALSE
#> 21 kcp running statistics (kerne… kcpRS avail… <NA> FALSE
#> 22 npmojo distribution (multivariat… CptNo… avail… <NA> FALSE
#> 23 decafs mean (drift + AR noise) DeCAFS avail… <NA> FALSE
#> 24 sn mean, var, acf, correlati… SNSeg avail… <NA> FALSE
#> 25 inspect mean (high-dimensional) Inspe… avail… <NA> FALSE
#> 26 ocd mean (high-dimensional, o… ocd avail… <NA> FALSE
#> 27 geomcp distribution (multivariat… chang… avail… <NA> FALSE
#> 28 strucchange mean, regression (with CI… struc… avail… <NA> FALSE
#> 29 segmented slope (with CIs) segme… avail… <NA> FALSE
#> 30 envcpt mean/trend vs autocorrela… EnvCpt avail… <NA> FALSE
#> 31 fastcpd mean, var, meanvar, AR/AR… fastc… avail… <NA> FALSE
#> 32 gfpop mean (graph-constrained) gfpop plann… when on CRAN NA
#> 33 robust mean (robust loss) robseg plann… when on CRAN NA
#> 34 focus mean (online) FOCuS plann… when on CRAN NA
#> 35 sbs mean (high-dimensional) hdbin… plann… next release NAcpt_penalty() constructs standard penalty values for the
engines that take numeric penalties; see its help for the per-engine
penalty semantics.
Each engine also has a direct wrapper exposing its native arguments.
Every wrapper from 0.2.0 onwards returns a ggcpt object;
only the two original wrappers below predate the class and still return
a bare tibble.
cpt_wrapper() and ecp_wrapper() are the
original interface to the changepoint/changepoint.np
(Killick, Fearnhead, and Eckley 2012; Killick and
Eckley 2014; Haynes, Fearnhead, and Eckley 2017) and ecp
(Matteson and James 2014; James and Matteson
2014) engines; they return bare tibbles for backward
compatibility.
Seven multiscale, randomised, and functional-pruning engines (Fryzlewicz 2014, 2020, 2018; Baranowski, Chen, and Fryzlewicz 2019; Eichinger and Kirch 2018; Anastasiou and Fryzlewicz 2022; Maidstone et al. 2017), one call each:
fpop_wrapper(x, penalty = 2 * log(length(x)))
#> ggcpt (changepoint detection result)
#> Method: fpop
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: Manual = 10.597
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369wbs_wrapper(x, n_intervals = 2000, seed = 1)
#> ggcpt (changepoint detection result)
#> Method: wbs
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: sSIC
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369wbs2_wrapper(x)
#> ggcpt (changepoint detection result)
#> Method: wbs2
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: SDLL
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369not_wrapper(x, contrast = "pcwsConstMean", seed = 1)
#> ggcpt (changepoint detection result)
#> Method: not
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: sSIC
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369mosum_wrapper(x)
#> ggcpt (changepoint detection result)
#> Method: mosum
#> Change in: mean
#> Changepoints found: 1
#> CP convention: left
#> Penalty: threshold = 3.6342
#> Series length: 200
#>
#> Changepoints:
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 0.369
mosum_wrapper(x3, multiscale = TRUE)
#> ggcpt (changepoint detection result)
#> Method: mosum
#> Change in: mean
#> Changepoints found: 2
#> CP convention: left
#> Penalty: threshold
#> Series length: 300
#>
#> Changepoints:
#> # A tibble: 2 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 100 -0.100
#> 2 200 2.90Multiscale inference with confidence.
smuce_wrapper() implements SMUCE (Frick, Munk, and Sieling 2014), which bounds
the probability of over-estimating the number of changes at the level
alpha and returns a confidence interval for every
changepoint location (ci_lower/ci_upper);
family = "hsmuce" switches to the heteroskedastic extension
HSMUCE (Pein, Sieling, and Munk 2017),
which estimates a variance per segment:
Change in slope. cpop_wrapper()
performs exact penalised estimation of a continuous piecewise-linear
mean (Fearnhead, Maidstone, and Letchford 2019;
Fearnhead and Grose 2024):
y_slope <- cumsum(c(rep(0.4, 100), rep(-0.3, 100))) + rnorm(200)
res_cpop <- cpop_wrapper(y_slope)
tidy(res_cpop)Bayesian detection. bcp_wrapper()
implements the Barry–Hartigan product-partition model (Barry and Hartigan 1993; Erdman and Emerson
2007); bocpd_wrapper() runs Bayesian online
changepoint detection over the run-length posterior (Adams and MacKay 2007);
beast_wrapper() wraps the BEAST Bayesian model-averaging
ensemble (K. Zhao et al. 2019). The first
and third keep the locations whose posterior probability clears
prob_threshold and record it in a
posterior_prob column; BOCPD returns the maximum a
posteriori changepoint set.
Sequential and kernel nonparametrics.
cpm_wrapper() runs distribution-free sequential tests and
reports, alongside each estimated location, the
detection_time at which a stream monitor would have flagged
it (Ross 2015); kcp_wrapper()
applies kernel change point analysis to running statistics (mean,
variance, autocorrelation, correlation) (Arlot,
Celisse, and Harchaoui 2019; Cabrieto et al. 2018);
npmojo_wrapper() detects distributional changes under
serial dependence (McGonigle and Cho
2025):
Robustness to drift and dependence.
decafs_wrapper() detects abrupt changes when the signal
also drifts and the noise is autocorrelated (Romano et al. 2022); sn_wrapper()
segments a chosen parameter (mean, variance,
autocorrelation, or bivariate correlation) by self-normalisation,
avoiding long-run variance estimation entirely (Z. Zhao, Jiang, and Shao 2022);
envcpt_wrapper() reports changepoints only when a
changepoint model beats the trend and autoregressive alternatives (Beaulieu and Killick 2018):
res_env <- envcpt_wrapper(x, models = c("mean", "meancpt", "trendcpt"))
tidy(res_env)
res_env$penalty$type # criterion and winning modelHigh-dimensional and multivariate. These wrappers
take a matrix or data frame with one row per time point.
inspect_wrapper() finds sparse mean changes by projection
(Wang and Samworth 2018);
ocd_wrapper() monitors a high-dimensional stream online
(Chen, Wang, and Samworth 2022);
geomcp_wrapper() maps each observation to a distance and an
angle and segments both (Grundy, Killick, and
Mihaylov 2020); multivariate ecp input flows through
cpt_detect() unchanged. The univariate wrappers, by
contrast, reject a multi-column argument rather than silently flattening
it.
set.seed(1)
X <- cbind(a = c(rnorm(80), rnorm(80, 4)),
b = c(rnorm(80), rnorm(80, -3)),
c = rnorm(160))# Online detection: the reported locations are declaration times (the change
# plus the detection delay), in a `declared_at` column. Monte Carlo threshold
# calibration makes this the slowest wrapper, so it is shown but not run
# here. It needs at least two coordinates and rejects a univariate series.
res_ocd <- ocd_wrapper(X, mc_reps = 100)
tidy(res_ocd)tidy(cpt_detect(X, method = "ecp", seed = 1))
#> # A tibble: 1 × 2
#> cp cp_value
#> <int> <dbl>
#> 1 80 -0.590Regression structure.
strucchange_wrapper() dates Bai–Perron breaks, either in a
bare series or in the coefficients of a formula (Bai and Perron 1998, 2003; Zeileis et al.
2002); segmented_wrapper() fits a continuous
broken-line regression, so the change it reports is a kink in the trend
rather than a jump in the level (Muggeo 2003,
2008). Both carry a confidence interval for every break:
The modern PELT family.
fastcpd_wrapper() exposes the fastcpd engine,
whose family argument covers changes in the mean, the
variance, or both, as well as changes in a fitted AR/ARMA/GARCH model
(Li and Zhang 2024):
Rather than guessing one penalty, cpt_crops() computes
every optimal segmentation over a penalty interval (the CROPS
algorithm, via changepoint (Killick and
Eckley 2014)) and returns a ggcpt_path object with
its own print(), tidy(), and three
autoplot() types:
path <- cpt_crops(x3)
path
#> ggcpt_path (CROPS penalty path)
#> Change in: mean
#> Penalty range: [5.704, 57.04]
#> Series length: 300
#> Distinct segmentations: 5
#>
#> # A tibble: 5 × 3
#> penalty n_cpts cost
#> <dbl> <int> <dbl>
#> 1 6.69 2 327.
#> 2 6.27 5 307.
#> 3 6.19 8 288.
#> 4 5.76 9 282.
#> 5 5.70 10 276.
tidy(path)
#> # A tibble: 5 × 4
#> penalty n_cpts cost cpts
#> <dbl> <int> <dbl> <list>
#> 1 6.69 2 327. <int [2]>
#> 2 6.27 5 307. <int [5]>
#> 3 6.19 8 288. <int [8]>
#> 4 5.76 9 282. <int [9]>
#> 5 5.70 10 276. <int [10]>autoplot() renders any ggcpt. Options:
show_segments (fitted segment means), show_fit
(the engine’s own fitted signal, where provided — SMUCE, DeCAFS, CPOP,
segmented, bcp, BEAST), show_ci (changepoint-location
confidence intervals, where provided — SMUCE/HSMUCE, strucchange,
segmented), show_points/show_line, an
index for a date axis, and the cptline_*
styling arguments (cptline_color,
cptline_alpha, cptline_type,
cptline_linewidth). Asking for an overlay the result cannot
supply warns rather than failing silently.
autoplot(res, show_segments = TRUE, cptline_color = "firebrick",
cptline_type = "dashed", cptline_linewidth = 0.8)Multivariate results facet automatically:
The composable layers work inside any ggplot pipeline:
geom_changepoint() draws vertical rules,
geom_cpt_segment() draws segment levels,
geom_cpt_ci() draws horizontal interval whiskers, and
stat_changepoint() runs detection inside the plot.
theme_ggcpt() is a publication theme and
annotate_segments() shades alternating segments.
cp_tbl <- tidy(res)
df <- data.frame(index = seq_along(x), value = x)
ggplot(df, aes(index, value)) +
annotate_segments(cp = cp_tbl$cp, n = length(x)) +
geom_line() +
geom_changepoint(data = cp_tbl, aes(xintercept = cp), color = "red") +
geom_cpt_segment(
data = res$segments,
aes(x = start, xend = end, y = param_estimate, yend = param_estimate),
inherit.aes = FALSE, color = "darkred", linewidth = 1
) +
theme_ggcpt()ci_tbl <- tidy(res_smuce)
ci_tbl$y_pos <- min(x) - 1
ggplot(df, aes(index, value)) +
geom_line(color = "grey60") +
geom_cpt_ci(
data = ci_tbl,
aes(y = y_pos, xmin = ci_lower, xmax = ci_upper),
width = 0.6, color = "blue", inherit.aes = FALSE
) +
geom_changepoint(data = ci_tbl, aes(xintercept = cp), color = "blue")ggcptplot() and ggecpplot() are the
original one-call plots for the two classical engines:
The Bayesian engines get the field’s signature displays:
ggcpt_posterior() shows the posterior mean over the series
and the per-location changepoint probability (for bcp and
BEAST results); ggcpt_runlength() shows the BOCPD
run-length posterior as a heatmap.
Finally, ggcpt_interactive() turns any result (or any
ggplot built from one) into a plotly widget with values on
hover. The widget itself is not embedded here, only its class:
ggcpt_compare() runs several detectors on the same
series and renders them faceted (default) or overlaid, honouring
future::plan() for parallel execution;
ggcpt_compare_table() returns the tidy union of the same
runs. A method that finds nothing keeps its panel and contributes an
NA row — “no changepoints” is a result, not a missing
one.
cpt_batch() runs one detector over many series (matrix,
data frame, or list of vectors), also under future::plan(),
and returns a tidy ggcpt_batch tibble with list-columns for
the per-series changepoints and the ggcpt objects
themselves:
XB <- cbind(shifted = x, pure_noise = rnorm(200))
batch <- cpt_batch(XB, method = "pelt")
batch
#> ggcpt_batch (2 series, method: pelt)
#>
#> # A tibble: 2 × 2
#> series n_changepoints
#> <chr> <int>
#> 1 shifted 1
#> 2 pure_noise 0
tidy(batch)
#> # A tibble: 1 × 3
#> series cp cp_value
#> <chr> <int> <dbl>
#> 1 shifted 100 0.369
autoplot(batch)cpt_stability() quantifies how fragile a segmentation
is: it resamples residuals within the fitted segments, re-runs the
detector, and reports the re-detection frequency at every location — a
model-agnostic confidence signal for engines with no native
intervals:
st <- cpt_stability(x, method = "pelt", B = 30, seed = 1)
st
#> ggcpt_stability (30 bootstrap replicates, method: pelt)
#>
#> Original changepoints and their re-detection frequency:
#> # A tibble: 1 × 2
#> cp stability
#> <int> <dbl>
#> 1 100 1
autoplot(st)cpt_metrics() scores predictions against a known truth:
precision, recall and F1 under one-to-one matching, the covering metric
and adjusted Rand index in the conventions of van
den Burg and Williams (2020), Hausdorff distance, annotation
error, and matched MAE/RMSE. cpt_metrics_annotated()
averages over multiple annotators, and ggcpt_eval() draws
the agreement (true positives, false positives, and misses):
truth <- 100
pred <- tidy(res)$cp
cpt_metrics(pred, truth, n = length(x), margin = 5)
#> # A tibble: 1 × 12
#> n n_pred n_truth precision recall f1 covering hausdorff rand_index
#> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 200 1 1 1 1 1 1 0 1
#> # ℹ 3 more variables: annotation_error <int>, mae_matched <dbl>,
#> # rmse_matched <dbl>
cpt_metrics_annotated(pred, list(100, 101, 99), n = length(x))
#> # A tibble: 1 × 7
#> n n_annotators n_pred precision recall f1 covering
#> <int> <int> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 200 3 1 1 1 1 0.993cpt_simulate() (alias rcpt()) generates
series with known changepoints in mean, variance, both, or slope, under
Gaussian, Student-t, AR(1), or random-walk noise; the truth travels with
the data in the true_changepoints and
true_segments attributes. Five canonical test signals from
the literature ship ready-made: signal_blocks() (the
Donoho–Johnstone blocks signal (Donoho and
Johnstone 1994)), signal_fms(),
signal_mix(), signal_teeth(), and
signal_stairs().
sim <- cpt_simulate(300, changepoints = c(100, 200), change_in = "mean",
params = c(0, 5, 1), seed = 1)
attr(sim, "true_changepoints")
#> [1] 100 200
sim2 <- rcpt(300, changepoints = 150, params = c(0, 3), seed = 2) # the alias
attr(sim2, "true_changepoints")
#> [1] 150
signals <- list(blocks = signal_blocks(1024, seed = 1),
fms = signal_fms(500, seed = 1),
mix = signal_mix(500, seed = 1),
teeth = signal_teeth(400, seed = 1),
stairs = signal_stairs(500, seed = 1))
vapply(signals, function(s) length(attr(s, "true_changepoints")), integer(1))
#> blocks fms mix teeth stairs
#> 11 7 5 3 9blocks <- signals$blocks
ggplot(blocks, aes(index, value)) +
geom_line(color = "grey50") +
geom_vline(xintercept = attr(blocks, "true_changepoints"),
color = "blue", linewidth = 0.3) +
labs(title = "The blocks test signal with its true changepoints")cpt_cite() returns the verified reference(s) behind a
result or a method name, so a write-up can cite the right paper without
leaving R; called with no argument it returns the whole
method-to-reference table.
cpt_cite("pelt")
#> [pelt] Killick, R., Fearnhead, P. and Eckley, I. A. (2012). Optimal detection of changepoints with a linear computational cost. Journal of the American Statistical Association, 107(500), 1590-1598.
cpt_cite(res)
#> [pelt] Killick, R., Fearnhead, P. and Eckley, I. A. (2012). Optimal detection of changepoints with a linear computational cost. Journal of the American Statistical Association, 107(500), 1590-1598.This tour visited every exported function in the package. For the
framework’s design, the mathematics of the wrapped methods, and worked
analyses, see
vignette("introduction", package = "ggchangepoint"); for
method comparison and accuracy evaluation in depth, see
vignette("comparison", package = "ggchangepoint").