The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Comparison with other R packages

Data setup

Univariate mean change

# Univariate mean change
set.seed(1)
p <- 1
mean_data_1 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(400, mean = rep(50, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(2, p), sigma = diag(100, p))
)

plot.ts(mean_data_1)

Univariate mean and/or variance change

# Univariate mean and/or variance change
set.seed(1)
p <- 1
mv_data_1 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(10, p), sigma = diag(100, p))
)

plot.ts(mv_data_1)

Multivariate mean change

# Multivariate mean change
set.seed(1)
p <- 3
mean_data_3 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(400, mean = rep(50, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(2, p), sigma = diag(100, p))
)

plot.ts(mean_data_3)

Multivariate mean and/or variance change

# Multivariate mean and/or variance change
set.seed(1)
p <- 4
mv_data_3 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(10, p), sigma = diag(100, p))
)

plot.ts(mv_data_3)

Linear regression

# Linear regression
set.seed(1)
n <- 300
p <- 4
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
theta_0 <- rbind(c(1, 3.2, -1, 0), c(-1, -0.5, 2.5, -2), c(0.8, 0, 1, 2))
y <- c(
  x[1:100, ] %*% theta_0[1, ] + rnorm(100, 0, 3),
  x[101:200, ] %*% theta_0[2, ] + rnorm(100, 0, 3),
  x[201:n, ] %*% theta_0[3, ] + rnorm(100, 0, 3)
)
lm_data <- data.frame(y = y, x = x)

plot.ts(lm_data)

Logistic regression

# Logistic regression
set.seed(1)
n <- 500
p <- 4
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
theta <- rbind(rnorm(p, 0, 1), rnorm(p, 2, 1))
y <- c(
  rbinom(300, 1, 1 / (1 + exp(-x[1:300, ] %*% theta[1, ]))),
  rbinom(200, 1, 1 / (1 + exp(-x[301:n, ] %*% theta[2, ])))
)
binomial_data <- data.frame(y = y, x = x)

plot.ts(binomial_data)

Poisson regression

# Poisson regression
set.seed(1)
n <- 1100
p <- 3
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
delta <- rnorm(p)
theta_0 <- c(1, 0.3, -1)
y <- c(
  rpois(500, exp(x[1:500, ] %*% theta_0)),
  rpois(300, exp(x[501:800, ] %*% (theta_0 + delta))),
  rpois(200, exp(x[801:1000, ] %*% theta_0)),
  rpois(100, exp(x[1001:1100, ] %*% (theta_0 - delta)))
)
poisson_data <- data.frame(y = y, x = x)

plot.ts(log(poisson_data$y))

plot.ts(poisson_data[, -1])

Lasso

# Lasso
set.seed(1)
n <- 480
p_true <- 6
p <- 50
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
theta_0 <- rbind(
  runif(p_true, -5, -2),
  runif(p_true, -3, 3),
  runif(p_true, 2, 5),
  runif(p_true, -5, 5)
)
theta_0 <- cbind(theta_0, matrix(0, ncol = p - p_true, nrow = 4))
y <- c(
  x[1:80, ] %*% theta_0[1, ] + rnorm(80, 0, 1),
  x[81:200, ] %*% theta_0[2, ] + rnorm(120, 0, 1),
  x[201:320, ] %*% theta_0[3, ] + rnorm(120, 0, 1),
  x[321:n, ] %*% theta_0[4, ] + rnorm(160, 0, 1)
)
lasso_data <- data.frame(y = y, x = x)

plot.ts(lasso_data[, seq_len(p_true + 1)])

AR(3)

# AR(3)
set.seed(1)
n <- 1000
x <- rep(0, n + 3)
for (i in 1:600) {
  x[i + 3] <- 0.6 * x[i + 2] - 0.2 * x[i + 1] + 0.1 * x[i] + rnorm(1, 0, 3)
}
for (i in 601:1000) {
  x[i + 3] <- 0.3 * x[i + 2] + 0.4 * x[i + 1] + 0.2 * x[i] + rnorm(1, 0, 3)
}
ar_data <- x[-seq_len(3)]

plot.ts(ar_data)

GARCH(1, 1)

# GARCH(1, 1)
set.seed(1)
n <- 400
sigma_2 <- rep(1, n + 1)
x <- rep(0, n + 1)
for (i in seq_len(200)) {
  sigma_2[i + 1] <- 20 + 0.5 * x[i]^2 + 0.1 * sigma_2[i]
  x[i + 1] <- rnorm(1, 0, sqrt(sigma_2[i + 1]))
}
for (i in 201:400) {
  sigma_2[i + 1] <- 1 + 0.1 * x[i]^2 + 0.5 * sigma_2[i]
  x[i + 1] <- rnorm(1, 0, sqrt(sigma_2[i + 1]))
}
garch_data <- x[-1]

plot.ts(garch_data)

VAR(2)

# VAR(2)
set.seed(1)
n <- 800
p <- 2
theta_1 <- matrix(c(-0.3, 0.6, -0.5, 0.4, 0.2, 0.2, 0.2, -0.2), nrow = p)
theta_2 <- matrix(c(0.3, -0.4, 0.1, -0.5, -0.5, -0.2, -0.5, 0.2), nrow = p)
x <- matrix(0, n + 2, p)
for (i in 1:500) {
  x[i + 2, ] <- theta_1 %*% c(x[i + 1, ], x[i, ]) + rnorm(p, 0, 1)
}
for (i in 501:n) {
  x[i + 2, ] <- theta_2 %*% c(x[i + 1, ], x[i, ]) + rnorm(p, 0, 1)
}
var_data <- x[-seq_len(2), ]

plot.ts(var_data)

Univariate mean change

The true change points are 300 and 700. Some methods are plotted due to the un-retrievable change points.

(fastcpd_result <- fastcpd::fastcpd.mean(mean_data_1, r.progress = FALSE)@cp_set)
#> error: fastcpd_class_public.cc:85: Invalid family.
#> error: fastcpd_class_public.cc:99: Invalid family.
#> [1] 300 700
(CptNonPar_result <- CptNonPar::np.mojo(mean_data_1, G = floor(length(mean_data_1) / 6))$cpts)
#> [1] 300 700
strucchange::breakpoints(y ~ 1, data = data.frame(y = mean_data_1))$breakpoints
#> [1] 300 700
ecp::e.divisive(mean_data_1)$estimates
#> [1]    1  301  701 1001
(changepoint_result <- changepoint::cpt.mean(c(mean_data_1))@cpts)
#> [1]  300 1000
(breakfast_result <- breakfast::breakfast(mean_data_1)$cptmodel.list[[6]]$cpts)
#> [1] 300 700
(wbs_result <- wbs::wbs(mean_data_1)$cpt$cpt.ic$mbic.penalty)
#> [1] 300 700
mosum::mosum(c(mean_data_1), G = 40)$cpts.info$cpts
#> [1] 300 700
(fpop_result <- fpop::Fpop(mean_data_1, nrow(mean_data_1))$t.est)
#> [1]  300  700 1000
gfpop::gfpop(
  data = mean_data_1,
  mygraph = gfpop::graph(
    penalty = 2 * log(nrow(mean_data_1)) * gfpop::sdDiff(mean_data_1) ^ 2,
    type = "updown"
  ),
  type = "mean"
)$changepoints
#> [1]  300  700 1000
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mean_data_1),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mean_data_1), ncol(mean_data_1)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#> [1] 300 700
(jointseg_result <- jointseg::jointSeg(mean_data_1, K = 2)$bestBkp)
#> [1] 300 700
Rbeast::beast(
  mean_data_1, season = "none", print.progress = FALSE, quiet = TRUE
)$trend$cp
#>  [1] 701 301 NaN NaN NaN NaN NaN NaN NaN NaN
(stepR_result <- stepR::stepFit(mean_data_1, alpha = 0.5)$rightEnd)
#> [1]  300  700 1000
(cpm_result <- cpm::processStream(mean_data_1, cpmType = "Student")$changePoints)
#> [1] 299 699
(segmented_result <- segmented::stepmented(
  as.numeric(mean_data_1), npsi = 2
)$psi[, "Est."])
#> psi1.index psi2.index 
#>   298.1981   699.1524
plot(
  mcp::mcp(
    list(y ~ 1, ~ 1, ~ 1),
    data = data.frame(y = mean_data_1, x = seq_len(nrow(mean_data_1))),
    par_x = "x"
  )
)
mcp plot for univariate mean change
mcp plot for univariate mean change
plot(not::not(mean_data_1, contrast = "pcwsConstMean"))

