Skip to contents

Abstract

ggchangepoint provides a unified, tidy, ggplot2-native interface to changepoint detection in R: one dispatcher (cpt_detect()) covering 31 methods across five 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")).

set.seed(2026)
x <- c(rnorm(100, 0, 1), rnorm(100, 6, 1))    # one mean shift at t = 100
x3 <- c(rnorm(100), rnorm(100, 4), rnorm(100, 1))  # shifts at 100, 200

The result object and its methods

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.369
manual <- 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] TRUE

The 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   FALSE

The 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.015 
#> 
#> 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"
plot(res)

ggchangepoint feature tour plot

Unified detection

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.369

Anything 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.369

cpt_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             TRUE     
#> 15 hsmuce      mean (heteroskedastic, wi… stepR  avail… NA             TRUE     
#> 16 cpop        slope                      cpop   avail… NA             TRUE     
#> 17 bcp         mean (Bayesian)            bcp    avail… NA             TRUE     
#> 18 bocpd       mean (Bayesian online)     ocp    avail… NA             TRUE     
#> 19 beast       mean/trend (Bayesian)      Rbeast avail… NA             TRUE     
#> 20 cpm         distribution (sequential)  cpm    avail… NA             TRUE     
#> 21 kcp         running statistics (kerne… kcpRS  avail… NA             TRUE     
#> 22 npmojo      distribution (multivariat… CptNo… avail… NA             TRUE     
#> 23 decafs      mean (drift + AR noise)    DeCAFS avail… NA             TRUE     
#> 24 sn          mean, var, acf, correlati… SNSeg  avail… NA             TRUE     
#> 25 inspect     mean (high-dimensional)    Inspe… avail… NA             TRUE     
#> 26 ocd         mean (high-dimensional, o… ocd    avail… NA             TRUE     
#> 27 geomcp      distribution (multivariat… chang… avail… NA             TRUE     
#> 28 strucchange mean, regression (with CI… struc… avail… NA             TRUE     
#> 29 segmented   slope (with CIs)           segme… avail… NA             TRUE     
#> 30 envcpt      mean/trend vs autocorrela… EnvCpt avail… NA             TRUE     
#> 31 fastcpd     mean, var, meanvar, AR/AR… fastc… avail… NA             TRUE     
#> 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… when on CRAN   NA

cpt_penalty() constructs standard penalty values for the engines that take numeric penalties; see its help for the per-engine penalty semantics.

cpt_penalty("BIC", n = 200)
#> [1] 5.298317
cpt_penalty("MBIC", n = 200)
#> [1] 10.59663
cpt_penalty("Manual", value = 10)
#> [1] 10

Engine wrappers

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.

The classical core (0.1.0)

cpt_wrapper() and ecp_wrapper() are the original interface to the changepoint/changepoint.np (Killick et al. 2012; Killick and Eckley 2014; Haynes et al. 2017) and ecp (Matteson and James 2014; James and Matteson 2014) engines; they return bare tibbles for backward compatibility.

cpt_wrapper(x, change_in = "mean", cp_method = "PELT")
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369
ecp_wrapper(x, algorithm = "divisive", seed = 1)
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <dbl>    <dbl>
#> 1   101     7.22

The search and pruning wave (0.2.0)

