-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME.Rmd
More file actions
419 lines (325 loc) · 14.4 KB
/
Copy pathREADME.Rmd
File metadata and controls
419 lines (325 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
---
output:
github_document:
html_preview: false
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
message = FALSE,
warning = FALSE,
fig.path = "man/figures/README-",
out.width = "100%",
fig.alt = "ggchangepoint plot of a time series with detected changepoints"
)
```
# ggchangepoint <img src="man/figures/logo.png" align="right" height="139" alt="ggchangepoint hex sticker" />
<!-- badges: start -->
[](https://github.com/PursuitOfDataScience/ggchangepoint/actions/workflows/R-CMD-check.yaml)
[](https://CRAN.R-project.org/package=ggchangepoint)
[](https://CRAN.R-project.org/package=ggchangepoint)
[](https://lifecycle.r-lib.org/articles/stages.html#stable)
<!-- badges: end -->
> **One interface. 31 changepoint methods. Every result tidy, every result plottable.**
R has excellent changepoint packages, but each one takes its own input,
returns its own result object, and has its own idea of a plot — so
switching methods means rewriting your analysis. ggchangepoint puts them
behind one interface: `cpt_detect()` runs any of 31 detection methods,
every result comes back as the same tidy `ggcpt` object, and `autoplot()`
draws it, with confidence intervals, posteriors, penalty paths and
accuracy metrics when you need them.
## Installation
Install the released version from CRAN:
``` r
install.packages("ggchangepoint")
```
Or the development version from GitHub:
``` r
# install.packages("devtools")
devtools::install_github("PursuitOfDataScience/ggchangepoint")
```
## Quick start
```{r}
library(ggchangepoint)
library(ggplot2)
```
Generate a series with a mean shift:
```{r}
set.seed(2022)
x <- c(rnorm(100, 0, 1), rnorm(100, 10, 1))
```
Detect changepoints with the unified `cpt_detect()`:
```{r}
res <- cpt_detect(x, method = "pelt", change_in = "mean")
res
```
The result is a `ggcpt` S3 object, so the broom verbs work on it: `tidy()`
gives one row per changepoint, `glance()` a one-row model summary, and
`augment()` the original series with segment labels, fitted values and
residuals.
```{r}
tidy(res)
glance(res)
```
Visualise with `autoplot()`:
```{r}
autoplot(res)
```
## Why ggchangepoint
- **Detect with one call** — `cpt_detect(x, method = "...")` dispatches to
31 methods, from classic PELT to Bayesian online detection.
- **Tidy everywhere** — every method returns the same `ggcpt` object, with
`tidy()`, `glance()`, and `augment()`.
- **Plot everything** — `autoplot()` draws any result: changepoints,
confidence intervals, fitted signals, posteriors, multivariate facets.
- **Trust the answer** — compare methods side by side, sweep the penalty
(CROPS), bootstrap stability, and score accuracy against ground truth.
| Family | Methods |
|---|---|
| Penalised / optimal partitioning | PELT · BinSeg · SegNeigh · AMOC · FPOP · CPOP (slope) · fastcpd (mean/var/AR/ARMA/GARCH) |
| Multiscale / search | WBS · WBS2 · TGUH · NOT · MOSUM · Isolate-Detect · SMUCE · HSMUCE (the last two with CIs) |
| Nonparametric / kernel | ED-PELT · E-Divisive / E-Agglo · kernel running stats · NP-MOJO · sequential CPM · self-normalisation |
| Bayesian | bcp posteriors · online BOCPD · BEAST model averaging |
| High-dimensional / multivariate | inspect · ocd · geomcp |
| Regression breaks / robust | Bai–Perron · segmented · EnvCpt · DeCAFS |
Run `cpt_methods()` for the live table with engines and installation
status. Only three engines are required — `changepoint`, `changepoint.np`
and `ecp`; every other engine lives in `Suggests` and is loaded on demand,
so a plain install stays light.
## Unified detection across engines
`cpt_detect()` dispatches to any supported method by name:
```{r}
cpt_detect(x, method = "binseg", change_in = "mean")
cpt_detect(x, method = "wbs", change_in = "mean")
cpt_detect(x, method = "fpop", change_in = "mean")
```
Use `cpt_methods()` to see all available and planned methods with their engine
packages and installation status:
```{r}
cpt_methods()
```
## Detection with uncertainty: confidence intervals and posteriors
SMUCE (`smuce_wrapper()`, via `stepR`) delivers a confidence interval for
every changepoint location; Bai–Perron (`strucchange_wrapper()`) and
broken-line regression (`segmented_wrapper()`) do the same for regression
breaks. The intervals live in `ci_lower`/`ci_upper` columns and render with
`autoplot(show_ci = TRUE)` or the `geom_cpt_ci()` layer:
```{r, eval = requireNamespace("stepR", quietly = TRUE), fig.alt = "Series with the SMUCE step fit, changepoint rules and horizontal confidence intervals for each changepoint location"}
res_smuce <- smuce_wrapper(x)
tidy(res_smuce)
autoplot(res_smuce, show_ci = TRUE, show_fit = TRUE)
```
Read the intervals, not just the locations: the genuine shift is pinned to
a single index, while the spurious early changepoint carries an interval
nearly a hundred observations wide — exactly the distinction a bare list
of locations hides.
Bayesian engines return posterior probabilities instead: `bcp_wrapper()`
(posterior probability of a change at every location), `beast_wrapper()`
(Bayesian model averaging), and `bocpd_wrapper()` (online run-length
posterior). Two dedicated displays accompany them — `ggcpt_posterior()` and
`ggcpt_runlength()`:
```{r, eval = requireNamespace("bcp", quietly = TRUE), fig.alt = "Two-panel Bayesian display: the series with its posterior mean above, the per-location posterior changepoint probability below"}
res_bcp <- bcp_wrapper(x, seed = 1)
tidy(res_bcp)
ggcpt_posterior(res_bcp)
```
## The penalty path: CROPS
Instead of guessing one penalty, `cpt_crops()` computes **every** optimal
segmentation over a penalty range (Haynes, Eckley and Fearnhead, 2017) and
plots the elbow diagnostic, the penalty path, or the candidate segmentations
themselves:
```{r, fig.alt = "CROPS elbow diagnostic: segmentation cost against the number of changepoints"}
path <- cpt_crops(c(rnorm(100), rnorm(100, 3), rnorm(100, -1)))
path
autoplot(path) # cost elbow
```
```{r, fig.alt = "Faceted small-multiples, one panel per candidate segmentation on the CROPS penalty path"}
autoplot(path, type = "segmentations") # see the actual candidate models
```
## Compare methods
`ggcpt_compare()` runs several detectors on the same series and facets the
results, so agreement (and disagreement) is visible at a glance:
```{r, fig.alt = "One facet per detection method, each showing the same series with that method's changepoints"}
ggcpt_compare(x, methods = c("pelt", "binseg", "fpop", "wbs"))
```
For a numeric summary, use `ggcpt_compare_table()`:
```{r}
ggcpt_compare_table(x, methods = c("pelt", "binseg", "fpop", "wbs"))
```
## Batch detection and stability diagnostics
`cpt_batch()` runs one detector over many series (a matrix, data frame, or
list) and returns a tidy tibble of results — honouring `future::plan()` for
parallel execution. `cpt_stability()` bootstrap-resamples within fitted
segments and reports how often each location is re-detected, a cheap
confidence signal for engines with no native intervals:
```{r, fig.alt = "One facet per series in the batch, each with its own detected changepoints"}
X <- cbind(shifted = x, noise = rnorm(200))
batch <- cpt_batch(X, method = "pelt")
batch
autoplot(batch)
```
```{r, fig.alt = "Bootstrap re-detection frequency for each changepoint location"}
st <- cpt_stability(x, method = "pelt", B = 50, seed = 1)
st
autoplot(st)
```
## Multivariate and high-dimensional detection
The multivariate engines accept a matrix (rows are time points) directly
through `cpt_detect()` and render as faceted small-multiples:
```{r, eval = requireNamespace("InspectChangepoint", quietly = TRUE), fig.alt = "One facet per coordinate of a multivariate series, sharing the changepoints detected across coordinates"}
set.seed(1)
Xhd <- cbind(a = c(rnorm(80), rnorm(80, 3)),
b = c(rnorm(80), rnorm(80, -2)),
c = rnorm(160))
res_hd <- inspect_wrapper(Xhd)
tidy(res_hd)
autoplot(res_hd)
```
Univariate methods never silently flatten a matrix: hand one to `pelt` and
you get an error naming the multivariate alternatives instead.
`ocd_wrapper()` insists the other way — it projects across coordinates, so
it needs a matrix with at least two columns.
## Evaluation
When ground truth changepoints are known, compute accuracy metrics
(precision/recall/F1 under one-to-one matching within a tolerance
`margin`, the covering metric, Hausdorff distance, adjusted Rand index):
```{r}
# 98 matches the true changepoint at 100; 150 is a false positive
cpt_metrics(pred = c(98, 150), truth = c(100), n = 200)
```
When multiple annotation sets are available, use `cpt_metrics_annotated()`,
and visualise agreement with `ggcpt_eval()`:
```{r}
cpt_metrics_annotated(c(100), list(c(100), c(101), c(99)), n = 200, margin = 5)
```
## Data simulation
```{r}
dat <- cpt_simulate(200, changepoints = c(100), change_in = "mean",
params = c(0, 10), sd = 1)
attributes(dat)$true_changepoints
```
`rcpt()` is an alias, for readers who prefer the `r*` random-generation
naming. Built-in test signals include `signal_blocks()` (the
Donoho–Johnstone blocks signal), `signal_fms()`, `signal_mix()`,
`signal_teeth()` and `signal_stairs()`, each carrying its known
changepoints in a `true_changepoints` attribute.
## Penalty configuration
`cpt_penalty()` constructs penalty values for the methods that take a
numeric penalty. Engines differ in how they read a penalty, and
`?cpt_penalty` documents each convention:
```{r}
cpt_penalty("BIC", n = 200)
cpt_penalty("AIC", n = 200)
cpt_penalty("Manual", value = 10)
```
## Direct engine wrappers
For fine-grained control, each engine also has a dedicated wrapper that
exposes its own arguments and returns a `ggcpt` object directly. The
classic search and pruning engines:
```{r}
fpop_wrapper(x, penalty = 2 * log(200))
wbs_wrapper(x, n_intervals = 2000)
wbs2_wrapper(x)
not_wrapper(x, contrast = "pcwsConstMean")
mosum_wrapper(x)
idetect_wrapper(x)
tguh_wrapper(x)
```
And the 0.4.0 wave (each behind its `Suggests` engine): `smuce_wrapper()`,
`cpop_wrapper()`, `bcp_wrapper()`, `bocpd_wrapper()`, `beast_wrapper()`,
`cpm_wrapper()`, `kcp_wrapper()`, `npmojo_wrapper()`, `decafs_wrapper()`,
`sn_wrapper()`, `inspect_wrapper()`, `ocd_wrapper()`, `geomcp_wrapper()`,
`strucchange_wrapper()`, `segmented_wrapper()`, `envcpt_wrapper()`, and
`fastcpd_wrapper()`.
```{r, eval = requireNamespace("cpop", quietly = TRUE), fig.alt = "Piecewise-linear series with the fitted broken line and a change-in-slope changepoint"}
# change-in-slope: exact penalised broken-line estimation
y_slope <- cumsum(c(rep(0.4, 100), rep(-0.3, 100))) + rnorm(200)
res_slope <- cpop_wrapper(y_slope)
autoplot(res_slope, show_fit = TRUE)
```
## Custom geoms, stats, and theming
The package provides composable ggplot2 layers for changepoint visualisation:
```{r, eval = FALSE}
library(ggplot2)
# Use geom_changepoint as a standalone layer
cp_tbl <- tidy(cpt_detect(x, method = "pelt", change_in = "mean"))
ggplot(data.frame(index = seq_along(x), value = x), aes(index, value)) +
geom_line() +
geom_changepoint(data = cp_tbl, aes(xintercept = cp), color = "red") +
theme_ggcpt()
# Use stat_changepoint to compute and draw changepoints in one step
ggplot(data.frame(index = seq_along(x), value = x), aes(index, value)) +
geom_line() +
stat_changepoint(method = "pelt", color = "red")
# Shade alternating segments between changepoints
ggplot(data.frame(index = seq_along(x), value = x), aes(index, value)) +
geom_line() +
annotate_segments(cp = cp_tbl$cp, n = length(x))
# Draw each segment's estimated level with geom_cpt_segment
segs <- cpt_detect(x, method = "pelt", change_in = "mean")$segments
ggplot(data.frame(index = seq_along(x), value = x), aes(index, value)) +
geom_line() +
geom_cpt_segment(data = segs,
aes(x = start, xend = end,
y = param_estimate, yend = param_estimate),
color = "blue", linewidth = 1)
# Draw confidence intervals with geom_cpt_ci (when the engine provides them)
cp_ci <- tidy(smuce_wrapper(x))
ggplot(data.frame(index = seq_along(x), value = x), aes(index, value)) +
geom_line() +
geom_cpt_ci(data = cp_ci,
aes(x = cp, xmin = ci_lower, xmax = ci_upper, y = min(x) - 1))
```
## Interactive exploration and citations
Any result — or any ggplot built from one — renders as an interactive
widget with `ggcpt_interactive()` (requires `plotly`). `cpt_cite()` returns
the methodological reference behind a result, so an analysis can cite the
right paper without leaving R:
```{r}
cpt_cite("pelt")
```
```{r, eval = FALSE}
ggcpt_interactive(res) # hover for values; requires plotly
```
## Class constructors
Advanced users can construct `ggcpt` objects directly or test for the class:
```{r, eval = FALSE}
new_ggcpt(
changepoints = tibble::tibble(cp = 100L, cp_value = 5.0),
data = tibble::tibble(index = 1:200, value = rnorm(200)),
method = "manual"
)
is_ggcpt(res)
```
## The original 0.1.0 API
`cpt_wrapper()`, `ecp_wrapper()`, `ggcptplot()` and `ggecpplot()` all
continue to work unchanged. `ecp_wrapper()` and `ggecpplot()` reach the
`ecp` engine directly, including genuine multivariate input:
```{r, eval = FALSE}
ecp_wrapper(x, algorithm = "divisive")
ggecpplot(x, algorithm = "divisive")
```
```{r}
cpt_wrapper(x)
ggcptplot(x)
```
## Additional S3 methods
The `ggcpt` class also provides:
```{r}
res <- cpt_detect(x, method = "pelt", change_in = "mean")
summary(res) # human-readable digest
as_tibble(res) # tibble of changepoints
as.data.frame(res) # data frame of changepoints
format(res) # one-line summary string
plot(res) # base-graphics fallback (delegates to autoplot)
```
## Learn more
The full reference index and rendered vignettes live at
<https://pursuitofdatascience.github.io/ggchangepoint/>. From R:
- `vignette("ggchangepoint", package = "ggchangepoint")` — feature tour
- `vignette("introduction", package = "ggchangepoint")` — the framework, in
research-paper form
- `vignette("comparison", package = "ggchangepoint")` — method comparison
and evaluation