plot(bcp::bcp(mean_data_1))
bcp plot for univariate mean change
bcp plot for univariate mean change

Univariate mean and/or variance change

The true change points are 300, 700, 1000, 1300 and 1700. Some methods are plotted due to the un-retrievable change points.

(fastcpd_result <- fastcpd::fastcpd.mv(mv_data_1, r.progress = FALSE)@cp_set)
#> error: fastcpd_class_public.cc:85: Invalid family.
#> error: fastcpd_class_public.cc:99: Invalid family.
#> [1]  300  700 1001 1300 1700
ecp::e.divisive(mv_data_1)$estimates
#> [1]    1  301  701 1001 1301 1701 2001
(changepoint_result <- changepoint::cpt.meanvar(c(mv_data_1))@cpts)
#> [1]  300 2000
(CptNonPar_result <- CptNonPar::np.mojo(mv_data_1, G = floor(length(mv_data_1) / 6))$cpts)
#> [1]  333  700 1300
(cpm_result <- cpm::processStream(mv_data_1, cpmType = "GLR")$changePoints)
#>  [1]  293  300  403  408  618  621  696 1000 1021 1024 1293 1300 1417 1693 1700
#> [16] 1981
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mv_data_1),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mv_data_1), ncol(mv_data_1)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#>   [1]  300  700  701  702  704  707  708  712  715  716  717  718  721  722
#>  [15]  723  726  727  729  731  732  734  736  740  742  744  746  748  750
#>  [29]  753  755  756  757  759  760  762  764  765  768  769  771  772  774
#>  [43]  776  777  784  785  786  789  791  792  794  797  798  799  801  802
#>  [57]  803  807  809  810  813  815  817  819  826  827  828  829  831  833
#>  [71]  835  836  837  838  840  841  842  843  845  848  849  852  854  860
#>  [85]  862  864  866  868  870  872  875  879  881  884  886  887  888  889
#>  [99]  896  897  898  899  901  903  904  905  906  909  912  913  915  917
#> [113]  919  921  922  923  927  928  932  934  936  937  940  944  945  947
#> [127]  948  949  951  956  958  959  961  962  963  964  966  967  968  972
#> [141]  974  976  978  979  986  988  990  992  995 1000 1300 1700 1702 1703
#> [155] 1704 1705 1708 1710 1712 1714 1716 1717 1718 1720 1721 1723 1725 1726
#> [169] 1727 1729 1731 1733 1735 1736 1737 1739 1742 1745 1747 1748 1752 1754
#> [183] 1756 1758 1759 1760 1766 1768 1770 1771 1773 1775 1778 1782 1784 1785
#> [197] 1790 1792 1793 1795 1796 1797 1799 1800 1802 1803 1804 1805 1806 1807
#> [211] 1808 1809 1821 1824 1825 1827 1828 1829 1833 1835 1837 1840 1841 1842
#> [225] 1848 1849 1851 1852 1854 1855 1857 1859 1860 1862 1863 1865 1867 1868
#> [239] 1876 1878 1879 1880 1882 1883 1884 1886 1887 1889 1894 1898 1899 1905
#> [253] 1906 1907 1908 1909 1912 1919 1926 1927 1928 1930 1933 1934 1935 1936
#> [267] 1938 1940 1944 1947 1950 1952 1954 1955 1956 1960 1962 1963 1965 1966
#> [281] 1967 1969 1970 1974 1976 1977 1978 1980 1985 1987 1988 1990 1997 1998
Rbeast::beast(
  mv_data_1, season = "none", print.progress = FALSE, quiet = TRUE
)$trend$cp
#>  [1] 1794 1855 1986 1301  301  703 1981 1769 1860 1834
plot(
  mcp::mcp(
    list(y ~ 1, ~ 1, ~ 1, ~ 1, ~ 1, ~ 1),
    data = data.frame(y = mv_data_1, x = seq_len(nrow(mv_data_1))),
    par_x = "x"
  )
)
mcp plot for univariate mean and/or variance change
mcp plot for univariate mean and/or variance change
plot(not::not(mv_data_1, contrast = "pcwsConstMeanVar"))

#> Press [enter] to continue

Multivariate mean change

The true change points are 300 and 700. Some methods are plotted due to the un-retrievable change points.

(fastcpd_result <- fastcpd::fastcpd.mean(mean_data_3, r.progress = FALSE)@cp_set)
#> error: fastcpd_class_public.cc:85: Invalid family.
#> error: fastcpd_class_public.cc:99: Invalid family.
#> [1] 300 700
(CptNonPar_result <- CptNonPar::np.mojo(mean_data_3, G = floor(nrow(mean_data_3) / 6))$cpts)
#> [1] 300 700
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mean_data_3),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mean_data_3), ncol(mean_data_3)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#> [1] 300 700
(jointseg_result <- jointseg::jointSeg(mean_data_3, K = 2)$bestBkp)
#> [1] 300 700
invisible(
  capture.output(
    result_Rbeast <- Rbeast::beast123(
      mean_data_3,
      metadata = list(whichDimIsTime = 1),
      season = "none"
    )
  )
)
result_Rbeast$trend$cp
#> Warning message:
#> In Rbeast::beast123(mean_data_3, metadata = list(whichDimIsTime = 1),  :
#>   WARNING: If the input data is regular and ordered in time,the times of individual datapoints are determined fully by 'metadata$startTime' and 'metadata$deltaTime'. But startTime and deltaTime are missing and a default value 1 is used for both!
#>       [,1] [,2] [,3]
#>  [1,]  301  301  701
#>  [2,]  701  701  301
#>  [3,]  NaN  225  NaN
#>  [4,]  NaN  684  NaN
#>  [5,]  NaN  NaN  NaN
#>  [6,]  NaN  NaN  NaN
#>  [7,]  NaN  NaN  NaN
#>  [8,]  NaN  NaN  NaN
#>  [9,]  NaN  NaN  NaN
#> [10,]  NaN  NaN  NaN
strucchange::breakpoints(
  cbind(y.1, y.2, y.3) ~ 1, data = data.frame(y = mean_data_3)
)$breakpoints
#> [1] 300 700
ecp::e.divisive(mean_data_3)$estimates
#> [1]    1  301  701 1001
plot(bcp::bcp(mean_data_3))
bcp plot for multivariate mean change
bcp plot for multivariate mean change

Multivariate mean and/or variance change

The true change points are 300, 700, 1000, 1300 and 1700. Some methods are plotted due to the un-retrievable change points.