Seven multiscale, randomised, and functional-pruning engines (Fryzlewicz 2014, 2020, 2022; Baranowski et al. 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.369
wbs_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.369
wbs2_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.369
not_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.369
mosum_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.90
idetect_wrapper(x, seed = 1)
#> ggcpt (changepoint detection result)
#>   Method:          idetect 
#>   Change in:       mean 
#>   Changepoints found: 1 
#>   CP convention:   left 
#>   Penalty:         threshold 
#>   Series length:   200 
#> 
#> Changepoints:
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369
tguh_wrapper(x)
#> ggcpt (changepoint detection result)
#>   Method:          tguh 
#>   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.369

The engine wave (0.4.0)

Multiscale inference with confidence. smuce_wrapper() implements SMUCE (Frick et al. 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 et al. 2017), which estimates a variance per segment:

res_smuce <- smuce_wrapper(x, alpha = 0.5)
tidy(res_smuce)
#> # A tibble: 1 × 4
#>      cp cp_value ci_lower ci_upper
#>   <int>    <dbl>    <int>    <int>
#> 1   100    0.369      100      100

Change in slope. cpop_wrapper() performs exact penalised estimation of a continuous piecewise-linear mean (Fearnhead et al. 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)
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100     39.5

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 (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.

res_bcp <- bcp_wrapper(x, seed = 1)
tidy(res_bcp)
#> # A tibble: 1 × 3
#>      cp cp_value posterior_prob
#>   <int>    <dbl>          <dbl>
#> 1   100    0.369              1
res_bocpd <- bocpd_wrapper(x)
tidy(res_bocpd)
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369
res_beast <- beast_wrapper(x, seed = 1)
tidy(res_beast)
#> # A tibble: 1 × 3
#>      cp cp_value posterior_prob
#>   <int>    <dbl>          <dbl>
#> 1   100    0.369              1

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 et al. 2019; Cabrieto et al. 2018); npmojo_wrapper() detects distributional changes under serial dependence (McGonigle and Cho 2025):

tidy(cpm_wrapper(x, cpm_type = "Mann-Whitney"))
#> # A tibble: 3 × 3
#>      cp cp_value detection_time
#>   <int>    <dbl>          <int>
#> 1    23   -1.39              30
#> 2    50    0.426             67
#> 3   100    0.369            104
tidy(kcp_wrapper(x, running_stat = "mean", nperm = 100, seed = 1))
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   101     7.22
tidy(npmojo_wrapper(x))
#> # A tibble: 2 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369
#> 2   122    8.13

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 (Zhao et al. 2022); envcpt_wrapper() reports changepoints only when a changepoint model beats the trend and autoregressive alternatives (Beaulieu and Killick 2018):

tidy(decafs_wrapper(x))
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369
tidy(sn_wrapper(x3, parameter = "mean"))
#> # A tibble: 2 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100   -0.100
#> 2   199    5.53
res_env <- envcpt_wrapper(x, models = c("mean", "meancpt", "trendcpt"))
tidy(res_env)
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369
res_env$penalty$type   # criterion and winning model
#> [1] "AIC: meancpt"

High-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 et al. 2022); geomcp_wrapper() maps each observation to a distance and an angle and segments both (Grundy et al. 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))
res_hd <- inspect_wrapper(X)
tidy(res_hd)
#> # A tibble: 1 × 3
#>      cp cp_value strength
#>   <int>    <dbl>    <dbl>
#> 1    80   -0.590     30.8
# 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(geomcp_wrapper(X))
#> # A tibble: 0 × 2
#> # ℹ 2 variables: cp <int>, cp_value <dbl>
tidy(cpt_detect(X, method = "ecp", seed = 1))
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1    80   -0.590

Regression 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:

tidy(strucchange_wrapper(x))
#> # A tibble: 1 × 4
#>      cp cp_value ci_lower ci_upper
#>   <int>    <dbl>    <int>    <int>
#> 1   100    0.369       99      101
tidy(segmented_wrapper(y_slope, npsi = 1, seed = 1))
#> # A tibble: 1 × 4
#>      cp cp_value ci_lower ci_upper
#>   <int>    <dbl>    <int>    <int>
#> 1   100     39.5       98      101

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):

tidy(fastcpd_wrapper(x, family = "mean"))
#> # A tibble: 1 × 2
#>      cp cp_value
#>   <int>    <dbl>
#> 1   100    0.369

The penalty path

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(path)                          # the cost elbow

ggchangepoint feature tour plot

autoplot(path, type = "path")           # changepoints vs penalty

ggchangepoint feature tour plot

autoplot(path, type = "segmentations")  # the candidate models themselves

ggchangepoint feature tour plot

The visualisation layer

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)

ggchangepoint feature tour plot

autoplot(res_smuce, show_ci = TRUE, show_fit = TRUE)

ggchangepoint feature tour plot

Multivariate results facet automatically:

autoplot(res_hd)

ggchangepoint feature tour plot

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()

ggchangepoint feature tour plot

ggplot(df, aes(index, value)) +
  geom_line() +
  stat_changepoint(method = "pelt", color = "blue")

ggchangepoint feature tour plot

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")

ggchangepoint feature tour plot

ggcptplot() and ggecpplot() are the original one-call plots for the two classical engines:

ggcptplot(x, change_in = "mean", cp_method = "PELT")

ggchangepoint feature tour plot

ggecpplot(x, algorithm = "divisive", seed = 1)

ggchangepoint feature tour plot

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.

ggchangepoint feature tour plot

ggcpt_runlength(res_bocpd)

ggchangepoint feature tour plot

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:

class(ggcpt_interactive(res))   # requires plotly
#> [1] "plotly"     "htmlwidget"

Comparing methods

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.

ggcpt_compare(x, methods = c("pelt", "binseg", "amoc"))

ggchangepoint feature tour plot

ggcpt_compare(x, methods = c("pelt", "binseg"), layout = "overlay")

ggchangepoint feature tour plot

