| Title: | Penalized Estimation for Latent Variable Models with 'lavaan' |
| Version: | 0.0.1 |
| Description: | Extends the popular 'lavaan' package by adding penalized estimation capabilities. It supports penalty on individual parameters as well as the difference between parameters. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| RoxygenNote: | 7.3.3 |
| Suggests: | knitr, rmarkdown, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| Config/testthat/edition: | 3 |
| Imports: | lavaan, numDeriv |
| URL: | https://marklhc.github.io/plavaan/, https://github.com/marklhc/plavaan |
| BugReports: | https://github.com/marklhc/plavaan/issues |
| NeedsCompilation: | no |
| Packaged: | 2025-12-19 06:30:15 UTC; marklai |
| Author: | Hok Chio (Mark) Lai
|
| Maintainer: | Hok Chio (Mark) Lai <marklhc@gmail.com> |
| Depends: | R (≥ 3.5.0) |
| Repository: | CRAN |
| Date/Publication: | 2025-12-30 19:00:02 UTC |
Composite Pairwise Loss Function
Description
Computes the total loss across all pairwise combinations of rows in a matrix.
Usage
composite_pair_loss(x, fun, trans = identity, rescale = "df", ...)
Arguments
x |
A numeric vector, matrix, or data frame. If not a matrix, it will be coerced to one after applying the transformation function. |
fun |
A function to compute the loss for each pairwise difference.
The package supports the alignment loss ( |
trans |
A transformation function to apply to |
rescale |
Either |
... |
Additional arguments passed to the loss function |
Details
The function works by:
Applying the transformation function
transto the inputxConverting the result to a matrix
Generating all possible pairwise combinations of row indices
Computing the difference between each pair of rows
Applying the loss function
funto each differenceSumming all the individual losses
Value
A numeric scalar representing the sum of losses across all pairwise combinations of rows.
Examples
# Example with a simple matrix
x <- matrix(runif(12), nrow = 4)
composite_pair_loss(x, fun = alf)
# Example with log transformation and L2 loss
composite_pair_loss(x, fun = function(x) x^2, trans = log)
Loss functions
Description
For small eps this provides a smooth, numerically stable approximation of |x|^(1/2) (i.e. the square root of the absolute value). The function is vectorized over x.
Usage
alf(x, eps = 0.001)
l0a(x, eps = 0.01)
Arguments
x |
Numeric vector. Input values to transform. |
eps |
Positive numeric scalar (default .001 for |
Details
The ALF, (x^2 + eps)^(1/4), is useful when a smooth surrogate for sqrt(|x|) is required (for optimization or regularization) while maintaining numerical stability near x = 0.
L0a, x^2/(x^2 + eps), is an approximation of the L0 penalty.
Value
Numeric vector of the same length as x.
Examples
alf(0)
alf(c(-4, -1, 0, 1, 4))
alf(0.5, eps = 1e-6)
l0a(0)
l0a(c(0, 1e-3, 0.1, 1))
l0a(c(-2, 0, 2), eps = 1e-4)
Penalized Parameter Estimation for Longitudinal CFA Models
Description
Performs penalized estimation on a lavaan model object by optimizing a penalized objective function. The function extracts the objective function from a lavaan model, applies a penalty function to specified parameters or pairwise differences of parameters, and returns an updated model with the optimized parameter estimates.
Usage
penalized_est(
x,
w,
pen_par_id = NULL,
pen_diff_id = NULL,
pen_fn = "l0a",
pen_gr = NULL,
se = "none",
opt_control = list()
)
Arguments
x |
A fitted lavaan model object from which estimation components will be extracted. |
w |
Numeric scalar. Penalty weight (multiplier) applied to the penalty terms. |
pen_par_id |
Integer vector of parameter IDs to apply the penalty function
directly to, in the same order as returned by |
pen_diff_id |
List of matrices containing parameter IDs. For each matrix, the penalty is applied to the pairwise differences of parameters in the same column indicated by the IDs. For matrices with names starting with "loading", the log transformation is applied before computing differences. |
pen_fn |
A character string ( |
pen_gr |
A function that computes the gradient of the penalty function.
If |
se |
Character string specifying the type of standard errors to compute.
Options are |
opt_control |
A list of control parameters passed to |
Details
The function uses nlminb() to minimize a penalized objective function that
combines the standard lavaan objective function with a penalty term. Only the
parameter estimates and the log-likelihood should be interpreted. The
returned object was not "fitted" (do.fit = FALSE) to avoid users
interpreting the standard errors, which are generally not valid with
penalized estimation. The degrees of freedom may also be inaccurate. If the
optimization does not converge (convergence code != 0), a warning is issued.
Value
A lavaan model object updated with the penalized parameter estimates.
The returned object includes an attribute opt_info containing the
optimization information returned by nlminb().
Warning
The returned object is not fitted using standard ML. Standard errors reported
by summary() or parameterEstimates() will be missing unless
se = "robust.huber.white" was specified. Even then, they are based on an
experimental sandwich approximation and should be interpreted with caution.
See Also
Examples
library(lavaan)
# Define a longitudinal factor model with PoliticalDemocracy data
model <- "
dem60 =~ y1 + y2 + y3 + y4
dem65 =~ y5 + y6 + y7 + y8
dem60 ~~ dem65
dem60 ~~ 1 * dem60
dem65 ~~ NA * dem65
dem60 ~ 0
dem65 ~ NA * 1
y1 ~~ y5
y2 ~~ y6
y3 ~~ y7
y4 ~~ y8
"
# Fit the model without constraints first to get parameter table
fit_un <- cfa(model, data = PoliticalDemocracy, std.lv = TRUE,
meanstructure = TRUE, do.fit = FALSE)
# Get parameter IDs
pt <- parTable(fit_un)
# Loadings
load_60 <- pt$free[pt$op == "=~" & pt$lhs == "dem60"]
load_65 <- pt$free[pt$op == "=~" & pt$lhs == "dem65"]
# Intercepts
int_60 <- pt$free[pt$op == "~1" & pt$lhs %in% c("y1", "y2", "y3", "y4")]
int_65 <- pt$free[pt$op == "~1" & pt$lhs %in% c("y5", "y6", "y7", "y8")]
# Apply penalized estimation to penalize differences in loadings and intercepts
pen_fit <- penalized_est(
x = fit_un,
w = 0.03,
pen_diff_id = list(
loadings = rbind(load_60, load_65),
intercepts = rbind(int_60, int_65)
),
pen_fn = "l0a"
)
# Compare parameter estimates
summary(pen_fit)