(fastcpd_result <- fastcpd::fastcpd.mv(mv_data_3, r.progress = FALSE)@cp_set)
#> error: fastcpd_class_public.cc:85: Invalid family.
#> error: fastcpd_class_public.cc:99: Invalid family.
#> [1]  300  700 1000 1300 1700
ecp::e.divisive(mv_data_3)$estimates
#> [1]    1  301  701 1001 1301 1701 2001
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mv_data_3),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mv_data_3), ncol(mv_data_3)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#>   [1]  300  700  701  703  705  707  708  709  711  712  714  715  717  718
#>  [15]  720  721  723  724  726  727  729  731  733  734  736  737  739  740
#>  [29]  742  743  744  746  747  749  750  752  753  754  755  756  758  760
#>  [43]  762  763  765  766  767  769  770  772  773  774  775  777  779  780
#>  [57]  782  784  786  788  790  791  793  795  797  799  801  803  804  806
#>  [71]  808  809  810  811  813  814  816  817  818  820  821  823  825  827
#>  [85]  828  830  831  833  835  836  837  838  840  842  843  845  846  848
#>  [99]  849  850  852  853  854  855  856  858  859  860  862  863  865  866
#> [113]  868  869  871  872  874  876  877  878  879  881  883  885  887  888
#> [127]  889  891  893  894  895  897  898  900  901  903  904  906  908  909
#> [141]  911  913  914  916  917  918  920  921  923  924  925  927  928  929
#> [155]  931  932  934  936  937  938  939  941  942  943  945  946  947  949
#> [169]  950  952  954  955  956  957  958  959  961  962  964  965  967  968
#> [183]  970  972  973  974  975  977  979  981  982  984  985  986  987  988
#> [197]  990  991  992  994  995  997  999 1000 1300 1700 1702 1703 1704 1705
#> [211] 1706 1708 1709 1710 1712 1713 1714 1715 1717 1719 1721 1722 1723 1725
#> [225] 1727 1729 1730 1732 1734 1735 1737 1738 1739 1741 1742 1744 1746 1748
#> [239] 1750 1752 1753 1754 1755 1757 1758 1759 1761 1762 1763 1764 1766 1767
#> [253] 1769 1770 1771 1773 1774 1775 1777 1779 1781 1782 1783 1785 1786 1788
#> [267] 1789 1791 1793 1794 1796 1798 1800 1803 1804 1805 1806 1808 1809 1811
#> [281] 1812 1814 1815 1817 1818 1819 1821 1822 1824 1825 1827 1828 1829 1831
#> [295] 1833 1835 1836 1838 1839 1841 1843 1844 1846 1847 1848 1850 1851 1853
#> [309] 1854 1856 1857 1858 1859 1860 1862 1863 1864 1865 1867 1869 1870 1872
#> [323] 1873 1874 1876 1878 1879 1881 1882 1884 1885 1887 1889 1891 1893 1894
#> [337] 1896 1898 1899 1900 1901 1902 1904 1906 1907 1909 1911 1913 1914 1916
#> [351] 1917 1918 1919 1921 1923 1924 1925 1927 1928 1930 1932 1933 1935 1936
#> [365] 1938 1939 1941 1942 1944 1946 1948 1950 1951 1952 1954 1956 1957 1959
#> [379] 1961 1963 1965 1967 1968 1970 1972 1973 1974 1976 1977 1979 1981 1982
#> [393] 1984 1985 1987 1989 1990 1992 1993 1995 1996 1998
invisible(
  capture.output(
    result_Rbeast <- Rbeast::beast123(
      mv_data_3,
      metadata = list(whichDimIsTime = 1),
      season = "none"
    )
  )
)
result_Rbeast$trend$cp
#> Warning message:
#> In Rbeast::beast123(mv_data_3, metadata = list(whichDimIsTime = 1),  :
#>   WARNING: If the input data is regular and ordered in time,the times of individual datapoints are determined fully by 'metadata$startTime' and 'metadata$deltaTime'. But startTime and deltaTime are missing and a default value 1 is used for both!
#>       [,1] [,2] [,3] [,4]
#>  [1,]  701  301  301  710
#>  [2,] 1301 1301 1301  301
#>  [3,]  301  701  702 1301
#>  [4,]  814 1993 1829 1986
#>  [5,] 1968  767 1822  785
#>  [6,] 1994  781  810  774
#>  [7,]  761  884  845 1912
#>  [8,] 1873  755  798  792
#>  [9,] 1865  747  771  879
#> [10,] 1962  924 1700 1919

Linear regression

The true change points are 100 and 200.

(fastcpd_result <- fastcpd::fastcpd.lm(lm_data, r.progress = FALSE)@cp_set)
#> [1]  97 201
strucchange::breakpoints(y ~ . - 1, data = lm_data)$breakpoints
#> [1] 100 201
(segmented_result <- segmented::segmented(
  lm(
    y ~ . - 1,
    data.frame(y = lm_data$y, x = lm_data[, -1], index = seq_len(nrow(lm_data)))
  ),
  seg.Z = ~ index
)$psi[, "Est."])
#> [1] 233

Logistic regression

The true change point is 300.

(fastcpd_result <- fastcpd::fastcpd.binomial(binomial_data, r.progress = FALSE)@cp_set)
#> [1] 302
strucchange::breakpoints(y ~ . - 1, data = binomial_data)$breakpoints
#> [1] 297

Poisson regression

The true change points are 500, 800 and 1000.

(fastcpd_result <- fastcpd::fastcpd.poisson(poisson_data, r.progress = FALSE)@cp_set)
#> [1]  498  805 1003
strucchange::breakpoints(y ~ . - 1, data = poisson_data)$breakpoints
#> [1] 935

Lasso

The true change points are 80, 200 and 320.

(fastcpd_result <- fastcpd::fastcpd.lasso(lasso_data, r.progress = FALSE)@cp_set)
#> [1]  79 199 321
strucchange::breakpoints(y ~ . - 1, data = lasso_data)$breakpoints,
#> [1]  80 200 321

AR(3)

The true change point is 600. Some methods are plotted due to the un-retrievable change points.

(fastcpd_result <- fastcpd::fastcpd.ar(ar_data, 3, r.progress = FALSE)@cp_set)
#> [1] 614
(CptNonPar_result <- CptNonPar::np.mojo(ar_data, G = floor(length(ar_data) / 6))$cpts)
#> numeric(0)
(segmented_result <- segmented::segmented(
  lm(
    y ~ x + 1, data.frame(y = ar_data, x = seq_along(ar_data))
  ),
  seg.Z = ~ x
)$psi[, "Est."])
#> [1] 690
plot(
  mcp::mcp(
    list(y ~ 1 + ar(3), ~ 0 + ar(3)),
    data = data.frame(y = ar_data, x = seq_along(ar_data)),
    par_x = "x"
  )
)
mcp plot for AR(3)
mcp plot for AR(3)

GARCH(1, 1)

The true change point is 200.

(fastcpd_result <- fastcpd::fastcpd.garch(garch_data, c(1, 1), r.progress = FALSE)@cp_set)
#> error: fastcpd_class_public.cc:85: Invalid family.
#> error: fastcpd_class_public.cc:99: Invalid family.
#> Registered S3 method overwritten by 'quantmod':
#>   method            from
#>   as.zoo.data.frame zoo
#> [1] 205
(CptNonPar_result <- CptNonPar::np.mojo(garch_data, G = floor(length(garch_data) / 6))$cpts)
#> [1] 206
strucchange::breakpoints(x ~ 1, data = data.frame(x = garch_data))$breakpoints
#> [1] NA

VAR(2)

The true change points is 500.

(fastcpd_result <- fastcpd::fastcpd.var(
  var_data, 2, cost_adjustment = NULL, r.progress = FALSE
)@cp_set)
#> error: fastcpd_class_public.cc:85: Invalid family.
#> error: fastcpd_class_public.cc:99: Invalid family.
#> [1] 500
(VARDetect_result <- VARDetect::tbss(var_data)$cp)
#> [1] "first.brk.points:"
#>  [1] 140 196 252 308 364 420 476 532 588 644 700
#> [1] "selected lambda1:"
#> [1] 0.2107081
#> [1] "selected lambda2:"
#> [1] 0.02943525
#> [1] "second.brk.points:"
#> [1] 308 364 588
#> [1] "second.brk.points:"
#> [1] 308 476 588
#> [1] "second.brk.points:"
#> [1] 476 532
#> [1] "second.brk.points:"
#> [1] 476 532
#> [1] "second.brk.points:"
#> [1] 476 532
#> no refit for the parameter estimation
#> [1] 501