ggcpt_compare_table(x, methods = c("pelt", "binseg", "amoc"))
#> # A tibble: 3 × 3
#>   method    cp cp_value
#>   <chr>  <int>    <dbl>
#> 1 pelt     100    0.369
#> 2 binseg   100    0.369
#> 3 amoc     100    0.369

Batch detection and stability

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)

ggchangepoint feature tour plot

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)

ggchangepoint feature tour plot

Evaluation against ground truth

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.993
ggcpt_eval(pred, truth, x, margin = 5)

ggchangepoint feature tour plot

Simulation and canonical signals

cpt_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      9
blocks <- 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")

ggchangepoint feature tour plot

Citing the methodology

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.

Closing note

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").

References

Adams, Ryan Prescott, and David JC MacKay. 2007. “Bayesian Online Changepoint Detection.” arXiv Preprint arXiv:0710.3742.
Anastasiou, Andreas, and Piotr Fryzlewicz. 2022. “Detecting Multiple Generalized Change-Points by Isolating Single Ones.” Metrika 85: 141–74. https://doi.org/10.1007/s00184-021-00821-6.
Arlot, Sylvain, Alain Celisse, and Zaid Harchaoui. 2019. “A Kernel Multiple Change-Point Algorithm via Model Selection.” Journal of Machine Learning Research 20 (162): 1–56.
Bai, Jushan, and Pierre Perron. 1998. “Estimating and Testing Linear Models with Multiple Structural Changes.” Econometrica 66 (1): 47–78.
Bai, Jushan, and Pierre Perron. 2003. “Computation and Analysis of Multiple Structural Change Models.” Journal of Applied Econometrics 18 (1): 1–22.
Baranowski, Rafał, Yining Chen, and Piotr Fryzlewicz. 2019. “Narrowest-over-Threshold Detection of Multiple Change Points and Change-Point-Like Features.” Journal of the Royal Statistical Society Series B 81 (3): 649–72. https://doi.org/10.1111/rssb.12322.
Barry, Daniel, and John A Hartigan. 1993. “A Bayesian Analysis for Change Point Problems.” Journal of the American Statistical Association 88 (421): 309–19.
Beaulieu, Claudie, and Rebecca Killick. 2018. “Distinguishing Trends and Shifts from Memory in Climate Data.” Journal of Climate 31 (23): 9519–43.
Cabrieto, Jedelyn, Janne Adolf, Francis Tuerlinckx, Peter Kuppens, and Eva Ceulemans. 2018. “Detecting Long-Lived Autodependency Changes in a Multivariate System via Change Point Detection and Regime Switching Models.” Scientific Reports 8: 15637.
Chen, Yudong, Tengyao Wang, and Richard J Samworth. 2022. “High-Dimensional, Multiscale Online Changepoint Detection.” Journal of the Royal Statistical Society: Series B 84 (1): 234–66.
Donoho, David L, and John M Johnstone. 1994. “Ideal Spatial Adaptation by Wavelet Shrinkage.” Biometrika 81 (3): 425–55. https://doi.org/10.1093/biomet/81.3.425.
Eichinger, Birte, and Claudia Kirch. 2018. “A MOSUM Procedure for the Estimation of Multiple Random Change Points.” Bernoulli 24 (1): 526–64. https://doi.org/10.3150/16-BEJ887.
Erdman, Chandra, and John W Emerson. 2007. “Bcp: An r Package for Performing a Bayesian Analysis of Change Point Problems.” Journal of Statistical Software 23 (3): 1–13.
Fearnhead, Paul, and Daniel Grose. 2024. “Cpop: Detecting Changes in Piecewise-Linear Signals.” Journal of Statistical Software 109 (7): 1–30.
Fearnhead, Paul, Robert Maidstone, and Adam Letchford. 2019. “Detecting Changes in Slope with an L0 Penalty.” Journal of Computational and Graphical Statistics 28 (2): 265–75.
Frick, Klaus, Axel Munk, and Hannes Sieling. 2014. “Multiscale Change Point Inference.” Journal of the Royal Statistical Society: Series B 76 (3): 495–580.
Fryzlewicz, Piotr. 2014. “Wild Binary Segmentation for Multiple Change-Point Detection.” The Annals of Statistics 42 (6): 2243–81. https://doi.org/10.1214/14-AOS1245.
Fryzlewicz, Piotr. 2020. “Detecting Multiple Change-Point Features via Narrowest-over-Threshold.” Journal of the Royal Statistical Society Series B 82 (5): 1377–418.
Fryzlewicz, Piotr. 2022. “Tail-Greedy Bottom-up Data Decompositions and Fast Multiple Change-Point Detection.” The Annals of Statistics 50 (5): 2721–61.
Grundy, Thomas, Rebecca Killick, and Gueorgui Mihaylov. 2020. “High-Dimensional Changepoint Detection via a Geometrically Inspired Mapping.” Statistics and Computing 30: 1155–66.
Haynes, Kaylea, Paul Fearnhead, and Idris A Eckley. 2017. “A Computationally Efficient Nonparametric Approach for Changepoint Detection.” Statistics and Computing 27 (5): 1313–29. https://doi.org/10.1007/s11222-016-9687-5.
James, Nicholas A, and David S Matteson. 2014. “Ecp: An R Package for Nonparametric Multiple Change Point Analysis of Multivariate Data.” Journal of Statistical Software 62 (7): 1–25. https://doi.org/10.18637/jss.v062.i07.
Killick, Rebecca, and Idris Eckley. 2014. “Changepoint: An R Package for Changepoint Analysis.” Journal of Statistical Software 58 (3): 1–19. https://doi.org/10.18637/jss.v058.i03.
Killick, Rebecca, Paul Fearnhead, and Idris A Eckley. 2012. “Optimal Detection of Changepoints with a Linear Computational Cost.” Journal of the American Statistical Association 107 (500): 1590–98. https://doi.org/10.1080/01621459.2012.737745.
Li, Xingchi, and Xianyang Zhang. 2024. “Fastcpd: Fast Change Point Detection in r.” arXiv Preprint arXiv:2404.05933.
Maidstone, Robert, Toby Hocking, Guillem Rigaill, and Paul Fearnhead. 2017. “On Optimal Multiple Changepoint Algorithms for Large Data.” Statistics and Computing 27 (2): 519–33. https://doi.org/10.1007/s11222-016-9636-3.
Matteson, David S, and Nicholas A James. 2014. “A Nonparametric Approach for Multiple Change Point Analysis of Multivariate Data.” Journal of the American Statistical Association 109 (505): 334–45.
McGonigle, Euan T, and Haeran Cho. 2025. “Nonparametric Data Segmentation in Multivariate Time Series via Joint Characteristic Functions.” Biometrika 112 (2): asaf024.
Muggeo, Vito MR. 2003. “Estimating Regression Models with Unknown Break-Points.” Statistics in Medicine 22 (19): 3055–71.
Muggeo, Vito MR. 2008. “Segmented: An r Package to Fit Regression Models with Broken-Line Relationships.” R News 8 (1): 20–25.
Pein, Florian, Hannes Sieling, and Axel Munk. 2017. “Heterogeneous Change Point Inference.” Journal of the Royal Statistical Society: Series B 79 (4): 1207–27.
Robinson, David. 2017. “Broom: An R Package for Converting Statistical Analysis Objects into Tidy Data Frames.” Journal of Open Source Software 2 (19): 401. https://doi.org/10.21105/joss.00401.
Romano, Gaetano, Guillem Rigaill, Vincent Runge, and Paul Fearnhead. 2022. “Detecting Abrupt Changes in the Presence of Local Fluctuations and Autocorrelated Noise.” Journal of the American Statistical Association 117 (540): 2147–62.
Ross, Gordon J. 2015. “Parametric and Nonparametric Sequential Change Detection in r: The Cpm Package.” Journal of Statistical Software 66 (3): 1–20.
van den Burg, Gerrit J J, and Christopher K I Williams. 2020. “An Evaluation of Change Point Detection Algorithms.” arXiv Preprint arXiv:2003.06222, ahead of print. https://doi.org/10.48550/arXiv.2003.06222.
Wang, Tengyao, and Richard J Samworth. 2018. “High Dimensional Change Point Estimation via Sparse Projection.” Journal of the Royal Statistical Society: Series B 80 (1): 57–83.
Wickham, Hadley. 2016. Ggplot2: Elegant Graphics for Data Analysis. Springer. https://doi.org/10.1007/978-3-319-24277-4.
Zeileis, Achim, Friedrich Leisch, Kurt Hornik, and Christian Kleiber. 2002. “Strucchange: An r Package for Testing for Structural Change in Linear Regression Models.” Journal of Statistical Software 7 (2): 1–38.
Zhao, Kaiguang, Michael A Wulder, Tongxi Hu, et al. 2019. “Detecting Change-Point, Trend, and Seasonality in Satellite Time Series Data to Track Abrupt Changes and Nonlinear Dynamics: A Bayesian Ensemble Algorithm.” Remote Sensing of Environment 232: 111181.
Zhao, Zifeng, Feiyu Jiang, and Xiaofeng Shao. 2022. “Segmenting Time Series via Self-Normalisation.” Journal of the Royal Statistical Society: Series B 84 (5): 1699–725.