| Title: | A Splicing Approach to the Inverse Problem of L0 Trend Filtering |
|---|---|
| Description: | Trend filtering is a widely used nonparametric method for knot detection. This package provides an efficient solution for L0 trend filtering, avoiding the traditional methods of using Lagrange duality or Alternating Direction Method of Multipliers algorithms. It employ a splicing approach that minimizes L0-regularized sparse approximation by transforming the L0 trend filtering problem. The package excels in both efficiency and accuracy of trend estimation and changepoint detection in segmented functions. References: Wen et al. (2020) <doi:10.18637/jss.v094.i04>; Zhu et al. (2020)<doi:10.1073/pnas.2014241117>; Wen et al. (2023) <doi:10.1287/ijoc.2021.0313>. |
| Authors: | Tianhao Wang [aut, cre], Canhong Wen [aut] |
| Maintainer: | Tianhao Wang <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.1.0 |
| Built: | 2026-06-04 08:25:49 UTC |
| Source: | https://github.com/cran/L0TFinv |
Trend filtering is a typical method for nonparametric regression.
The commonly used trend filtering models is the L1 trend filtering model based on the difference matrix , as illustrated below.
L0 trend filtering has a advantage over other trend filtering methods, especially in the detection of change points.
The expression for L0 trend filtering is as follows:
We explore transforming the problem into a L0-regularized sparse format by introducing an artificial design matrix that corresponds to the difference matrix, thereby reformulating the L0 trend filtering problem into the following format.
In our practical approach, we consider the maximum number of change points as a constraint, transforming the aforementioned L0 penalty problem into the following L0 constraint problem.
For such L0 constraint problems , we employ a splicing-based approach to design algorithms for processing.
This package has the following seven main methods:
Generate or matrix.
Simplify the calculation of the inverse matrix of for the cases where or , which is frequently used in splicing algorithms.
Fit a piecewise constant or piecewise linear estimated trend with a given number of change points.
Fit a piecewise constant or piecewise linear estimated trend with a maximum number of change points, and select the optimal estimated trend using appropriate information criteria.
Generate piecewise constant or piecewise linear data.
Print a summary of the trend estimation results.
Plot a summary of the trend estimation results.
_PACKAGE
In previous studies, algorithms solving trend filtering problems necessitate the computation of .
When is large, just fitting the matrix into memory becomes an issue.
In L0 trend filtering , the positions of non-zero elements in the L0 norm correspond with the locations of change points.
We consider two subsets: the active set for non-zero elements and the inactive set for zero elements.
Despite this, computing remains a task involving a substantial matrix.
Due to the connection between L0 constraint problems and L0 penalty problems, and considering that the sparsity of is is more meaningful in practical applications than the selection of the hyperparameter .
We focus on the constraint that reflects our aim to achieve an estimated trend with a given number of change points.
So we transform the L0 penalty problem into the L0 constraint problem .
Kim SJ, Koh K, Boyd SP and Gorinevsky DM. L1 Trend Filtering. Society for Industrial and Applied Mathematics (2009).
Wen C, Wang X and Zhang A. L0 Trend Filtering. INFORMS Journal on Computing (2023).
Extract the coefficients of the estimated trends under the constraint of a given number of change points.
## S3 method for class 'L0TFinvfix' coef(object, k = NULL, ...) ## S3 method for class 'L0TFinvopt' coef(object, k = NULL, ...)## S3 method for class 'L0TFinvfix' coef(object, k = NULL, ...) ## S3 method for class 'L0TFinvopt' coef(object, k = NULL, ...)
object |
The output of L0TFinvfix or L0TFinvopt |
k |
The given number of change points |
... |
ignore |
Estimated parameters and trends for L0TFinvfix or L0TFinvopt models with specified numbers of change points
This function generates a matrix for computing differences of a certain order, useful in numerical methods and for creating specific matrix patterns.
DiffMat(n, q)DiffMat(n, q)
n |
The number of data points |
q |
The order of the difference |
A matrix with dimensions by , whose elements correspond to the combinatorial values of .
Mat1 <- DiffMat(n = 10, q = 0) print(Mat1) Mat2 <- DiffMat(n = 15, q = 1) print(Mat2) Mat3 <- DiffMat(n = 15, q = 2) print(Mat3)Mat1 <- DiffMat(n = 10, q = 0) print(Mat1) Mat2 <- DiffMat(n = 15, q = 1) print(Mat2) Mat3 <- DiffMat(n = 15, q = 2) print(Mat3)
Fit the input data points to a piecewise constant or piecewise linear trend with a given number of change points.
L0TFinv.fix(y = y, k = k, q = q, first = 0, last = 1)L0TFinv.fix(y = y, k = k, q = q, first = 0, last = 1)
y |
The input data points |
k |
The given number of change points |
q |
0 or 1. Correspond to a piecewise constant or piecewise linear trend |
first |
The value ranges from 0 to 1. Represent the minimum percentile point where a change point may occur. If 'first' = 0.01, it means that change points cannot appear in the first 1% of the data points. If 'first' = 0, there is no constraint on the position of the change point. |
last |
The value ranges from 0 to 1. Represent the maximum percentile point where a change point may occur. If 'last' = 0.99, it means that change points cannot appear in the last 1% of the data points. If 'last' = 1, there is no constraint on the position of the change point. |
An S3 object of type "L0TFinvfix". A list containing the fitted trend results:
sic |
Information criterion value with a penalty term of |
bic |
Information criterion value with a penalty term of |
mse |
The mean square error between the fitted trend and the input data |
y |
The input data points |
betak |
The fitted |
yk |
The fitted trend with the number of change points being |
Ak |
The set of position indicators of the fitted change points with the number of change points being |
beta.all |
A data frame with dimensions |
y.all |
A data frame with dimensions |
A.all |
A list of length |
tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 350, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.fix(y=BlocksData$y, k=5, q=0, first=0.01, last=1) print(res$Ak) print(BlocksData$setA) plot(BlocksData$x, BlocksData$y, xlab="", ylab="") lines(BlocksData$x, BlocksData$y0, col = "red") lines(BlocksData$x, res$yk, col = "lightgreen") tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85) h1 = c(-1, 5, 3, 0, -1, 2) a0 = -10 WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.fix(y=WaveData$y, k=5, q=1, first=0, last=0.99) print(res1$Ak) print(WaveData$setA) plot(WaveData$x, WaveData$y, xlab="", ylab="") lines(WaveData$x, WaveData$y0, col = "red") lines(WaveData$x, res1$yk, col = "lightgreen")tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 350, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.fix(y=BlocksData$y, k=5, q=0, first=0.01, last=1) print(res$Ak) print(BlocksData$setA) plot(BlocksData$x, BlocksData$y, xlab="", ylab="") lines(BlocksData$x, BlocksData$y0, col = "red") lines(BlocksData$x, res$yk, col = "lightgreen") tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85) h1 = c(-1, 5, 3, 0, -1, 2) a0 = -10 WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.fix(y=WaveData$y, k=5, q=1, first=0, last=0.99) print(res1$Ak) print(WaveData$setA) plot(WaveData$x, WaveData$y, xlab="", ylab="") lines(WaveData$x, WaveData$y0, col = "red") lines(WaveData$x, res1$yk, col = "lightgreen")
Fit the input data points to a piecewise constant or piecewise linear trend with optimal change points.
L0TFinv.opt(y = y, kmax = kmax, q = q, first = 0, last = 1, penalty = "bic")L0TFinv.opt(y = y, kmax = kmax, q = q, first = 0, last = 1, penalty = "bic")
y |
The input data points |
kmax |
The maximum number of change points |
q |
0 or 1. Correspond to a piecewise constant or piecewise linear trend |
first |
The value ranges from 0 to 1. Represent the minimum percentile point where a change point may occur. If 'first' = 0.01, it means that change points cannot appear in the first 1% of the data points. If 'first' = 0, there is no constraint on the position of the change point. |
last |
The value ranges from 0 to 1. Represent the maximum percentile point where a change point may occur. If 'last' = 0.99, it means that change points cannot appear in the last 1% of the data points. If 'last' = 1, there is no constraint on the position of the change point. |
penalty |
'sic' or 'bic' penalty |
Let the fitted trend be denoted as , then
and
The term represents the degrees of freedom for the estimated trend, where . Here, refers to the number of change points in the estimated trend.
An S3 object of type "L0TFinvopt". A list containing the fitted trend results:
sic |
Information criterion value with a penalty term of |
bic |
Information criterion value with a penalty term of |
mse |
The mean square error between the fitted trend and the input data |
y |
The input data points |
betaopt |
The fitted |
yopt |
The fitted trend with optimal change points |
Aopt |
The set of position indicators of the fitted change points with optimal change points |
kopt |
Optimal number of change points |
beta.all |
A data frame with dimensions |
y.all |
A data frame with dimensions |
A.all |
A list of length |
tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.opt(y=BlocksData$y, kmax=20, q=0, first=0.01, last=1, penalty="bic") print(res$Aopt) print(BlocksData$setA) plot(BlocksData$x, BlocksData$y, xlab="", ylab="") lines(BlocksData$x, BlocksData$y0, col = "red") lines(BlocksData$x, res$yopt, col = "lightgreen") tau1 = c(0.4, 0.6, 0.7) h1 = c(-3, 5, -4, 6) a0 = -10 WaveData <- SimuWaveInv(n = 500, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.opt(y=WaveData$y, kmax=10, q=1, first=0, last=0.99, penalty="sic") print(res1$Aopt) print(WaveData$setA) plot(WaveData$x, WaveData$y, xlab="", ylab="") lines(WaveData$x, WaveData$y0, col = "red") lines(WaveData$x, res1$yopt, col = "lightgreen")tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.opt(y=BlocksData$y, kmax=20, q=0, first=0.01, last=1, penalty="bic") print(res$Aopt) print(BlocksData$setA) plot(BlocksData$x, BlocksData$y, xlab="", ylab="") lines(BlocksData$x, BlocksData$y0, col = "red") lines(BlocksData$x, res$yopt, col = "lightgreen") tau1 = c(0.4, 0.6, 0.7) h1 = c(-3, 5, -4, 6) a0 = -10 WaveData <- SimuWaveInv(n = 500, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.opt(y=WaveData$y, kmax=10, q=1, first=0, last=0.99, penalty="sic") print(res1$Aopt) print(WaveData$setA) plot(WaveData$x, WaveData$y, xlab="", ylab="") lines(WaveData$x, WaveData$y0, col = "red") lines(WaveData$x, res1$yopt, col = "lightgreen")
Plots a summary of L0TFinvfix or L0TFinvopt
## S3 method for class 'L0TFinvfix' plot(x, type = NULL, k = NULL, ...) ## S3 method for class 'L0TFinvopt' plot(x, type = "mse", k = NULL, ...)## S3 method for class 'L0TFinvfix' plot(x, type = NULL, k = NULL, ...) ## S3 method for class 'L0TFinvopt' plot(x, type = "mse", k = NULL, ...)
x |
The output of L0TFinvfix or L0TFinvopt |
type |
The values are taken as c(" |
k |
Only used for |
... |
ignore |
Plot the information criteria for L0TFinvfix and L0TFinvopt across an increasing number of change points or display the estimated trend with a selected change point
Prints a summary of L0TFinvfix or L0TFinvopt
## S3 method for class 'L0TFinvfix' print(x, ...) ## S3 method for class 'L0TFinvopt' print(x, ...)## S3 method for class 'L0TFinvfix' print(x, ...) ## S3 method for class 'L0TFinvopt' print(x, ...)
x |
The output of L0TFinvfix or L0TFinvopt |
... |
ignore |
Estimated parameters and information criteria for L0TFinvfix and L0TFinvopt across an increasing number of change points
library(ggplot2) tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic") print(res) coef(res,k=res$kopt) plot(res,type="yhat") plot(res,type="bic") tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85) h1 = c(-1, 5, 3, 0, -1, 2) a0 = -10 WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99) print(res1) coef(res1,k=5) plot(res1,type="yhat",k=5) plot(res1,type="mse")library(ggplot2) tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 500, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic") print(res) coef(res,k=res$kopt) plot(res,type="yhat") plot(res,type="bic") tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85) h1 = c(-1, 5, 3, 0, -1, 2) a0 = -10 WaveData <- SimuWaveInv(n = 2000, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99) print(res1) coef(res1,k=5) plot(res1,type="yhat",k=5) plot(res1,type="mse")
This function generates data points of piecewise constant trends.
SimuBlocksInv(n, sigma, seed = NA, tau, h)SimuBlocksInv(n, sigma, seed = NA, tau, h)
n |
Number of data points |
sigma |
Standard deviation of the noise added to the signal |
seed |
An optional seed for random number generation to make results reproducible |
tau |
The locations of change points in the underlying trend |
h |
The constant values of the |
To simplify the analysis, normalize the change point positions to a range between 0 and 1. Require that all elements of the input are within this range. Consequently, the change point positions in simulated data forms a subset of the set { , , ,..., 1}.
In fact, change points can divide the interval into segments of constant function values. Therefore, ensure that the length of vector is .
A list containing the piecewise constant simulated data and the underlying trend:
x |
The set { |
y |
The piecewise constant simulated data of length |
y0 |
The underlying trend of length |
setA |
The set of position indicators of change points in the simulated data |
tau |
The locations of change points in the underlying trend |
tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 350, sigma = 0.1, seed = 50, tau = tau ,h = h) plot(BlocksData$x, BlocksData$y, xlab="", ylab="") lines(BlocksData$x, BlocksData$y0, col = "red") print(BlocksData$setA) print(BlocksData$tau)tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) BlocksData <- SimuBlocksInv(n = 350, sigma = 0.1, seed = 50, tau = tau ,h = h) plot(BlocksData$x, BlocksData$y, xlab="", ylab="") lines(BlocksData$x, BlocksData$y0, col = "red") print(BlocksData$setA) print(BlocksData$tau)
This function generates data points of piecewise linear trends.
SimuWaveInv(n, sigma, seed = NA, tau, h, a0 = 0)SimuWaveInv(n, sigma, seed = NA, tau, h, a0 = 0)
n |
Number of data points |
sigma |
Standard deviation of the noise added to the signal |
seed |
An optional seed for random number generation to make results reproducible |
tau |
The locations of change points in the underlying trend |
h |
The slope of the |
a0 |
The initial point value |
A list containing the piecewise linear simulated data and the underlying trend:
x |
The set { |
y |
The piecewise linear simulated data of length |
y0 |
The underlying trend of length |
setA |
The set of position indicators of change points in the simulated data |
tau |
The locations of change points in the underlying trend |
tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) a0 = -10 WaveData <- SimuWaveInv(n = 650, sigma = 0.1, seed = 50, tau = tau, h = h, a0 = a0) plot(WaveData$x, WaveData$y, xlab="", ylab="") lines(WaveData$x, WaveData$y0, col = "red") print(WaveData$setA) print(WaveData$tau)tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) a0 = -10 WaveData <- SimuWaveInv(n = 650, sigma = 0.1, seed = 50, tau = tau, h = h, a0 = a0) plot(WaveData$x, WaveData$y, xlab="", ylab="") lines(WaveData$x, WaveData$y0, col = "red") print(WaveData$setA) print(WaveData$tau)
Generate the inverse matrix of for the cases where or , commonly employed in splicing algorithms. Note that an explicit solution exists for the inverse when , but not when .
solMat(n, q, A)solMat(n, q, A)
n |
The number of data points |
q |
The order of the difference, 0 or 1 |
A |
The set of indicators, a subset of |
The inverse matrix of for the cases where 0 or 1.
Mat1 <- XMat(n = 10, q = 0) A1 = c(1,2,5,8) mat1 = as.matrix(Mat1[,A1]) S1 <- solMat(n = 10, q = 0, A = A1) print(S1) print(round(S1%*%t(mat1)%*%mat1,10)) Mat2 <- XMat(n = 15, q = 1) A2 = c(1,3,8,10,15) mat2 = as.matrix(Mat2[,A2]) S2 <- solMat(n = 15, q = 1, A = A2) print(S2) print(round(S2%*%t(mat2)%*%mat2,10))Mat1 <- XMat(n = 10, q = 0) A1 = c(1,2,5,8) mat1 = as.matrix(Mat1[,A1]) S1 <- solMat(n = 10, q = 0, A = A1) print(S1) print(round(S1%*%t(mat1)%*%mat1,10)) Mat2 <- XMat(n = 15, q = 1) A2 = c(1,3,8,10,15) mat2 = as.matrix(Mat2[,A2]) S2 <- solMat(n = 15, q = 1, A = A2) print(S2) print(round(S2%*%t(mat2)%*%mat2,10))
Prints four metrics to compare the quality of change point detection results.
TFmetrics(y0, tau = NULL, yhat, cpts = NULL)TFmetrics(y0, tau = NULL, yhat, cpts = NULL)
y0 |
The underlying trend |
tau |
The locations of change points in the underlying trend |
yhat |
The fitted trend |
cpts |
The positions of the fitted change points |
represents the estimated change point positions, while denotes the locations of change points in the underlying trend.
Note that the number of and does not need to be the same.
MSE |
The mean square error between the fitted trend and the underlying trend |
MAD |
The median absolute deviation between the fitted trend and the underlying trend |
dH |
Hausdorff Distance (dH) measures the accuracy of the estimated change points |
nknot |
The number of detected change points |
tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) n = 500 BlocksData <- SimuBlocksInv(n = n, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic") metrics <- TFmetrics(BlocksData$y0,BlocksData$tau,res$yopt,res$Aopt/n) print(metrics) tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85) h1 = c(-1, 5, 3, 0, -1, 2) a0 = -10 n1 = 2000 WaveData <- SimuWaveInv(n = n1, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99) metrics1 <- TFmetrics(WaveData$y0,WaveData$tau,res1$y.all[,5],res1$A.all[[5]]/n1) print(metrics1)tau = c(0.1, 0.3, 0.4, 0.7, 0.85) h = c(-1, 5, 3, 0, -1, 2) n = 500 BlocksData <- SimuBlocksInv(n = n, sigma = 0.2, seed = 50, tau = tau ,h = h) res <- L0TFinv.opt(y=BlocksData$y, kmax=10, q=0, first=0.01, last=1, penalty="bic") metrics <- TFmetrics(BlocksData$y0,BlocksData$tau,res$yopt,res$Aopt/n) print(metrics) tau1 = c(0.1, 0.3, 0.4, 0.7, 0.85) h1 = c(-1, 5, 3, 0, -1, 2) a0 = -10 n1 = 2000 WaveData <- SimuWaveInv(n = n1, sigma = 0.1, seed = 50, tau = tau1, h = h1, a0 = a0) res1 <- L0TFinv.fix(y=WaveData$y, k=20, q=1, first=0, last=0.99) metrics1 <- TFmetrics(WaveData$y0,WaveData$tau,res1$y.all[,5],res1$A.all[[5]]/n1) print(metrics1)
This matrix corresponds to the difference matrix, transforming the L0 trend filtering model into an inverse statistical problem.
XMat(n, q)XMat(n, q)
n |
The number of data points |
q |
The order of the difference |
Noticing the correspondence between and , the result of their matrix multiplication is a combination of a zero matrix and an identity matrix. Expressed as . The result is advantageous for the invertible processing of the original L0 trend filtering problem.
A matrix with dimensions by , whose elements correspond to the difference matrix.
mat1 <- XMat(n = 10, q = 0) print(mat1) mat2 <- XMat(n = 15, q = 1) print(mat2) mat3 <- XMat(n = 15, q = 2) print(mat3) Mat1 <- DiffMat(n = 10, q = 0) Mat2 <- DiffMat(n = 15, q = 1) Mat3 <- DiffMat(n = 15, q = 2) print(Mat1%*%mat1) print(Mat2%*%mat2) print(Mat3%*%mat3)mat1 <- XMat(n = 10, q = 0) print(mat1) mat2 <- XMat(n = 15, q = 1) print(mat2) mat3 <- XMat(n = 15, q = 2) print(mat3) Mat1 <- DiffMat(n = 10, q = 0) Mat2 <- DiffMat(n = 15, q = 1) Mat3 <- DiffMat(n = 15, q = 2) print(Mat1%*%mat1) print(Mat2%*%mat2) print(Mat3%*%mat3)