Detection comparison using well_log

well_log <- well_log[well_log > 1e5]

result <- list(
  fastcpd = fastcpd.mean(well_log, trim = 0.003)@cp_set,
  changepoint = changepoint::cpt.mean(well_log)@cpts,
  CptNonPar =
    CptNonPar::np.mojo(well_log, G = floor(length(well_log) / 6))$cpts,
  strucchange = strucchange::breakpoints(
    y ~ 1, data = data.frame(y = well_log)
  )$breakpoints,
  ecp = ecp::e.divisive(matrix(well_log))$estimates,
  breakfast = breakfast::breakfast(well_log)$cptmodel.list[[6]]$cpts,
  wbs = wbs::wbs(well_log)$cpt$cpt.ic$mbic.penalty,
  mosum = mosum::mosum(c(well_log), G = 40)$cpts.info$cpts,
  # fpop = fpop::Fpop(well_log, length(well_log))$t.est,  # meaningless
  gfpop = gfpop::gfpop(
    data = well_log,
    mygraph = gfpop::graph(
      penalty = 2 * log(length(well_log)) * gfpop::sdDiff(well_log) ^ 2,
      type = "updown"
    ),
    type = "mean"
  )$changepoints,
  InspectChangepoint = InspectChangepoint::inspect(
    well_log,
    threshold = InspectChangepoint::compute.threshold(length(well_log), 1)
  )$changepoints[, "location"],
  jointseg = jointseg::jointSeg(well_log, K = 12)$bestBkp,
  Rbeast = Rbeast::beast(
    well_log, season = "none", print.progress = FALSE, quiet = TRUE
  )$trend$cp,
  stepR = stepR::stepFit(well_log, alpha = 0.5)$rightEnd
)

package_list <- sort(names(result), decreasing = TRUE)
comparison_table <- NULL
for (package_index in seq_along(package_list)) {
  package <- package_list[[package_index]]
  comparison_table <- rbind(
    comparison_table,
    data.frame(
      change_point = result[[package]],
      package = package,
      y_offset = (package_index - 1) * 1000
    )
  )
}

most_selected <- sort(table(comparison_table$change_point), decreasing = TRUE)
most_selected <- sort(as.numeric(names(most_selected[most_selected >= 4])))
for (i in seq_len(length(most_selected) - 1)) {
  if (most_selected[i + 1] - most_selected[i] < 2) {
    most_selected[i] <- NA
    most_selected[i + 1] <- most_selected[i + 1] - 0.5
  }
}
(most_selected <- most_selected[!is.na(most_selected)])
#>  [1]    6.0  314.0  434.0  704.0  776.0 1021.0 1057.0 1347.0 1405.0 1502.0 1661.0 1842.0 2023.0 2202.0
#> [15] 2384.5 2445.0 2507.0 2567.5 2738.0 2921.0 3094.0 3251.0 3464.0 3499.0 3622.0 3709.0 3820.0 3976.0
ggplot2::ggplot() +
  ggplot2::geom_point(
    data = data.frame(x = seq_along(well_log), y = c(well_log)),
    ggplot2::aes(x = x, y = y)
  ) +
  ggplot2::geom_vline(
    xintercept = most_selected,
    color = "black",
    linetype = "dashed",
    alpha = 0.2
  ) +
  ggplot2::geom_point(
    data = comparison_table,
    ggplot2::aes(x = change_point, y = 50000 + y_offset, color = package),
    shape = 17,
    size = 1.9
  ) +
  ggplot2::geom_hline(
    data = comparison_table,
    ggplot2::aes(yintercept = 50000 + y_offset, color = package),
    linetype = "dashed",
    alpha = 0.1
  ) +
  ggplot2::coord_cartesian(
    ylim = c(50000 - 500, max(well_log) + 1000),
    xlim = c(-200, length(well_log) + 200),
    expand = FALSE
  ) +
  ggplot2::theme(
    panel.background = ggplot2::element_blank(),
    panel.border = ggplot2::element_rect(colour = "black", fill = NA),
    panel.grid.major = ggplot2::element_blank(),
    panel.grid.minor = ggplot2::element_blank()
  ) +
  ggplot2::xlab(NULL) + ggplot2::ylab(NULL)
Plot of the detection using well_log data
Plot of the detection using well_log data

Time comparison using well_log

microbenchmark_result <- microbenchmark::microbenchmark(
  fastcpd = fastcpd::fastcpd.mean(well_log, trim = 0.003, r.progress = FALSE),
  changepoint = changepoint::cpt.mean(well_log, method = "PELT"),
  CptNonPar = CptNonPar::np.mojo(well_log, G = floor(length(well_log) / 6)),
  strucchange =
    strucchange::breakpoints(y ~ 1, data = data.frame(y = well_log)),
  ecp = ecp::e.divisive(matrix(well_log)),
  breakfast = breakfast::breakfast(well_log),
  wbs = wbs::wbs(well_log),
  mosum = mosum::mosum(c(well_log), G = 40),
  fpop = fpop::Fpop(well_log, nrow(well_log)),
  gfpop = gfpop::gfpop(
    data = well_log,
    mygraph = gfpop::graph(
      penalty = 2 * log(length(well_log)) * gfpop::sdDiff(well_log) ^ 2,
      type = "updown"
    ),
    type = "mean"
  ),
  InspectChangepoint = InspectChangepoint::inspect(
    well_log,
    threshold = InspectChangepoint::compute.threshold(length(well_log), 1)
  ),
  jointseg = jointseg::jointSeg(well_log, K = 12),
  Rbeast = Rbeast::beast(
    well_log, season = "none", print.progress = FALSE, quiet = TRUE
  ),
  stepR = stepR::stepFit(well_log, alpha = 0.5),
  not = not::not(well_log, contrast = "pcwsConstMean"),
  times = 10
)
#> Unit: milliseconds
#>                expr          min           lq         mean       median           uq          max neval
#>             fastcpd 8.411284e+01 8.692226e+01 1.011440e+02 1.044509e+02 1.089672e+02    118.05842    10
#>         changepoint 3.206773e+01 3.377081e+01 6.843465e+01 3.857181e+01 5.243834e+01    244.76672    10
#>           CptNonPar 1.827381e+04 1.901094e+04 2.002752e+04 1.985180e+04 2.076803e+04  22511.59316    10
#>         strucchange 5.955079e+04 6.059315e+04 6.185727e+04 6.131291e+04 6.312073e+04  64638.93090    10
#>                 ecp 7.590543e+05 7.707573e+05 7.859080e+05 7.830752e+05 8.093015e+05 810339.52140    10
#>           breakfast 9.170992e+03 9.344041e+03 9.628236e+03 9.382078e+03 9.628663e+03  11073.79318    10
#>                 wbs 1.139078e+02 1.145472e+02 1.178167e+02 1.166746e+02 1.201676e+02    127.27064    10
#>               mosum 1.172847e+00 1.231747e+00 1.740727e+01 1.416854e+00 1.919586e+00    160.76997    10
#>                fpop 2.588228e+00 2.630407e+00 4.587742e+00 2.832556e+00 3.312986e+00     18.52067    10
#>               gfpop 5.971245e+01 6.072684e+01 6.533492e+01 6.172578e+01 6.839653e+01     87.89407    10
#>  InspectChangepoint 1.698673e+02 1.909034e+02 2.392539e+02 2.117010e+02 3.004474e+02    329.87724    10
#>            jointseg 2.000894e+01 2.136878e+01 2.551210e+01 2.167757e+01 2.403593e+01     47.98397    10
#>              Rbeast 6.533998e+02 6.625203e+02 6.783586e+02 6.792646e+02 6.875840e+02    723.45376    10
#>               stepR 2.793709e+01 2.902084e+01 4.380857e+01 3.068416e+01 3.227125e+01    164.81082    10
#>                 not 9.599763e+01 9.701856e+01 1.028601e+02 1.012292e+02 1.049974e+02    120.73529    10
ggplot2::autoplot(microbenchmark_result)
Plot of the running time using well_log data
Plot of the running time using well_log data

