Skip to contents

Runs a changepoint detection method on a sequence and returns a tidy ggcpt result object. This is the recommended entry point for most users. See cpt_methods() for the full method table with engines and capabilities.

Usage

cpt_detect(
  x,
  method = "pelt",
  change_in = "mean",
  penalty = "MBIC",
  index = NULL,
  y = NULL,
  ...
)

Arguments

x

The series. A numeric vector for univariate methods, or a numeric matrix/data frame (rows are time points) for the multivariate methods (run subset(cpt_methods(), multivariate)$method for the list). A ts, xts, zoo or (unkeyed) tsibble is accepted directly and its time index is carried through to tidy() and autoplot(); so is a data frame together with y (and optionally index).

method

Detection method: any method in cpt_methods() whose status is "available" or "registered". Methods whose engines live in Suggests report what to install when missing; cpt_register_method() adds detectors this package does not wrap.

change_in

What to detect change in. One of "mean", "var", "meanvar", "slope", "distribution", "covariance", "network", "regression" or "seasonality". Defaults to "mean". The requested value is validated against the method's capabilities (see cpt_methods()); incompatible combinations error rather than silently running something else.

A compatible request may still be routed to the method's own native change type, because several engines have no separate estimator for the thing being asked about. That is never silent: the result's change_in records what was actually detected, so compare it with what you asked for. Measured across every method and every value its supports entry lists, six pairs are routed: not's "var" becomes "meanvar" (its variance contrast is piecewise-constant in mean and variance), cpm's "mean" and "var" both become "distribution", kcp's become "running mean" and "running var", and wbsts's "mean" becomes "var" (it detects change in the wavelet spectrum). Every other listed combination returns the change type it was asked for.

penalty

Penalty type or value. Either a character string ("MBIC", "BIC", "SIC", "AIC", "Hannan-Quinn", "None") or a numeric penalty value. Defaults to "MBIC". See the penalty-semantics section of cpt_penalty for how each engine interprets it; methods that use thresholds, significance levels, or posteriors instead of penalties ignore this argument, and "segneigh" falls back to "SIC" because changepoint does not implement MBIC for Segment Neighbourhood. Note also that the default "MBIC" is resolved to a numeric value for the numeric-penalty engines ("fpop", "cpop", "decafs"), and that value is stronger than those wrappers' own 2 * log(n) default (19.9 against 11.8 at \(n = 360\)), so cpt_detect(x, method = "decafs") can report fewer changepoints than decafs_wrapper(x) on the same series. Pass penalty explicitly to make the two entry points agree.

index

Optional time index, one value per observation (dates, say). Detection still runs on observation positions (every wrapped engine assumes an equally spaced sequence), but the index is stored on the result and threaded through tidy() (as cp_index), augment(), autoplot() and cpt_report(), so the output speaks in the user's own units. An index that is not equally spaced warns. When x is a data frame and y is given, index selects a column of that data frame instead of being a vector.

y

Column selection for the data-frame interface: cpt_detect(df, y = value, index = date, method = "pelt"). A bare column name, a string, or a column position. Only meaningful when x is a data frame; a data frame passed without y keeps its 0.4.0 meaning (one column per coordinate).

...

Additional arguments passed to the specific wrapper (see the wrapper's help page for engine-specific options). Where an argument is also derived from change_in (not's contrast, cpm's cpm_type, kcp's running_stat, sn's parameter, fastcpd's family), a value supplied here takes precedence. Check the spelling against the wrapper's help page: several engines end their own signature in ... (wbs, not, Rbeast, strucchange, segmented, fastcpd, fChange, bfast), so for those a misspelt argument name is silently discarded upstream and the engine quietly uses its default rather than reporting the typo. Every other wired method rejects an unknown argument by name.

Value

A ggcpt object: a list with changepoints (cp, cp_value), segments (seg_id, start, end, n, param_estimate), data (index, value), the method, change_in, penalty, cp_convention and runtime that produced it, the matched call, and fit, the raw upstream object. Optional slots (data_wide, regions, diagnostics, ...) appear only when an engine supplies them; new_ggcpt() documents all of them, and tidy.ggcpt(), glance.ggcpt() and augment.ggcpt() are the supported way to read one.

Scale sensitivity of the penalised change-in-mean engines

"pelt", "binseg", "segneigh" and "fpop" compare a penalty against a raw segment cost when change_in = "mean": changepoint's Normal cost assumes a noise standard deviation of 1, and fpop's lambda is an absolute penalty on the residual sum of squares. Neither rescales the data, so on a series whose noise is much wider than 1 the penalty is effectively negligible and the segmentation shatters. On 200 observations with one true changepoint in the middle and a jump of five standard deviations, "pelt" returns 1 changepoint at \(\sigma = 1\), 39 at \(\sigma = 3\) and 141 at \(\sigma = 10\). These are means over 20 draws, because a single draw is not stable here: the same three settings gave 21/75 at \(n = 100\) and 57/266 at \(n = 400\), so the effect grows with the series as well as with the noise. Three ways to avoid it, in order of convenience:

  • standardise the series first (cpt_detect(scale(x)[, 1], method = "pelt"));

  • pass a penalty on the data's own scale, for example penalty = 2 * log(length(x)) * stats::var(diff(x)) / 2;

  • use change_in = "meanvar", which estimates a variance per segment and is unaffected.

Most other engines are unaffected: SMUCE, WBS, WBS2, NOT, MOSUM, Isolate-Detect, TGUH, CPOP, "bcp", "beast" and the nonparametric and multivariate methods estimate or cancel the noise scale internally, and returned the same segmentation at a thousandth, one and a thousand times the units. Three did not, on the same series: "geomcp" runs PELT on its mapped distance and angle series and so inherits the sensitivity above; "decafs" floors its noise estimate at about 0.03, so it under-segments a series whose noise is smaller than that; and "bocpd"'s default prior is on the data's own scale. At a thousandth of the units the last two found nothing. Standardising first avoids all three.

See also

cpt_methods() for what is available and what each method can do. To get the result out: tidy.ggcpt(), glance.ggcpt(), augment.ggcpt(), summary.ggcpt() and print.ggcpt(). To draw it: autoplot.ggcpt(). For the penalty: cpt_penalty().

Examples

set.seed(2022)
x <- c(rnorm(100, 0, 1), rnorm(100, 10, 1))
result <- cpt_detect(x, method = "pelt", change_in = "mean")
result
#> 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.467
ggplot2::autoplot(result)


# A date index: detection is unchanged, but the report speaks in dates.
dates <- as.Date("2000-01-01") + 0:199
dated <- cpt_detect(x, method = "pelt", index = dates)
tidy(dated)
#> # A tibble: 1 × 3
#>      cp cp_index   cp_value
#>   <int> <date>        <dbl>
#> 1   100 2000-04-09    0.467

# The data-frame interface.
df <- data.frame(day = dates, value = x)
cpt_detect(df, y = value, index = day, method = "pelt")
#> ggcpt (changepoint detection result)
#>   Method:             pelt
#>   Change in:          mean
#>   Changepoints found: 1
#>   CP convention:      left
#>   Penalty:            MBIC
#>   Series length:      200
#>   Index:              2000-01-01 to 2000-07-18
#> 
#> Changepoints:
#> # A tibble: 1 × 3
#>      cp cp_index   cp_value
#>   <int> <date>        <dbl>
#> 1   100 2000-04-09    0.467