Basic Usage
The main function of the package is mich()
, which takes
a vector or matrix of observations y
and returns a
mich.fit
object containing a variational approximation to
the posterior distribution of the change-points in the mean and/or
variance of y
. The integer valued parameters
L
, K
, and J
specify the
respective numbers of mean, variance, and joint mean and variance
change-points in y
. In each of the examples below, we
sample a series with two changes in the mean and/or variance at times
101
and 201
and use mich()
to
estimate and construct 95% credible sets around the locations of the
changes.
Mean Changes
# generate univariate data with two mean change-points
y = c(rnorm(100,0), rnorm(100,2), rnorm(100,0))
# fit two mean-scp components
fit = mich(y, L = 2)
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -242.324141949562; Converged: TRUE
#>
#> L = 2 Mean-SCP Component(s); 2 Detected Mean Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 101 100 102
#> 2 203 198 207
The summary output shows us the ELBO for the fitted model as well as
the estimated locations of the changes and the lower and upper bounds of
the level
-level credible sets around each change. Because
we called mich()
with L > 0
, the returned
mich.fit
object contains a named list
mean_model
that contains the posterior parameters for each
of the L
components in the model. The most important
element is pi_bar
, which is a T x L matrix of posterior
change-point location probabilities (for a detailed description of each
element of mean_model
, type ?mich
in the
console). We can use pi_bar
along with the function
mich_sets()
to construct change-point estimates and
credible sets.
# MAP estimates with 95% credible sets
mich_sets(fit$mean_model$pi_bar, level = 0.95)
#> $cp
#> [1] 101 203
#>
#> $sets
#> $sets[[1]]
#> [1] 100 101 102
#>
#> $sets[[2]]
#> [1] 198 199 200 201 203 204 207
The plot()
function is extended to the
mich.fit
class and uses mich_sets()
to plot
the returned MAP estimates as well as the estimated signal if
signal == TRUE
and credible sets if cs == TRUE
(note that as in the plot below, these truly are sets, not
intervals).
plot(fit, cs = TRUE, level = 0.95, signal = TRUE)
Variance Changes
# generate univariate data with two variance change-points
y = c(rnorm(100,0,10), rnorm(100,0,3), rnorm(100,0,6))
# fit two var-scp components
fit = mich(y, K = 2)
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -362.67485155576; Converged: TRUE
#>
#> K = 2 Var-SCP Component(s); 2 Detected Variance Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 100 100 104
#> 2 201 196 201
The posterior quantities are now stored in
fit$var_model$pi_bar
.
plot(fit, cs = TRUE, level = 0.95, signal = TRUE)
Mean-Variance Changes
# generate univariate data with two mean-variance change-points
y = c(rnorm(100,0,10), rnorm(100,10,3), rnorm(100,0,6))
# fit two meanvar-scp components
fit = mich(y, J = 2)
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -220.537132456086; Converged: TRUE
#>
#> J = 2 MeanVar-SCP Component(s); 2 Detected Mean-Variance Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 101 99 102
#> 2 201 199 201
The posterior quantities are now stored in
fit$meanvar_model$pi_bar
.
plot(fit, cs = TRUE, level = 0.95, signal = TRUE)
Multiple Change-Types
Note that by default L=K=J=0
, so the call
mich(y)
will fit the null model assuming no changes are
present in the mean or variance of y
. It is also possible
to fit mich()
with multiple kinds of changes, e.g. we could
rerun the last example with one of the changes misspecified as just a
mean change.
# generate univariate data with two mean-variance change-points
y = c(rnorm(100,0,10), rnorm(100,10,3), rnorm(100,0,6))
# fit one mean-scp component and meanvar-scp component
fit = mich(y, L=1, J = 1)
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -254.723737904752; Converged: TRUE
#>
#> L = 1 Mean-SCP Component(s); 1 Detected Mean Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 101 86 103
#>
#> J = 1 MeanVar-SCP Component(s); 1 Detected Mean-Variance Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 200 199 202
We see that the model uses the mean-component to fit the first change and the mean-variance component to fit the second change.
plot(fit, cs = TRUE, level = 0.95)
Multivariate Mean Changes
In the case where y
is a T x d matrix,
mich()
can detect mean changes that are shared across all
or some of the columns. The columns of y
do not need to be
independent and by default mich()
will attempt to estimate
the precision matrix of the series (see the discussion of the
fit_scale
parameter below). In the following example
y[,1]
and y[,2]
are positively correlated.
T <- 150
d <- 2
# covariance matrix
Sigma <- rbind(c(1, 0.7), c(0.7, 2))
Sigma_eigen <- eigen(Sigma)
e_vectors <- Sigma_eigen$vectors
e_values <- Sigma_eigen$values
Sigma_sd <- e_vectors %*% diag(sqrt(e_values)) %*% t(e_vectors)
# construct mean signal
mu <- c(-1, 2)
mu_t <- matrix(0, nrow = 70, ncol = d)
mu_t <- rbind(mu_t, t(sapply(1:30, function(i) mu)))
mu_t <- rbind(mu_t, matrix(0, nrow = 50, ncol = d))
# generate data
Z <- sapply(1:d, function(i) rnorm(T))
Y <- mu_t + Z %*% Sigma_sd
# fit two multivariate mean-scp components
fit = mich(Y, L = 2)
plot(fit, cs = TRUE, level = 0.95, signal = TRUE)
Selecting L
, K
, and J
In the previous section we treated the numbers of each kind of
change-point as known quantities, but more often than not we need to
estimate these parameters. One option is to set L
,
K
, and J
equal to some large numbers that
upper bound each kind of change. For example if we think there are at
most five mean changes in the data then we can set
L = 5
.
# generate univariate data with two mean change-points
y = c(rnorm(100,0), rnorm(100,2), rnorm(100,0))
# fit five mean-scp components
fit = mich(y, L = 5)
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -226.57173121568; Converged: TRUE
#>
#> L = 5 Mean-SCP Component(s); 2 Detected Mean Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 101 100 103
#> 2 201 201 201
plot(fit, cs = TRUE, level = 0.95)
Note that the model still only detected the two true change-points.
The plot below shows that this is because the extra three components we
included have very diffuse posterior distributions that do not satisfy
the detection criterion of having credible sets containing fewer than
log(T)^2
indices (see Corollary 1 of Berlind, Cappello, and Madrid
Padilla (2025)).
Auto-MICH
Alternatively, we can use the ELBO as an approximation to marginal
likelihood of the model and automatically select the L
,
K
, and J
that maximizes the ELBO. This option
is implemented in mich()
via the L_auto
,
K_auto
, and J_auto
parameters. If
L_auto == TRUE
, then mich()
searches for the
number of mean changes between L
and L_max
that maximize the EBLO.
# fit mich with L selected automatically
fit = mich(y, L_auto = TRUE, verbose = TRUE, restart = FALSE)
#> [1] "(L = 0, K = 0, J = 0): ELBO = -304.942450777235; Counter: 6"
#> [1] "(L = 1, K = 0, J = 0): ELBO = -298.331456279222; Counter: 6"
#> [1] "(L = 2, K = 0, J = 0): ELBO = -211.047722355175; Counter: 6"
#> [1] "(L = 3, K = 0, J = 0): ELBO = -215.885482079647; Counter: 6"
#> [1] "(L = 4, K = 0, J = 0): ELBO = -220.962514999129; Counter: 5"
#> [1] "(L = 5, K = 0, J = 0): ELBO = -226.200102703732; Counter: 4"
#> [1] "(L = 6, K = 0, J = 0): ELBO = -231.558781723112; Counter: 3"
#> [1] "(L = 7, K = 0, J = 0): ELBO = -237.011609035981; Counter: 2"
#> [1] "(L = 8, K = 0, J = 0): ELBO = -242.541140918896; Counter: 1"
#> [1] "(L = 9, K = 0, J = 0): ELBO = -248.136597702824; Counter: 0"
#> [1] "Merging. Merge Counter: 1"
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -211.048798898986; Converged: TRUE
#>
#> L = 2 Mean-SCP Component(s); 2 Detected Mean Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 101 100 103
#> 2 201 201 201
Once again mich()
is able to correctly identify the
mean-changes, but now setting L_auto == TRUE
results in a
model with only two components.
plot(fit, cs = TRUE, level = 0.95)
Similarly, if K_auto == TRUE
or
J_auto == TRUE
then mich()
searches for the
optimal number of variance and mean-variance components to include in
the model. It is possible to have some combination of
L_auto
, K_auto
, and/or J_auto
set
equal to true, in which case mich()
takes turns
incrementing L
, K
, and J
and
moves in the direction that results in the largest increase in the
ELBO.
# fit mich with L selected automatically
fit = mich(y, L_auto = TRUE, J_auto = TRUE, verbose = TRUE, restart = FALSE)
#> [1] "(L = 0, K = 0, J = 0): ELBO = -304.942450777235"
#> [1] "(L = 0, K = 0, J = 1): ELBO = -290.56141335325"
#> [1] "(L = 1, K = 0, J = 0): ELBO = -298.331456279222"
#> [1] "(L = 0, K = 0, J = 2): ELBO = -226.747670431813"
#> [1] "(L = 1, K = 0, J = 1): ELBO = -308.61486984484"
#> [1] "(L = 0, K = 0, J = 3): ELBO = -235.207021249792"
#> [1] "(L = 1, K = 0, J = 2): ELBO = -232.203193790049"
#> [1] "(L = 1, K = 0, J = 3): ELBO = -247.407027549628"
#> [1] "(L = 2, K = 0, J = 2): ELBO = -232.674862962078"
#> [1] "(L = 2, K = 0, J = 3): ELBO = -247.141610997682"
#> [1] "(L = 3, K = 0, J = 2): ELBO = -238.433249627662"
#> [1] "(L = 3, K = 0, J = 3): ELBO = -250.526457976407"
#> [1] "(L = 4, K = 0, J = 2): ELBO = -243.104705526495"
#> [1] "(L = 4, K = 0, J = 3): ELBO = -257.781771835369"
#> [1] "(L = 5, K = 0, J = 2): ELBO = -248.914056107144"
#> [1] "(L = 5, K = 0, J = 3): ELBO = -264.495764094317"
#> [1] "(L = 6, K = 0, J = 2): ELBO = -254.768484151035"
summary(fit, level = 0.95)
#> Univariate MICH Model:
#>
#> ELBO: -226.747670431813; Converged: TRUE
#>
#> J = 2 MeanVar-SCP Component(s); 2 Detected Mean-Variance Change-Point(s):
#> change.points lower.0.95.credible.set upper.0.95.credible.set
#> 1 101 100 103
#> 2 201 201 201
plot(fit, cs = TRUE, level = 0.95)
Lastly, mich()
accepts the integer valued parameter
increment
, which determines how many components are added
to the model as mich()
searches for the best
L
, K
, and/or J
, and
restart
, which causes mich()
to refit the
model from a null parameterization once the ELBO stops increasing, which
can help the variational algorithm escape local minima.
Priors
Prior Change-Point Location Probabilities
The priors for the change-point locations are controlled by the
pi_l
, pi_k
, and pi_j
parameters
(each corresponding the the prior locations of mean, variance, and mean
and variance changes). By default these parameters are set equal to
"weighted"
, in which case the functions
log_mean_prior()
, log_var_prior()
, and
log_meanvar_prior()
are used to calculated weighted priors
that ensure diffuse posterior distributions in the absence of any
changes in the mean and/or variance of y
(see Appendix C.2
of Berlind, Cappello, and
Madrid Padilla (2025) for a detailed discussion). Setting
pi_l
, pi_k
, or pi_j
equal to
"uniform"
will force mich()
to use the uniform
prior rep(1/T, T)
instead. The user can also provide their
own priors by setting pi_l
, pi_k
, or
pi_j
equal to a vector pi
such that
length(pi) == length(y)
and sum(pi) == 1
, in
which case pi
will be used as the prior for all of the
model components. pi
can also be a matrix of probabilities
so long as nrow(pi) == length(y)
,
all(colSums(pi) == 1) == TRUE
, and if
pi_l = pi
then ncol(pi) == L
and so on for
pi_k
and pi_j
.
Precision, Shape, and Rate Parameters
Each of the prior precision, shape, and rate parameters in the model
is set equal to 0.001
by default. The user can change these
parameters by modifying omega_l, u_k, v_k, omega_j, u_j
and
v_j
, but the sensitivity analysis in Berlind, Cappello, and Madrid
Padilla (2025) indicates that as long as these values are small,
they do not meaningfully alter the output of mich()
.
Standardization
When standardize == TRUE
, mich()
will
center and rescale the data before running the main variational
algorithm. While this step can help improve performance, because the
location of the first change-point is unknown, it does not guarantee
that y
will begin centered at zero or have unit variance.
Instead, mich()
estimates and returns an intercept
mu_0
and initial precision lambda_0
by default
(when y
is a matrix, the estimated variance-covariance
matrix is returned instead as Sigma
). If y
is
known to be centered at zero or have unit variance, than the estimation
of these parameters can be avoided by setting
fit_intecept == FALSE
and/or
fit_scale == FALSE
.
Additonal Parameters
Additional control parameters can be viewed by typing
?mich
in the console. These include the convergence
criterion tol
, a verbose
parameter that will
prompt mich()
to provide feedback as it fits the model, and
a reverse
parameter that will make mich()
fit
to y[T:1]
in place of y
which can occasionally
improve the model fit.