Acknowledgements

Appendix: all code snippets

knitr::opts_chunk$set(
  collapse = TRUE, comment = "#>", eval = TRUE, cache = FALSE,
  warning = FALSE, fig.width = 8, fig.height = 5
)
# Univariate mean change
set.seed(1)
p <- 1
mean_data_1 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(400, mean = rep(50, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(2, p), sigma = diag(100, p))
)

plot.ts(mean_data_1)
# Univariate mean and/or variance change
set.seed(1)
p <- 1
mv_data_1 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(10, p), sigma = diag(100, p))
)

plot.ts(mv_data_1)
# Multivariate mean change
set.seed(1)
p <- 3
mean_data_3 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(400, mean = rep(50, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(2, p), sigma = diag(100, p))
)

plot.ts(mean_data_3)
# Multivariate mean and/or variance change
set.seed(1)
p <- 4
mv_data_3 <- rbind(
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(100, p)),
  mvtnorm::rmvnorm(300, mean = rep(0, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(400, mean = rep(10, p), sigma = diag(1, p)),
  mvtnorm::rmvnorm(300, mean = rep(10, p), sigma = diag(100, p))
)

plot.ts(mv_data_3)
# Linear regression
set.seed(1)
n <- 300
p <- 4
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
theta_0 <- rbind(c(1, 3.2, -1, 0), c(-1, -0.5, 2.5, -2), c(0.8, 0, 1, 2))
y <- c(
  x[1:100, ] %*% theta_0[1, ] + rnorm(100, 0, 3),
  x[101:200, ] %*% theta_0[2, ] + rnorm(100, 0, 3),
  x[201:n, ] %*% theta_0[3, ] + rnorm(100, 0, 3)
)
lm_data <- data.frame(y = y, x = x)

plot.ts(lm_data)
# Logistic regression
set.seed(1)
n <- 500
p <- 4
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
theta <- rbind(rnorm(p, 0, 1), rnorm(p, 2, 1))
y <- c(
  rbinom(300, 1, 1 / (1 + exp(-x[1:300, ] %*% theta[1, ]))),
  rbinom(200, 1, 1 / (1 + exp(-x[301:n, ] %*% theta[2, ])))
)
binomial_data <- data.frame(y = y, x = x)

plot.ts(binomial_data)
# Poisson regression
set.seed(1)
n <- 1100
p <- 3
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
delta <- rnorm(p)
theta_0 <- c(1, 0.3, -1)
y <- c(
  rpois(500, exp(x[1:500, ] %*% theta_0)),
  rpois(300, exp(x[501:800, ] %*% (theta_0 + delta))),
  rpois(200, exp(x[801:1000, ] %*% theta_0)),
  rpois(100, exp(x[1001:1100, ] %*% (theta_0 - delta)))
)
poisson_data <- data.frame(y = y, x = x)

plot.ts(log(poisson_data$y))
plot.ts(poisson_data[, -1])
# Lasso
set.seed(1)
n <- 480
p_true <- 6
p <- 50
x <- mvtnorm::rmvnorm(n, rep(0, p), diag(p))
theta_0 <- rbind(
  runif(p_true, -5, -2),
  runif(p_true, -3, 3),
  runif(p_true, 2, 5),
  runif(p_true, -5, 5)
)
theta_0 <- cbind(theta_0, matrix(0, ncol = p - p_true, nrow = 4))
y <- c(
  x[1:80, ] %*% theta_0[1, ] + rnorm(80, 0, 1),
  x[81:200, ] %*% theta_0[2, ] + rnorm(120, 0, 1),
  x[201:320, ] %*% theta_0[3, ] + rnorm(120, 0, 1),
  x[321:n, ] %*% theta_0[4, ] + rnorm(160, 0, 1)
)
lasso_data <- data.frame(y = y, x = x)

plot.ts(lasso_data[, seq_len(p_true + 1)])
# AR(3)
set.seed(1)
n <- 1000
x <- rep(0, n + 3)
for (i in 1:600) {
  x[i + 3] <- 0.6 * x[i + 2] - 0.2 * x[i + 1] + 0.1 * x[i] + rnorm(1, 0, 3)
}
for (i in 601:1000) {
  x[i + 3] <- 0.3 * x[i + 2] + 0.4 * x[i + 1] + 0.2 * x[i] + rnorm(1, 0, 3)
}
ar_data <- x[-seq_len(3)]

plot.ts(ar_data)
# GARCH(1, 1)
set.seed(1)
n <- 400
sigma_2 <- rep(1, n + 1)
x <- rep(0, n + 1)
for (i in seq_len(200)) {
  sigma_2[i + 1] <- 20 + 0.5 * x[i]^2 + 0.1 * sigma_2[i]
  x[i + 1] <- rnorm(1, 0, sqrt(sigma_2[i + 1]))
}
for (i in 201:400) {
  sigma_2[i + 1] <- 1 + 0.1 * x[i]^2 + 0.5 * sigma_2[i]
  x[i + 1] <- rnorm(1, 0, sqrt(sigma_2[i + 1]))
}
garch_data <- x[-1]

plot.ts(garch_data)
# VAR(2)
set.seed(1)
n <- 800
p <- 2
theta_1 <- matrix(c(-0.3, 0.6, -0.5, 0.4, 0.2, 0.2, 0.2, -0.2), nrow = p)
theta_2 <- matrix(c(0.3, -0.4, 0.1, -0.5, -0.5, -0.2, -0.5, 0.2), nrow = p)
x <- matrix(0, n + 2, p)
for (i in 1:500) {
  x[i + 2, ] <- theta_1 %*% c(x[i + 1, ], x[i, ]) + rnorm(p, 0, 1)
}
for (i in 501:n) {
  x[i + 2, ] <- theta_2 %*% c(x[i + 1, ], x[i, ]) + rnorm(p, 0, 1)
}
var_data <- x[-seq_len(2), ]

plot.ts(var_data)
(fastcpd_result <- fastcpd::fastcpd.mean(mean_data_1, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(300, 700), tolerance = 0.2)
(CptNonPar_result <- CptNonPar::np.mojo(mean_data_1, G = floor(length(mean_data_1) / 6))$cpts)
testthat::expect_equal(CptNonPar_result, c(300, 700), tolerance = 0.2)
strucchange::breakpoints(y ~ 1, data = data.frame(y = mean_data_1))$breakpoints
#> [1] 300 700
ecp::e.divisive(mean_data_1)$estimates
#> [1]    1  301  701 1001
(changepoint_result <- changepoint::cpt.mean(c(mean_data_1))@cpts)
testthat::expect_equal(changepoint_result, c(300, 1000), tolerance = 0.2)
(breakfast_result <- breakfast::breakfast(mean_data_1)$cptmodel.list[[6]]$cpts)
testthat::expect_equal(breakfast_result, c(300, 700), tolerance = 0.2)
(wbs_result <- wbs::wbs(mean_data_1)$cpt$cpt.ic$mbic.penalty)
testthat::expect_equal(wbs_result, c(300, 700), tolerance = 0.2)
mosum::mosum(c(mean_data_1), G = 40)$cpts.info$cpts
#> [1] 300 700
(fpop_result <- fpop::Fpop(mean_data_1, nrow(mean_data_1))$t.est)
testthat::expect_equal(fpop_result, c(300, 700, 1000), tolerance = 0.2)
gfpop::gfpop(
  data = mean_data_1,
  mygraph = gfpop::graph(
    penalty = 2 * log(nrow(mean_data_1)) * gfpop::sdDiff(mean_data_1) ^ 2,
    type = "updown"
  ),
  type = "mean"
)$changepoints
#> [1]  300  700 1000
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mean_data_1),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mean_data_1), ncol(mean_data_1)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#> [1] 300 700
(jointseg_result <- jointseg::jointSeg(mean_data_1, K = 2)$bestBkp)
testthat::expect_equal(jointseg_result, c(300, 700), tolerance = 0.2)
Rbeast::beast(
  mean_data_1, season = "none", print.progress = FALSE, quiet = TRUE
)$trend$cp
#>  [1] 701 301 NaN NaN NaN NaN NaN NaN NaN NaN
(stepR_result <- stepR::stepFit(mean_data_1, alpha = 0.5)$rightEnd)
testthat::expect_equal(stepR_result, c(300, 700, 1000), tolerance = 0.2)
(cpm_result <- cpm::processStream(mean_data_1, cpmType = "Student")$changePoints)
testthat::expect_equal(cpm_result, c(299, 699), tolerance = 0.2)
(segmented_result <- segmented::stepmented(
  as.numeric(mean_data_1), npsi = 2
)$psi[, "Est."])
testthat::expect_equivalent(segmented_result, c(298, 699), tolerance = 0.2)
plot(
  mcp::mcp(
    list(y ~ 1, ~ 1, ~ 1),
    data = data.frame(y = mean_data_1, x = seq_len(nrow(mean_data_1))),
    par_x = "x"
  )
)
plot(not::not(mean_data_1, contrast = "pcwsConstMean"))
plot(bcp::bcp(mean_data_1))
(fastcpd_result <- fastcpd::fastcpd.mv(mv_data_1, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(300, 700, 1001, 1300, 1700), tolerance = 0.2)
ecp::e.divisive(mv_data_1)$estimates
#> [1]    1  301  701 1001 1301 1701 2001
(changepoint_result <- changepoint::cpt.meanvar(c(mv_data_1))@cpts)
testthat::expect_equal(changepoint_result, c(300, 2000), tolerance = 0.2)
(CptNonPar_result <- CptNonPar::np.mojo(mv_data_1, G = floor(length(mv_data_1) / 6))$cpts)
testthat::expect_equal(CptNonPar_result, c(333, 700, 1300), tolerance = 0.2)
(cpm_result <- cpm::processStream(mv_data_1, cpmType = "GLR")$changePoints)
testthat::expect_equal(cpm_result, c(293, 300, 403, 408, 618, 621, 696, 1000, 1021, 1024, 1293, 1300, 1417, 1693, 1700, 1981), tolerance = 0.2)
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mv_data_1),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mv_data_1), ncol(mv_data_1)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#>   [1]  300  700  701  702  704  707  708  712  715  716  717  718  721  722
#>  [15]  723  726  727  729  731  732  734  736  740  742  744  746  748  750
#>  [29]  753  755  756  757  759  760  762  764  765  768  769  771  772  774
#>  [43]  776  777  784  785  786  789  791  792  794  797  798  799  801  802
#>  [57]  803  807  809  810  813  815  817  819  826  827  828  829  831  833
#>  [71]  835  836  837  838  840  841  842  843  845  848  849  852  854  860
#>  [85]  862  864  866  868  870  872  875  879  881  884  886  887  888  889
#>  [99]  896  897  898  899  901  903  904  905  906  909  912  913  915  917
#> [113]  919  921  922  923  927  928  932  934  936  937  940  944  945  947
#> [127]  948  949  951  956  958  959  961  962  963  964  966  967  968  972
#> [141]  974  976  978  979  986  988  990  992  995 1000 1300 1700 1702 1703
#> [155] 1704 1705 1708 1710 1712 1714 1716 1717 1718 1720 1721 1723 1725 1726
#> [169] 1727 1729 1731 1733 1735 1736 1737 1739 1742 1745 1747 1748 1752 1754
#> [183] 1756 1758 1759 1760 1766 1768 1770 1771 1773 1775 1778 1782 1784 1785
#> [197] 1790 1792 1793 1795 1796 1797 1799 1800 1802 1803 1804 1805 1806 1807
#> [211] 1808 1809 1821 1824 1825 1827 1828 1829 1833 1835 1837 1840 1841 1842
#> [225] 1848 1849 1851 1852 1854 1855 1857 1859 1860 1862 1863 1865 1867 1868
#> [239] 1876 1878 1879 1880 1882 1883 1884 1886 1887 1889 1894 1898 1899 1905
#> [253] 1906 1907 1908 1909 1912 1919 1926 1927 1928 1930 1933 1934 1935 1936
#> [267] 1938 1940 1944 1947 1950 1952 1954 1955 1956 1960 1962 1963 1965 1966
#> [281] 1967 1969 1970 1974 1976 1977 1978 1980 1985 1987 1988 1990 1997 1998
Rbeast::beast(
  mv_data_1, season = "none", print.progress = FALSE, quiet = TRUE
)$trend$cp
#>  [1] 1794 1855 1986 1301  301  703 1981 1769 1860 1834
plot(
  mcp::mcp(
    list(y ~ 1, ~ 1, ~ 1, ~ 1, ~ 1, ~ 1),
    data = data.frame(y = mv_data_1, x = seq_len(nrow(mv_data_1))),
    par_x = "x"
  )
)
plot(not::not(mv_data_1, contrast = "pcwsConstMeanVar"))
(fastcpd_result <- fastcpd::fastcpd.mean(mean_data_3, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(300, 700), tolerance = 0.2)
(CptNonPar_result <- CptNonPar::np.mojo(mean_data_3, G = floor(nrow(mean_data_3) / 6))$cpts)
testthat::expect_equal(CptNonPar_result, c(300, 700), tolerance = 0.2)
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mean_data_3),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mean_data_3), ncol(mean_data_3)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#> [1] 300 700
(jointseg_result <- jointseg::jointSeg(mean_data_3, K = 2)$bestBkp)
testthat::expect_equal(jointseg_result, c(300, 700), tolerance = 0.2)
invisible(
  capture.output(
    result_Rbeast <- Rbeast::beast123(
      mean_data_3,
      metadata = list(whichDimIsTime = 1),
      season = "none"
    )
  )
)
result_Rbeast$trend$cp
#> Warning message:
#> In Rbeast::beast123(mean_data_3, metadata = list(whichDimIsTime = 1),  :
#>   WARNING: If the input data is regular and ordered in time,the times of individual datapoints are determined fully by 'metadata$startTime' and 'metadata$deltaTime'. But startTime and deltaTime are missing and a default value 1 is used for both!
#>       [,1] [,2] [,3]
#>  [1,]  301  301  701
#>  [2,]  701  701  301
#>  [3,]  NaN  225  NaN
#>  [4,]  NaN  684  NaN
#>  [5,]  NaN  NaN  NaN
#>  [6,]  NaN  NaN  NaN
#>  [7,]  NaN  NaN  NaN
#>  [8,]  NaN  NaN  NaN
#>  [9,]  NaN  NaN  NaN
#> [10,]  NaN  NaN  NaN
strucchange::breakpoints(
  cbind(y.1, y.2, y.3) ~ 1, data = data.frame(y = mean_data_3)
)$breakpoints
#> [1] 300 700
ecp::e.divisive(mean_data_3)$estimates
#> [1]    1  301  701 1001
plot(bcp::bcp(mean_data_3))
(fastcpd_result <- fastcpd::fastcpd.mv(mv_data_3, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(300, 700, 1000, 1300, 1700), tolerance = 0.2)
ecp::e.divisive(mv_data_3)$estimates
#> [1]    1  301  701 1001 1301 1701 2001
invisible(
  suppressMessages(
    capture.output(
      result_InspectChangepoint <- InspectChangepoint::inspect(
        t(mv_data_3),
        threshold = InspectChangepoint::compute.threshold(
          nrow(mv_data_3), ncol(mv_data_3)
        )
      )
    )
  )
)
result_InspectChangepoint$changepoints[, "location"]
#>   [1]  300  700  701  703  705  707  708  709  711  712  714  715  717  718
#>  [15]  720  721  723  724  726  727  729  731  733  734  736  737  739  740
#>  [29]  742  743  744  746  747  749  750  752  753  754  755  756  758  760
#>  [43]  762  763  765  766  767  769  770  772  773  774  775  777  779  780
#>  [57]  782  784  786  788  790  791  793  795  797  799  801  803  804  806
#>  [71]  808  809  810  811  813  814  816  817  818  820  821  823  825  827
#>  [85]  828  830  831  833  835  836  837  838  840  842  843  845  846  848
#>  [99]  849  850  852  853  854  855  856  858  859  860  862  863  865  866
#> [113]  868  869  871  872  874  876  877  878  879  881  883  885  887  888
#> [127]  889  891  893  894  895  897  898  900  901  903  904  906  908  909
#> [141]  911  913  914  916  917  918  920  921  923  924  925  927  928  929
#> [155]  931  932  934  936  937  938  939  941  942  943  945  946  947  949
#> [169]  950  952  954  955  956  957  958  959  961  962  964  965  967  968
#> [183]  970  972  973  974  975  977  979  981  982  984  985  986  987  988
#> [197]  990  991  992  994  995  997  999 1000 1300 1700 1702 1703 1704 1705
#> [211] 1706 1708 1709 1710 1712 1713 1714 1715 1717 1719 1721 1722 1723 1725
#> [225] 1727 1729 1730 1732 1734 1735 1737 1738 1739 1741 1742 1744 1746 1748
#> [239] 1750 1752 1753 1754 1755 1757 1758 1759 1761 1762 1763 1764 1766 1767
#> [253] 1769 1770 1771 1773 1774 1775 1777 1779 1781 1782 1783 1785 1786 1788
#> [267] 1789 1791 1793 1794 1796 1798 1800 1803 1804 1805 1806 1808 1809 1811
#> [281] 1812 1814 1815 1817 1818 1819 1821 1822 1824 1825 1827 1828 1829 1831
#> [295] 1833 1835 1836 1838 1839 1841 1843 1844 1846 1847 1848 1850 1851 1853
#> [309] 1854 1856 1857 1858 1859 1860 1862 1863 1864 1865 1867 1869 1870 1872
#> [323] 1873 1874 1876 1878 1879 1881 1882 1884 1885 1887 1889 1891 1893 1894
#> [337] 1896 1898 1899 1900 1901 1902 1904 1906 1907 1909 1911 1913 1914 1916
#> [351] 1917 1918 1919 1921 1923 1924 1925 1927 1928 1930 1932 1933 1935 1936
#> [365] 1938 1939 1941 1942 1944 1946 1948 1950 1951 1952 1954 1956 1957 1959
#> [379] 1961 1963 1965 1967 1968 1970 1972 1973 1974 1976 1977 1979 1981 1982
#> [393] 1984 1985 1987 1989 1990 1992 1993 1995 1996 1998
invisible(
  capture.output(
    result_Rbeast <- Rbeast::beast123(
      mv_data_3,
      metadata = list(whichDimIsTime = 1),
      season = "none"
    )
  )
)
result_Rbeast$trend$cp
#> Warning message:
#> In Rbeast::beast123(mv_data_3, metadata = list(whichDimIsTime = 1),  :
#>   WARNING: If the input data is regular and ordered in time,the times of individual datapoints are determined fully by 'metadata$startTime' and 'metadata$deltaTime'. But startTime and deltaTime are missing and a default value 1 is used for both!
#>       [,1] [,2] [,3] [,4]
#>  [1,]  701  301  301  710
#>  [2,] 1301 1301 1301  301
#>  [3,]  301  701  702 1301
#>  [4,]  814 1993 1829 1986
#>  [5,] 1968  767 1822  785
#>  [6,] 1994  781  810  774
#>  [7,]  761  884  845 1912
#>  [8,] 1873  755  798  792
#>  [9,] 1865  747  771  879
#> [10,] 1962  924 1700 1919
(fastcpd_result <- fastcpd::fastcpd.lm(lm_data, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(97, 201), tolerance = 0.2)
strucchange::breakpoints(y ~ . - 1, data = lm_data)$breakpoints
#> [1] 100 201
(segmented_result <- segmented::segmented(
  lm(
    y ~ . - 1,
    data.frame(y = lm_data$y, x = lm_data[, -1], index = seq_len(nrow(lm_data)))
  ),
  seg.Z = ~ index
)$psi[, "Est."])
testthat::expect_equivalent(segmented_result, c(233), tolerance = 0.2)
(fastcpd_result <- fastcpd::fastcpd.binomial(binomial_data, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, 302, tolerance = 0.2)
strucchange::breakpoints(y ~ . - 1, data = binomial_data)$breakpoints
#> [1] 297
(fastcpd_result <- fastcpd::fastcpd.poisson(poisson_data, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(498, 805, 1003), tolerance = 0.2)
strucchange::breakpoints(y ~ . - 1, data = poisson_data)$breakpoints
#> [1] 935
(fastcpd_result <- fastcpd::fastcpd.lasso(lasso_data, r.progress = FALSE)@cp_set)
testthat::expect_true(sum(fastcpd_result - c(79, 199, 320)) <= 1)
strucchange::breakpoints(y ~ . - 1, data = lasso_data)$breakpoints,
#> [1]  80 200 321
(fastcpd_result <- fastcpd::fastcpd.ar(ar_data, 3, r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(614), tolerance = 0.2)
(CptNonPar_result <- CptNonPar::np.mojo(ar_data, G = floor(length(ar_data) / 6))$cpts)
testthat::expect_equal(CptNonPar_result, numeric(0), tolerance = 0.2)
(segmented_result <- segmented::segmented(
  lm(
    y ~ x + 1, data.frame(y = ar_data, x = seq_along(ar_data))
  ),
  seg.Z = ~ x
)$psi[, "Est."])
testthat::expect_equivalent(segmented_result, c(690), tolerance = 0.2)
plot(
  mcp::mcp(
    list(y ~ 1 + ar(3), ~ 0 + ar(3)),
    data = data.frame(y = ar_data, x = seq_along(ar_data)),
    par_x = "x"
  )
)
(fastcpd_result <- fastcpd::fastcpd.garch(garch_data, c(1, 1), r.progress = FALSE)@cp_set)
testthat::expect_equal(fastcpd_result, c(205), tolerance = 0.2)
(CptNonPar_result <- CptNonPar::np.mojo(garch_data, G = floor(length(garch_data) / 6))$cpts)
testthat::expect_equal(CptNonPar_result, c(206), tolerance = 0.2)
strucchange::breakpoints(x ~ 1, data = data.frame(x = garch_data))$breakpoints
#> [1] NA
(fastcpd_result <- fastcpd::fastcpd.var(
  var_data, 2, cost_adjustment = NULL, r.progress = FALSE
)@cp_set)
testthat::expect_equal(fastcpd_result, c(500), tolerance = 0.2)
(VARDetect_result <- VARDetect::tbss(var_data)$cp)
testthat::expect_equal(VARDetect_result, c(501), tolerance = 0.2)
well_log <- well_log[well_log > 1e5]

result <- list(
  fastcpd = fastcpd.mean(well_log, trim = 0.003)@cp_set,
  changepoint = changepoint::cpt.mean(well_log)@cpts,
  CptNonPar =
    CptNonPar::np.mojo(well_log, G = floor(length(well_log) / 6))$cpts,
  strucchange = strucchange::breakpoints(
    y ~ 1, data = data.frame(y = well_log)
  )$breakpoints,
  ecp = ecp::e.divisive(matrix(well_log))$estimates,
  breakfast = breakfast::breakfast(well_log)$cptmodel.list[[6]]$cpts,
  wbs = wbs::wbs(well_log)$cpt$cpt.ic$mbic.penalty,
  mosum = mosum::mosum(c(well_log), G = 40)$cpts.info$cpts,
  # fpop = fpop::Fpop(well_log, length(well_log))$t.est,  # meaningless
  gfpop = gfpop::gfpop(
    data = well_log,
    mygraph = gfpop::graph(
      penalty = 2 * log(length(well_log)) * gfpop::sdDiff(well_log) ^ 2,
      type = "updown"
    ),
    type = "mean"
  )$changepoints,
  InspectChangepoint = InspectChangepoint::inspect(
    well_log,
    threshold = InspectChangepoint::compute.threshold(length(well_log), 1)
  )$changepoints[, "location"],
  jointseg = jointseg::jointSeg(well_log, K = 12)$bestBkp,
  Rbeast = Rbeast::beast(
    well_log, season = "none", print.progress = FALSE, quiet = TRUE
  )$trend$cp,
  stepR = stepR::stepFit(well_log, alpha = 0.5)$rightEnd
)

package_list <- sort(names(result), decreasing = TRUE)
comparison_table <- NULL
for (package_index in seq_along(package_list)) {
  package <- package_list[[package_index]]
  comparison_table <- rbind(
    comparison_table,
    data.frame(
      change_point = result[[package]],
      package = package,
      y_offset = (package_index - 1) * 1000
    )
  )
}

most_selected <- sort(table(comparison_table$change_point), decreasing = TRUE)
most_selected <- sort(as.numeric(names(most_selected[most_selected >= 4])))
for (i in seq_len(length(most_selected) - 1)) {
  if (most_selected[i + 1] - most_selected[i] < 2) {
    most_selected[i] <- NA
    most_selected[i + 1] <- most_selected[i + 1] - 0.5
  }
}
(most_selected <- most_selected[!is.na(most_selected)])
#>  [1]    6.0  314.0  434.0  704.0  776.0 1021.0 1057.0 1347.0 1405.0 1502.0 1661.0 1842.0 2023.0 2202.0
#> [15] 2384.5 2445.0 2507.0 2567.5 2738.0 2921.0 3094.0 3251.0 3464.0 3499.0 3622.0 3709.0 3820.0 3976.0
ggplot2::ggplot() +
  ggplot2::geom_point(
    data = data.frame(x = seq_along(well_log), y = c(well_log)),
    ggplot2::aes(x = x, y = y)
  ) +
  ggplot2::geom_vline(
    xintercept = most_selected,
    color = "black",
    linetype = "dashed",
    alpha = 0.2
  ) +
  ggplot2::geom_point(
    data = comparison_table,
    ggplot2::aes(x = change_point, y = 50000 + y_offset, color = package),
    shape = 17,
    size = 1.9
  ) +
  ggplot2::geom_hline(
    data = comparison_table,
    ggplot2::aes(yintercept = 50000 + y_offset, color = package),
    linetype = "dashed",
    alpha = 0.1
  ) +
  ggplot2::coord_cartesian(
    ylim = c(50000 - 500, max(well_log) + 1000),
    xlim = c(-200, length(well_log) + 200),
    expand = FALSE
  ) +
  ggplot2::theme(
    panel.background = ggplot2::element_blank(),
    panel.border = ggplot2::element_rect(colour = "black", fill = NA),
    panel.grid.major = ggplot2::element_blank(),
    panel.grid.minor = ggplot2::element_blank()
  ) +
  ggplot2::xlab(NULL) + ggplot2::ylab(NULL)
microbenchmark_result <- microbenchmark::microbenchmark(
  fastcpd = fastcpd::fastcpd.mean(well_log, trim = 0.003, r.progress = FALSE),
  changepoint = changepoint::cpt.mean(well_log, method = "PELT"),
  CptNonPar = CptNonPar::np.mojo(well_log, G = floor(length(well_log) / 6)),
  strucchange =
    strucchange::breakpoints(y ~ 1, data = data.frame(y = well_log)),
  ecp = ecp::e.divisive(matrix(well_log)),
  breakfast = breakfast::breakfast(well_log),
  wbs = wbs::wbs(well_log),
  mosum = mosum::mosum(c(well_log), G = 40),
  fpop = fpop::Fpop(well_log, nrow(well_log)),
  gfpop = gfpop::gfpop(
    data = well_log,
    mygraph = gfpop::graph(
      penalty = 2 * log(length(well_log)) * gfpop::sdDiff(well_log) ^ 2,
      type = "updown"
    ),
    type = "mean"
  ),
  InspectChangepoint = InspectChangepoint::inspect(
    well_log,
    threshold = InspectChangepoint::compute.threshold(length(well_log), 1)
  ),
  jointseg = jointseg::jointSeg(well_log, K = 12),
  Rbeast = Rbeast::beast(
    well_log, season = "none", print.progress = FALSE, quiet = TRUE
  ),
  stepR = stepR::stepFit(well_log, alpha = 0.5),
  not = not::not(well_log, contrast = "pcwsConstMean"),
  times = 10
)
#> Unit: milliseconds
#>                expr          min           lq         mean       median           uq          max neval
#>             fastcpd 8.411284e+01 8.692226e+01 1.011440e+02 1.044509e+02 1.089672e+02    118.05842    10
#>         changepoint 3.206773e+01 3.377081e+01 6.843465e+01 3.857181e+01 5.243834e+01    244.76672    10
#>           CptNonPar 1.827381e+04 1.901094e+04 2.002752e+04 1.985180e+04 2.076803e+04  22511.59316    10
#>         strucchange 5.955079e+04 6.059315e+04 6.185727e+04 6.131291e+04 6.312073e+04  64638.93090    10
#>                 ecp 7.590543e+05 7.707573e+05 7.859080e+05 7.830752e+05 8.093015e+05 810339.52140    10
#>           breakfast 9.170992e+03 9.344041e+03 9.628236e+03 9.382078e+03 9.628663e+03  11073.79318    10
#>                 wbs 1.139078e+02 1.145472e+02 1.178167e+02 1.166746e+02 1.201676e+02    127.27064    10
#>               mosum 1.172847e+00 1.231747e+00 1.740727e+01 1.416854e+00 1.919586e+00    160.76997    10
#>                fpop 2.588228e+00 2.630407e+00 4.587742e+00 2.832556e+00 3.312986e+00     18.52067    10
#>               gfpop 5.971245e+01 6.072684e+01 6.533492e+01 6.172578e+01 6.839653e+01     87.89407    10
#>  InspectChangepoint 1.698673e+02 1.909034e+02 2.392539e+02 2.117010e+02 3.004474e+02    329.87724    10
#>            jointseg 2.000894e+01 2.136878e+01 2.551210e+01 2.167757e+01 2.403593e+01     47.98397    10
#>              Rbeast 6.533998e+02 6.625203e+02 6.783586e+02 6.792646e+02 6.875840e+02    723.45376    10
#>               stepR 2.793709e+01 2.902084e+01 4.380857e+01 3.068416e+01 3.227125e+01    164.81082    10
#>                 not 9.599763e+01 9.701856e+01 1.028601e+02 1.012292e+02 1.049974e+02    120.73529    10
ggplot2::autoplot(microbenchmark_result)

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.