Nothing
#' mixedPenalty
#'
#' Provides possibility to impose different penalties on different parameters.
#'
#' The `mixedPenalty` function allows you to add multiple penalties to a single model.
#' For instance, you may want to regularize both loadings and regressions in a SEM.
#' In this case, using the same penalty (e.g., lasso) for both types of penalties may
#' actually not be what you want to use because the penalty function is sensitive to
#' the scales of the parameters. Instead, you may want to use two separate lasso
#' penalties for loadings and regressions. Similarly, separate penalties for
#' different parameters have, for instance, been proposed in multi-group models
#' (Geminiani et al., 2021).
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well. Models are fitted with the glmnet or ista optimizer. Note that the
#' optimizers differ in which penalties they support. The following table provides
#' an overview:
#'
#' | Penalty | Function | glmnet | ista |
#' | --- | ---- | ---- | ---- |
#' | lasso | addLasso | x | x |
#' | elastic net | addElasticNet | x* | - |
#' | cappedL1 | addCappedL1 | x | x |
#' | lsp | addLsp | x | x |
#' | scad | addScad | x | x |
#' | mcp | addMcp | x | x |
#'
#' By default, glmnet will be used. Note that the elastic net penalty can
#' only be combined with other elastic net penalties.
#'
#' Check vignette(topic = "Mixed-Penalties", package = "lessSEM") for more details.
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Geminiani, E., Marra, G., & Moustaki, I. (2021). Single- and multiple-group penalized factor analysis:
#' A trust-region algorithm approach with integrated automatic multiple tuning parameter selection.
#' Psychometrika, 86(1), 65–95. https://doi.org/10.1007/s11336-021-09751-8
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param lavaanModel model of class lavaan
#' @param modifyModel used to modify the lavaanModel. See ?modifyModel.
#' @param method which optimizer should be used? Currently supported are "glmnet" and "ista".
#' @param control used to control the optimizer. This element is generated with
#' the controlIsta and controlGlmnet functions. See ?controlIsta and ?controlGlmnet
#' for more details.
#' @returns Model of class regularizedSEM
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # Regularization:
#'
#' # In this example, we want to regularize the loadings l6-l10
#' # independently of the loadings l11-15. This could, for instance,
#' # reflect that the items y6-y10 and y11-y15 may belong to different
#' # subscales.
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add lasso penalty on loadings l6 - l10:
#' addLasso(regularized = paste0("l", 6:10),
#' lambdas = seq(0,1,length.out = 4)) |>
#' # add scad penalty on loadings l11 - l15:
#' addScad(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,length.out = 3),
#' thetas = 3.1) |>
#' # fit the model:
#' fit()
#'
#' # elements of regularized can be accessed with the @ operator:
#' regularized@parameters[1,]
#'
#' # AIC and BIC:
#' AIC(regularized)
#' BIC(regularized)
#'
#' # The best parameters can also be extracted with:
#' coef(regularized, criterion = "AIC")
#' coef(regularized, criterion = "BIC")
#'
#' # The tuningParameterConfiguration corresponds to the rows
#' # in the lambda, theta, and alpha matrices in regularized@tuningParamterConfigurations.
#' # Configuration 3, for example, is given by
#' regularized@tuningParameterConfigurations$lambda[3,]
#' regularized@tuningParameterConfigurations$theta[3,]
#' regularized@tuningParameterConfigurations$alpha[3,]
#' # Note that lambda, theta, and alpha may correspond to tuning parameters
#' # of different penalties for different parameters (e.g., lambda for l6 is the lambda
#' # of the lasso penalty, while lambda for l12 is the lambda of the scad penalty).
#'
#' @md
#' @export
mixedPenalty <- function(lavaanModel,
modifyModel = lessSEM::modifyModel(),
method = "glmnet",
control = lessSEM::controlGlmnet()){
notes <- c("Notes:")
notes <- c(
notes,
paste0("Mixed penalties is a very new feature. Please note that there may still ",
"be bugs in the procedure. Use carefully!"))
mixedPenalty <- list(
lavaanModel = lavaanModel,
method = method,
modifyModel = modifyModel,
control = control,
penalties = list()
)
class(mixedPenalty) <- "mixedPenalty"
return(mixedPenalty)
}
#' addCappedL1
#'
#' Implements cappedL1 regularization for structural equation models.
#' The penalty function is given by:
#' \deqn{p( x_j) = \lambda \min(| x_j|, \theta)}
#' where \eqn{\theta > 0}. The cappedL1 penalty is identical to the lasso for
#' parameters which are below \eqn{\theta} and identical to a constant for parameters
#' above \eqn{\theta}. As adding a constant to the fitting function will not change its
#' minimum, larger parameters can stay unregularized while smaller ones are set to zero.
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well.
#'
#' CappedL1 regularization:
#'
#' * Zhang, T. (2010). Analysis of Multi-stage Convex Relaxation for Sparse Regularization.
#' Journal of Machine Learning Research, 11, 1081–1107.
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param mixedPenalty model of class mixedPenalty created with the mixedPenalty function (see ?mixedPenalty)
#' @param regularized vector with names of parameters which are to be regularized.
#' If you are unsure what these parameters are called, use
#' getLavaanParameters(model) with your lavaan model object
#' @param lambdas numeric vector: values for the tuning parameter lambda
#' @param thetas parameters whose absolute value is above this threshold will be penalized with
#' a constant (theta)
#' @returns Model of class mixedPenalty. Use the fit() - function to fit the model
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addCappedL1(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1),
#' thetas = 2.3) |>
#' # fit the model:
#' fit()
#' @export
addCappedL1 <- function(mixedPenalty,
regularized,
lambdas,
thetas){
if(!is(mixedPenalty, "mixedPenalty"))
stop("mixedPenalty must be of class mixedPenalty. ",
"These models can be created with the regularize() function.")
tps <- expand.grid(lambda = lambdas,
theta = thetas,
alpha = 1)
mixedPenalty$penalties <- c(
mixedPenalty$penalties,
list(list(regularized = regularized,
tps = tps,
penaltyType = .penaltyTypes("cappedL1"))
)
)
return(mixedPenalty)
}
#' addLasso
#'
#' Implements lasso regularization for structural equation models.
#' The penalty function is given by:
#' \deqn{p( x_j) = \lambda |x_j|}
#' Lasso regularization will set parameters to zero if \eqn{\lambda} is large enough
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well.
#'
#' Lasso regularization:
#'
#' * Tibshirani, R. (1996). Regression shrinkage and selection via the lasso. Journal of the Royal Statistical
#' Society. Series B (Methodological), 58(1), 267–288.
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on GLMNET, see:
#'
#' * Friedman, J., Hastie, T., & Tibshirani, R. (2010).
#' Regularization Paths for Generalized Linear Models via Coordinate Descent.
#' Journal of Statistical Software, 33(1), 1–20. https://doi.org/10.18637/jss.v033.i01
#' * Yuan, G.-X., Chang, K.-W., Hsieh, C.-J., & Lin, C.-J. (2010).
#' A Comparison of Optimization Methods and Software for Large-scale
#' L1-regularized Linear Classification. Journal of Machine Learning Research, 11, 3183–3234.
#' * Yuan, G.-X., Ho, C.-H., & Lin, C.-J. (2012).
#' An improved GLMNET for l1-regularized logistic regression.
#' The Journal of Machine Learning Research, 13, 1999–2030. https://doi.org/10.1145/2020408.2020421
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param mixedPenalty model of class mixedPenalty created with the mixedPenalty function (see ?mixedPenalty)
#' @param regularized vector with names of parameters which are to be regularized.
#' If you are unsure what these parameters are called, use
#' getLavaanParameters(model) with your lavaan model object
#' @param lambdas numeric vector: values for the tuning parameter lambda
#' @param weights can be used to give different weights to the different parameters
#' @returns Model of class mixedPenalty. Use the fit() - function to fit the model
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addLasso(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1)) |>
#' # fit the model:
#' fit()
#' @export
addLasso <- function(mixedPenalty,
regularized,
weights = 1,
lambdas){
if(!is(mixedPenalty, "mixedPenalty"))
stop("mixedPenalty must be of class mixedPenalty. ",
"These models can be created with the regularize() function.")
if((length(weights) != 1) && (length(weights) != length(regularized)))
stop("Weights argument must be of length 1 or of length(regularized).")
tps <- expand.grid(lambda = lambdas,
theta = 0,
alpha = 1)
mixedPenalty$penalties <- c(
mixedPenalty$penalties,
list(list(regularized = regularized,
weight = weights,
tps = tps,
penaltyType = .penaltyTypes("lasso")))
)
return(mixedPenalty)
}
#' addLsp
#'
#' Implements lsp regularization for structural equation models.
#' The penalty function is given by:
#' \deqn{p( x_j) = \lambda \log(1 + |x_j|/\theta)}
#' where \eqn{\theta > 0}.
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well.
#'
#' lsp regularization:
#'
#' * Candès, E. J., Wakin, M. B., & Boyd, S. P. (2008). Enhancing Sparsity by
#' Reweighted l1 Minimization. Journal of Fourier Analysis and Applications, 14(5–6),
#' 877–905. https://doi.org/10.1007/s00041-008-9045-x
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on GLMNET, see:
#'
#' * Friedman, J., Hastie, T., & Tibshirani, R. (2010).
#' Regularization Paths for Generalized Linear Models via Coordinate Descent.
#' Journal of Statistical Software, 33(1), 1–20. https://doi.org/10.18637/jss.v033.i01
#' * Yuan, G.-X., Chang, K.-W., Hsieh, C.-J., & Lin, C.-J. (2010).
#' A Comparison of Optimization Methods and Software for Large-scale
#' L1-regularized Linear Classification. Journal of Machine Learning Research, 11, 3183–3234.
#' * Yuan, G.-X., Ho, C.-H., & Lin, C.-J. (2012).
#' An improved GLMNET for l1-regularized logistic regression.
#' The Journal of Machine Learning Research, 13, 1999–2030. https://doi.org/10.1145/2020408.2020421
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param mixedPenalty model of class mixedPenalty created with the mixedPenalty function (see ?mixedPenalty)
#' @param regularized vector with names of parameters which are to be regularized.
#' If you are unsure what these parameters are called, use
#' getLavaanParameters(model) with your lavaan model object
#' @param lambdas numeric vector: values for the tuning parameter lambda
#' @param thetas parameters whose absolute value is above this threshold will be penalized with
#' a constant (theta)
#' @returns Model of class mixedPenalty. Use the fit() - function to fit the model
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addLsp(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1),
#' thetas = 2.3) |>
#' # fit the model:
#' fit()
#' @export
addLsp <- function(mixedPenalty,
regularized,
lambdas,
thetas){
if(any(thetas <= 0)) stop("Theta must be > 0")
if(!is(mixedPenalty, "mixedPenalty"))
stop("mixedPenalty must be of class mixedPenalty. ",
"These models can be created with the regularize() function.")
tps <- expand.grid(lambda = lambdas,
theta = thetas,
alpha = 0)
mixedPenalty$penalties <- c(
mixedPenalty$penalties,
list(list(regularized = regularized,
tps = tps,
penaltyType = .penaltyTypes("lsp"))
)
)
return(mixedPenalty)
}
#' addMcp
#'
#' Implements mcp regularization for structural equation models.
#' The penalty function is given by:
#' \ifelse{html}{\deqn{p( x_j) = \begin{cases}
#' \lambda |x_j| - x_j^2/(2\theta) & \text{if } |x_j| \leq \theta\lambda\\
#' \theta\lambda^2/2 & \text{if } |x_j| > \lambda\theta
#' \end{cases}} where \eqn{\theta > 0}.}{
#' Equation Omitted in Pdf Documentation.}
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well.
#'
#' mcp regularization:
#'
#' * Zhang, C.-H. (2010). Nearly unbiased variable selection under minimax concave penalty.
#' The Annals of Statistics, 38(2), 894–942. https://doi.org/10.1214/09-AOS729
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param mixedPenalty model of class mixedPenalty created with the mixedPenalty function (see ?mixedPenalty)
#' @param regularized vector with names of parameters which are to be regularized.
#' If you are unsure what these parameters are called, use
#' getLavaanParameters(model) with your lavaan model object
#' @param lambdas numeric vector: values for the tuning parameter lambda
#' @param thetas parameters whose absolute value is above this threshold will be penalized with
#' a constant (theta)
#' @returns Model of class mixedPenalty. Use the fit() - function to fit the model
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addMcp(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1),
#' thetas = 2.3) |>
#' # fit the model:
#' fit()
#' @export
addMcp <- function(mixedPenalty,
regularized,
lambdas,
thetas){
if(any(thetas <= 0)) stop("Theta must be > 0")
if(!is(mixedPenalty, "mixedPenalty"))
stop("mixedPenalty must be of class mixedPenalty. ",
"These models can be created with the regularize() function.")
tps <- expand.grid(lambda = lambdas,
theta = thetas,
alpha = 0)
mixedPenalty$penalties <- c(
mixedPenalty$penalties,
list(list(regularized = regularized,
tps = tps,
penaltyType = .penaltyTypes("mcp"))
)
)
return(mixedPenalty)
}
#' addScad
#'
#' Implements scad regularization for structural equation models.
#' The penalty function is given by:
#' \ifelse{html}{
#' \deqn{p( x_j) = \begin{cases}
#' \lambda |x_j| & \text{if } |x_j| \leq \theta\\
#' \frac{-x_j^2 + 2\theta\lambda |x_j| - \lambda^2}{2(\theta -1)} &
#' \text{if } \lambda < |x_j| \leq \lambda\theta \\
#' (\theta + 1) \lambda^2/2 & \text{if } |x_j| \geq \theta\lambda\\
#' \end{cases}}
#' where \eqn{\theta > 2}.}{
#' Equation Omitted in Pdf Documentation.
#' }
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well.
#'
#' scad regularization:
#'
#' * Fan, J., & Li, R. (2001). Variable selection via nonconcave penalized
#' likelihood and its oracle properties. Journal of the American Statistical Association,
#' 96(456), 1348–1360. https://doi.org/10.1198/016214501753382273
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param mixedPenalty model of class mixedPenalty created with the mixedPenalty function (see ?mixedPenalty)
#' @param regularized vector with names of parameters which are to be regularized.
#' If you are unsure what these parameters are called, use
#' getLavaanParameters(model) with your lavaan model object
#' @param lambdas numeric vector: values for the tuning parameter lambda
#' @param thetas parameters whose absolute value is above this threshold will be penalized with
#' a constant (theta)
#' @returns Model of class mixedPenalty. Use the fit() - function to fit the model
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addScad(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1),
#' thetas = 3.1) |>
#' # fit the model:
#' fit()
#' @export
addScad <- function(mixedPenalty,
regularized,
lambdas,
thetas){
if(any(thetas <= 2)) stop("Theta must be > 2")
if(!is(mixedPenalty, "mixedPenalty"))
stop("mixedPenalty must be of class mixedPenalty. ",
"These models can be created with the regularize() function.")
tps <- expand.grid(lambda = lambdas,
theta = thetas,
alpha = 0)
mixedPenalty$penalties <- c(
mixedPenalty$penalties,
list(list(regularized = regularized,
tps = tps,
penaltyType = .penaltyTypes("scad"))
)
)
return(mixedPenalty)
}
#' addElasticNet
#'
#' Adds an elastic net penalty to specified parameters.
#' The penalty function is given by:
#' \deqn{p( x_j) = \alpha\lambda|x_j| + (1-\alpha)\lambda x_j^2}
#' Note that the elastic net combines ridge and lasso regularization. If \eqn{\alpha = 0},
#' the elastic net reduces to ridge regularization. If \eqn{\alpha = 1} it reduces
#' to lasso regularization. In between, elastic net is a compromise between the shrinkage of
#' the lasso and the ridge penalty.
#'
#' Identical to \pkg{regsem}, models are specified using \pkg{lavaan}. Currently,
#' most standard SEM are supported. \pkg{lessSEM} also provides full information
#' maximum likelihood for missing data. To use this functionality,
#' fit your \pkg{lavaan} model with the argument `sem(..., missing = 'ml')`.
#' \pkg{lessSEM} will then automatically switch to full information maximum likelihood
#' as well.
#'
#' Elastic net regularization:
#'
#' * Zou, H., & Hastie, T. (2005). Regularization and variable selection via the elastic net.
#' Journal of the Royal Statistical Society: Series B, 67(2), 301–320. https://doi.org/10.1111/j.1467-9868.2005.00503.x
#'
#' Regularized SEM
#'
#' * Huang, P.-H., Chen, H., & Weng, L.-J. (2017). A Penalized Likelihood Method for Structural Equation Modeling. Psychometrika, 82(2), 329–354. https://doi.org/10.1007/s11336-017-9566-9
#' * Jacobucci, R., Grimm, K. J., & McArdle, J. J. (2016). Regularized Structural Equation Modeling. Structural
#' Equation Modeling: A Multidisciplinary Journal, 23(4), 555–566. https://doi.org/10.1080/10705511.2016.1154793
#'
#' For more details on GLMNET, see:
#'
#' * Friedman, J., Hastie, T., & Tibshirani, R. (2010).
#' Regularization Paths for Generalized Linear Models via Coordinate Descent.
#' Journal of Statistical Software, 33(1), 1–20. https://doi.org/10.18637/jss.v033.i01
#' * Yuan, G.-X., Chang, K.-W., Hsieh, C.-J., & Lin, C.-J. (2010).
#' A Comparison of Optimization Methods and Software for Large-scale
#' L1-regularized Linear Classification. Journal of Machine Learning Research, 11, 3183–3234.
#' * Yuan, G.-X., Ho, C.-H., & Lin, C.-J. (2012).
#' An improved GLMNET for l1-regularized logistic regression.
#' The Journal of Machine Learning Research, 13, 1999–2030. https://doi.org/10.1145/2020408.2020421
#'
#' For more details on ISTA, see:
#'
#' * Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding
#' Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1),
#' 183–202. https://doi.org/10.1137/080716542
#' * Gong, P., Zhang, C., Lu, Z., Huang, J., & Ye, J. (2013).
#' A General Iterative Shrinkage and Thresholding Algorithm for Non-convex
#' Regularized Optimization Problems. Proceedings of the 30th International
#' Conference on Machine Learning, 28(2)(2), 37–45.
#' * Parikh, N., & Boyd, S. (2013). Proximal Algorithms. Foundations and
#' Trends in Optimization, 1(3), 123–231.
#'
#' @param mixedPenalty model of class mixedPenalty created with the mixedPenalty function (see ?mixedPenalty)
#' @param regularized vector with names of parameters which are to be regularized.
#' If you are unsure what these parameters are called, use
#' getLavaanParameters(model) with your lavaan model object
#' @param alphas numeric vector: values for the tuning parameter alpha. Set to 1 for lasso and
#' to zero for ridge. Anything in between is an elastic net penalty.
#' @param lambdas numeric vector: values for the tuning parameter lambda
#' @param weights can be used to give different weights to the different parameters
#' @returns Model of class mixedPenalty. Use the fit() - function to fit the model
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addElasticNet(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1),
#' alphas = .4) |>
#' # fit the model:
#' fit()
#' @export
addElasticNet <- function(mixedPenalty,
regularized,
alphas,
lambdas,
weights = 1){
if(!is(mixedPenalty, "mixedPenalty"))
stop("mixedPenalty must be of class mixedPenalty. ",
"These models can be created with the regularize() function.")
if(!mixedPenalty$method == "glmnet"){
notes <- c(notes,
"Mixed penalty with addElasticNet is only supported for method = 'glmnet'. Switching optimizer to glmnet")
mixedPenalty$method = "glmnet"
mixedPenalty$control = controlGlmnet()
}
if(any(alphas > 1) || any(alphas < 0))
stop("alphas must be in the interval [0,1]")
tps <- expand.grid(alpha = alphas,
lambda = lambdas)
if((length(weights) != 1) && (length(weights) != length(regularized)))
stop("Weights argument must be of length 1 or of length(regularized).")
mixedPenalty$penalties <- c(
mixedPenalty$penalties,
list(list(regularized = regularized,
weight = weights,
tps = tps,
penaltyType = "elasticNet"))
)
return(mixedPenalty)
}
#' fit
#'
#' Optimizes an object with mixed penalty. See ?mixedPenalty for more details.
#'
#' @param mixedPenalty object of class mixedPenalty. This object can be created
#' with the mixedPenalty function. Penalties can be added with the addCappedL1, addElastiNet,
#' addLasso, addLsp, addMcp, and addScad functions.
#' @return throws error in case of undefined penalty combinations.
#' @examples
#' library(lessSEM)
#'
#' # Identical to regsem, lessSEM builds on the lavaan
#' # package for model specification. The first step
#' # therefore is to implement the model in lavaan.
#'
#' dataset <- simulateExampleData()
#'
#' lavaanSyntax <- "
#' f =~ l1*y1 + l2*y2 + l3*y3 + l4*y4 + l5*y5 +
#' l6*y6 + l7*y7 + l8*y8 + l9*y9 + l10*y10 +
#' l11*y11 + l12*y12 + l13*y13 + l14*y14 + l15*y15
#' f ~~ 1*f
#' "
#'
#' lavaanModel <- lavaan::sem(lavaanSyntax,
#' data = dataset,
#' meanstructure = TRUE,
#' std.lv = TRUE)
#'
#' # We can add mixed penalties as follows:
#'
#' regularized <- lavaanModel |>
#' # create template for regularized model with mixed penalty:
#' mixedPenalty() |>
#' # add penalty on loadings l6 - l10:
#' addElasticNet(regularized = paste0("l", 11:15),
#' lambdas = seq(0,1,.1),
#' alphas = .4) |>
#' # fit the model:
#' fit()
#' @export
fit <- function(mixedPenalty){
.checkPenalties(mixedPenalty)
if(.useElasticNet(mixedPenalty))
return(.fitElasticNetMix(mixedPenalty))
return(.fitMix(mixedPenalty))
stop("Unknown method used.")
}
#' .checkPenalties
#'
#' Internal function to check a mixedPenalty object
#' @param mixedPenalty object of class mixedPenalty. This object can be created
#' with the mixedPenalty function. Penalties can be added with the addCappedL1,
#' addLasso, addLsp, addMcp, and addScad functions.
.checkPenalties <- function(mixedPenalty){
if(!is(mixedPenalty, "mixedPenalty")){
stop("mixedPenalty must be of class mixedPenalty.")
}
if(length(mixedPenalty$penalties) == 0)
stop("Penaties are missing")
}
#' .useElasticNet
#'
#' Internal function checking if elastic net is used
#' @param mixedPenalty object of class mixedPenalty. This object can be created
#' with the mixedPenalty function. Penalties can be added with the addCappedL1,
#' addLasso, addLsp, addMcp, and addScad functions.
#' @return TRUE if elastic net, FALSE otherwise
.useElasticNet <- function(mixedPenalty){
hasElasticNet <- FALSE
hasNonElasticNet <- FALSE
for(i in 1:length(mixedPenalty$penalties)){
if(mixedPenalty$penalties[[i]]$penaltyType == "elasticNet")
hasElasticNet <- TRUE
if(mixedPenalty$penalties[[i]]$penaltyType != "elasticNet")
hasNonElasticNet <- TRUE
}
if(hasElasticNet && mixedPenalty$method == "ista")
stop("Ista currently does not support elasticNet type penalties.")
if(hasElasticNet && hasNonElasticNet)
stop("lessSEM cannot mix penalties of type elastic net and other penalties.")
return(hasElasticNet)
}
#' .fitMix
#'
#' Optimizes an object with mixed penalty. See ?mixedPenalty for more details.
#'
#' @param mixedPenalty object of class mixedPenalty. This object can be created
#' with the mixedPenalty function. Penalties can be added with the addCappedL1, addElastiNet,
#' addLasso, addLsp, addMcp, and addScad functions.
#' @return object of class regularizedSEMMixedPenalty
#' @keywords internal
.fitMix <- function(mixedPenalty){
notes <- c("Notes:")
lavaanModel = mixedPenalty$lavaanModel
method = mixedPenalty$method
modifyModel = mixedPenalty$modifyModel
control = mixedPenalty$control
inputArguments <- as.list(environment())
if(method == "ista" && !is(control, "controlIsta"))
stop("control must be of class controlIsta. See ?controlIsta.")
if(method == "glmnet" && !is(control, "controlGlmnet"))
stop("control must be of class controlGlmnet See ?controlGlmnet")
.checkLavaanModel(lavaanModel = lavaanModel)
### initialize model ####
startingValues <- control$startingValues
### initialize model ####
if(is(lavaanModel, "lavaan")){
SEM <- .initializeSEMForRegularization(lavaanModel = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}else if(is.vector(lavaanModel)){
SEM <- .initializeMultiGroupSEMForRegularization(lavaanModels = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
notes <- c(notes,
"Multi-group model. Switching initialHessian from 'lavaan' to 'compute'"
)
control$initialHessian <- "compute"
}
# check if we have a likelihood objective function:
usesLikelihood <- all(SEM$getEstimator() == "fiml")
N <- SEM$sampleSize
# get parameters in raw form
startingValues <- .getParameters(SEM, raw = TRUE)
rawParameters <- .getParameters(SEM, raw = TRUE)
# set weights
weights <- rep(0, length(startingValues))
names(weights) <- names(startingValues)
penaltyType <- rep(0, length(startingValues))
names(penaltyType) <- names(startingValues)
tpRows <- vector("list", length(mixedPenalty$penalties))
names(tpRows) <- paste0("penalty", 1:length(tpRows))
for(p in 1:length(mixedPenalty$penalties)){
if(any(weights[mixedPenalty$penalties[[p]]$regularized] != 0))
stop("It seems like some parameters appeared in multiple penalties. This is not supported. ",
"The following parameters were found in multiple penalties: ",
paste0(mixedPenalty$penalties[[p]]$regularized[weights[mixedPenalty$penalties[[p]]$regularized] != 0], sep = ","))
if(all(mixedPenalty$penalties[[p]]$penalty == .penaltyTypes("lasso"))){
weights[mixedPenalty$penalties[[p]]$regularized] <- mixedPenalty$penalties[[p]]$weight
}else{
weights[mixedPenalty$penalties[[p]]$regularized] <- 1
}
penaltyType[mixedPenalty$penalties[[p]]$regularized] <- mixedPenalty$penalties[[p]]$penaltyType
tpRows[[p]] <- 1:nrow(mixedPenalty$penalties[[p]]$tp)
}
tpGrid <- unique(expand.grid(tpRows))
lambda <- theta <- alpha <- matrix(0,
nrow = nrow(tpGrid),
ncol = length(startingValues),
dimnames = list(NULL, names(startingValues)))
for(p in 1:ncol(tpGrid)){
lambda[,mixedPenalty$penalties[[p]]$regularized] <- matrix(
rep(mixedPenalty$penalties[[p]]$tps$lambda[tpGrid[,p]],
length(mixedPenalty$penalties[[p]]$regularized)),
ncol = length(mixedPenalty$penalties[[p]]$regularized))
theta[,mixedPenalty$penalties[[p]]$regularized] <- matrix(
rep(mixedPenalty$penalties[[p]]$tps$theta[tpGrid[,p]],
length(mixedPenalty$penalties[[p]]$regularized)),
ncol = length(mixedPenalty$penalties[[p]]$regularized))
alpha[,mixedPenalty$penalties[[p]]$regularized] <- matrix(
rep(mixedPenalty$penalties[[p]]$tps$alpha[tpGrid[,p]],
length(mixedPenalty$penalties[[p]]$regularized)),
ncol = length(mixedPenalty$penalties[[p]]$regularized))
}
#### prepare regularized model object ####
if(method == "ista"){
controlIntern <- list(
L0 = control$L0,
eta = control$eta,
accelerate = control$accelerate,
maxIterOut = control$maxIterOut,
maxIterIn = control$maxIterIn,
breakOuter = control$breakOuter,
convCritInner = control$convCritInner,
sigma = control$sigma,
stepSizeInheritance = control$stepSizeInheritance,
verbose = control$verbose
)
if(is(SEM, "Rcpp_SEMCpp")){
regularizedModel <- new(istaMixedPenaltySEM,
weights,
penaltyType,
controlIntern)
}else if(is(SEM, "Rcpp_mgSEM")){
regularizedModel <- new(istaMixedPenaltymgSEM,
weights,
penaltyType,
controlIntern)
}
}
if(method == "glmnet"){
#### glmnet requires an initial Hessian ####
initialHessian <- .computeInitialHessian(initialHessian = control$initialHessian,
rawParameters = rawParameters,
lavaanModel = lavaanModel,
SEM = SEM,
addMeans = modifyModel$addMeans,
stepSize = control$stepSize,
notes = notes)
notes <- initialHessian$notes
control$initialHessian <- initialHessian$initialHessian
#### prepare regularized model object ####
controlIntern <- list(
initialHessian = control$initialHessian,
stepSize = control$stepSize,
sigma = control$sigma,
gamma = control$gamma,
maxIterOut = control$maxIterOut,
maxIterIn = control$maxIterIn,
maxIterLine = control$maxIterLine,
breakOuter = N*control$breakOuter,
breakInner = N*control$breakInner,
convergenceCriterion = control$convergenceCriterion,
verbose = control$verbose
)
if(is(SEM, "Rcpp_SEMCpp")){
regularizedModel <- new(glmnetMixedSEM,
weights,
penaltyType,
controlIntern)
}else if(is(SEM, "Rcpp_mgSEM")){
regularizedModel <- new(glmnetMixedMgSEM,
weights,
penaltyType,
controlIntern)
}
}
#### define tuning parameters and prepare fit results ####
fits <- data.frame(
"tuningParameterConfiguration" = 1:nrow(tpGrid),
"objectiveValue" = NA,
"regObjectiveValue" = NA,
"m2LL" = NA,
"regM2LL"= NA,
"nonZeroParameters" = NA,
"convergence" = NA
)
parameterEstimates <- as.data.frame(matrix(NA,
nrow = nrow(tpGrid),
ncol = length(rawParameters)))
colnames(parameterEstimates) <- names(rawParameters)
parameterEstimates <- cbind(
"tuningParameterConfiguration" = 1:nrow(tpGrid),
parameterEstimates
)
if(!is.null(modifyModel$transformations)){
transformationsAre <- .getParameters(SEM,
raw = FALSE,
transformations = TRUE)
transformationsAre <- transformationsAre[!names(transformationsAre)%in%names(rawParameters)]
transformations <- as.data.frame(matrix(NA,
nrow = nrow(tpGrid),
ncol = length(transformationsAre)))
colnames(transformations) <- names(transformationsAre)
transformations <- cbind(
"tuningParameterConfiguration" = 1:nrow(tpGrid),
transformations
)
}else{
transformations <- data.frame()
}
Hessians <- list(NULL)
# save model implied matrices
implied <- list(means = vector("list", nrow(tpGrid)),
covariances = vector("list", nrow(tpGrid)))
#### print progress ####
if(control$verbose == 0){
progressbar = utils::txtProgressBar(min = 0,
max = nrow(tpGrid),
initial = 0,
style = 3)
}
#### Iterate over all tuning parameter combinations and fit models ####
for(it in 1:nrow(tpGrid)){
if(control$verbose == 0){
utils::setTxtProgressBar(progressbar,it)
}else{
cat(paste0("\nIteration [", it, "/", nrow(tpGrid),"]\n"))
}
result <- try(regularizedModel$optimize(rawParameters,
SEM,
lambda[it,],
theta[it,],
alpha[it,])
)
if(is(result, "try-error")) next
rawParameters <- result$rawParameters
fits$nonZeroParameters[it] <- length(rawParameters) -
sum(rawParameters[weights[names(rawParameters)] != 0] == 0)
fits$regObjectiveValue[it] <- result$fit
if(usesLikelihood)
fits$regM2LL[it] <- fits$regObjectiveValue[it]
fits$convergence[it] <- result$convergence
# get unregularized fit:
SEM <- .setParameters(SEM,
names(rawParameters),
values = rawParameters,
raw = TRUE)
fits$objectiveValue[it] <- SEM$fit()
if(usesLikelihood)
fits$m2LL[it] <- fits$objectiveValue[it]
# transform internal parameter representation to "natural" form
transformedParameters <- .getParameters(SEM,
raw = FALSE)
parameterEstimates[it,
names(rawParameters)] <- transformedParameters[names(rawParameters)]
if(!is.null(modifyModel$transformations)){
transformationsAre <- .getParameters(SEM,
raw = FALSE,
transformations = TRUE)
transformationsAre <- transformationsAre[!names(transformationsAre)%in%names(rawParameters)]
transformations[it,
names(transformationsAre)] <- transformationsAre
}
# save implied
if(is(SEM, "Rcpp_SEMCpp") & control$saveDetails){
implied$means[[it]] <- SEM$impliedMeans
implied$covariances[[it]] <- SEM$impliedCovariance
rownames(implied$means[[it]]) <- SEM$manifestNames
dimnames(implied$covariances[[it]]) <- list(SEM$manifestNames,
SEM$manifestNames)
}else if(is(SEM, "Rcpp_mgSEM")){
implied$means[[it]] <- NULL
implied$covariances[[it]] <- NULL
}
# set initial values for next iteration
if(is(SEM, "try-Error")){
# reset
warning(paste0("Fit for tuningParameterConfiguration = ", it,
" resulted in Error!"))
if(is(lavaanModel, "lavaan")){
SEM <- .initializeSEMForRegularization(lavaanModel = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}else if(is.vector(lavaanModel)){
SEM <- .initializeMultiGroupSEMForRegularization(lavaanModels = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}
}else{
if(method == "glmnet"){
# set Hessian for next iteration
regularizedModel$setHessian(result$Hessian)
}
}
}
if(is(SEM, "Rcpp_SEMCpp")) internalOptimization <- list(
"implied" = implied,
"HessiansOfDifferentiablePart" = Hessians,
"functionCalls" = SEM$functionCalls,
"gradientCalls" = SEM$gradientCalls,
"N" = SEM$sampleSize
)
if(is(SEM, "Rcpp_mgSEM")) internalOptimization <- list(
"implied" = implied,
"HessiansOfDifferentiablePart" = Hessians,
"functionCalls" = NA,
"gradientCalls" = NA,
"N" = SEM$sampleSize
)
notes <- unique(notes)
if(length(notes) > 1){
cat("\n")
rlang::inform(
notes
)
}
results <- new("regularizedSEMMixedPenalty",
penalty = sapply(penaltyType, .penaltyTypes),
tuningParameterConfigurations = list(
lambda = cbind("configuration" = 1:nrow(tpGrid),
lambda),
theta = cbind("configuration" = 1:nrow(tpGrid),
theta),
alpha = cbind("configuration" = 1:nrow(tpGrid),
alpha)
),
parameters = parameterEstimates,
fits = fits,
parameterLabels = names(rawParameters),
weights = weights,
regularized = names(weights)[weights!=0],
transformations = transformations,
internalOptimization = internalOptimization,
inputArguments = inputArguments,
notes = notes)
return(results)
}
#' .fitElasticNetMix
#'
#' Optimizes an object with mixed penalty. See ?mixedPenalty for more details.
#'
#' @param mixedPenalty object of class mixedPenalty. This object can be created
#' with the mixedPenalty function. Penalties can be added with the addCappedL1, addElastiNet,
#' addLasso, addLsp, addMcp, and addScad functions.
#' @return object of class regularizedSEMMixedPenalty
#' @keywords internal
.fitElasticNetMix <- function(mixedPenalty){
notes <- c("Notes:")
lavaanModel = mixedPenalty$lavaanModel
method = mixedPenalty$method
modifyModel = mixedPenalty$modifyModel
control = mixedPenalty$control
inputArguments <- as.list(environment())
if(! method %in% c("glmnet"))
stop("Currently ony methods = 'glmnet' is supported for mixtures of elastic net penalties.")
if(method == "glmnet" && !is(control, "controlGlmnet"))
stop("control must be of class controlGlmnet See ?controlGlmnet.")
.checkLavaanModel(lavaanModel = lavaanModel)
### initialize model ####
startingValues <- control$startingValues
### initialize model ####
if(is(lavaanModel, "lavaan")){
SEM <- .initializeSEMForRegularization(lavaanModel = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}else if(is.vector(lavaanModel)){
SEM <- .initializeMultiGroupSEMForRegularization(lavaanModels = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}
N <- SEM$sampleSize
# get parameters in raw form
startingValues <- .getParameters(SEM, raw = TRUE)
rawParameters <- .getParameters(SEM, raw = TRUE)
# set weights
weights <- rep(0, length(startingValues))
names(weights) <- names(startingValues)
penaltyType <- rep("none", length(startingValues))
names(penaltyType) <- names(startingValues)
tpRows <- vector("list", length(mixedPenalty$penalties))
names(tpRows) <- paste0("penalty", 1:length(tpRows))
for(p in 1:length(mixedPenalty$penalties)){
if(any(weights[mixedPenalty$penalties[[p]]$regularized] != 0))
stop("It seems like some parameters appeared in multiple penalties. This is not supported. ",
"The following parameters were found in multiple penalties: ",
paste0(mixedPenalty$penalties[[p]]$regularized[weights[mixedPenalty$penalties[[p]]$regularized] != 0], sep = ","))
weights[mixedPenalty$penalties[[p]]$regularized] <- mixedPenalty$penalties[[p]]$weight
penaltyType[mixedPenalty$penalties[[p]]$regularized] <- "elasticNet"
tpRows[[p]] <- 1:nrow(mixedPenalty$penalties[[p]]$tp)
}
tpGrid <- unique(expand.grid(tpRows))
lambda <- alpha <- matrix(0,
nrow = nrow(tpGrid),
ncol = length(startingValues),
dimnames = list(NULL, names(startingValues)))
for(p in 1:ncol(tpGrid)){
lambda[,mixedPenalty$penalties[[p]]$regularized] <- matrix(
rep(mixedPenalty$penalties[[p]]$tps$lambda[tpGrid[,p]],
length(mixedPenalty$penalties[[p]]$regularized)),
ncol = length(mixedPenalty$penalties[[p]]$regularized))
alpha[,mixedPenalty$penalties[[p]]$regularized] <- matrix(
rep(mixedPenalty$penalties[[p]]$tps$alpha[tpGrid[,p]],
length(mixedPenalty$penalties[[p]]$regularized)),
ncol = length(mixedPenalty$penalties[[p]]$regularized))
}
#### glmnet requires an initial Hessian ####
initialHessian <- .computeInitialHessian(initialHessian = control$initialHessian,
rawParameters = rawParameters,
lavaanModel = lavaanModel,
SEM = SEM,
addMeans = modifyModel$addMeans,
stepSize = control$stepSize,
notes = notes)
notes <- initialHessian$notes
control$initialHessian <- initialHessian$initialHessian
#### prepare regularized model object ####
controlIntern <- list(
initialHessian = control$initialHessian,
stepSize = control$stepSize,
sigma = control$sigma,
gamma = control$gamma,
maxIterOut = control$maxIterOut,
maxIterIn = control$maxIterIn,
maxIterLine = control$maxIterLine,
breakOuter = N*control$breakOuter,
breakInner = N*control$breakInner,
convergenceCriterion = control$convergenceCriterion,
verbose = control$verbose
)
if(is(SEM, "Rcpp_SEMCpp")){
regularizedModel <- new(glmnetEnetSEM,
weights,
controlIntern)
}else if(is(SEM, "Rcpp_mgSEM")){
regularizedModel <- new(glmnetEnetMgSEM,
weights,
controlIntern)
}
#### define tuning parameters and prepare fit results ####
fits <- data.frame(
"tuningParameterConfiguration" = 1:nrow(tpGrid),
"m2LL" = NA,
"regM2LL"= NA,
"nonZeroParameters" = NA,
"convergence" = NA
)
parameterEstimates <- as.data.frame(matrix(NA,
nrow = nrow(tpGrid),
ncol = length(rawParameters)))
colnames(parameterEstimates) <- names(rawParameters)
parameterEstimates <- cbind(
"tuningParameterConfiguration" = 1:nrow(tpGrid),
parameterEstimates
)
if(!is.null(modifyModel$transformations)){
transformationsAre <- .getParameters(SEM,
raw = FALSE,
transformations = TRUE)
transformationsAre <- transformationsAre[!names(transformationsAre)%in%names(rawParameters)]
transformations <- as.data.frame(matrix(NA,
nrow = nrow(tpGrid),
ncol = length(transformationsAre)))
colnames(transformations) <- names(transformationsAre)
transformations <- cbind(
"tuningParameterConfiguration" = 1:nrow(tpGrid),
transformations
)
}else{
transformations <- data.frame()
}
if(control$saveDetails){
Hessians <- list(
"lambda" = lambda,
"alpha" = alpha,
"Hessian" = lapply(1:nrow(tpGrid),
matrix,
data= NA,
nrow=nrow(control$initialHessian),
ncol=ncol(control$initialHessian))
)
}else{
Hessians <- list(NULL)
}
# save model implied matrices
implied <- list(means = vector("list", nrow(tpGrid)),
covariances = vector("list", nrow(tpGrid)))
#### print progress ####
if(control$verbose == 0){
progressbar = utils::txtProgressBar(min = 0,
max = nrow(tpGrid),
initial = 0,
style = 3)
}
#### Iterate over all tuning parameter combinations and fit models ####
for(it in 1:nrow(tpGrid)){
if(control$verbose == 0){
utils::setTxtProgressBar(progressbar,it)
}else{
cat(paste0("\nIteration [", it, "/", nrow(tpGrid),"]\n"))
}
result <- try(regularizedModel$optimize(rawParameters,
SEM,
lambda[it,],
alpha[it,]
)
)
if(is(result, "try-error")) next
rawParameters <- result$rawParameters
fits$nonZeroParameters[it] <- length(rawParameters) -
sum(rawParameters[weights[names(rawParameters)] != 0] == 0)
fits$regM2LL[it] <- result$fit
fits$convergence[it] <- result$convergence
# get unregularized fit:
SEM <- .setParameters(SEM,
names(rawParameters),
values = rawParameters,
raw = TRUE)
fits$m2LL[it] <- SEM$fit()
# transform internal parameter representation to "natural" form
transformedParameters <- .getParameters(SEM,
raw = FALSE)
parameterEstimates[it,
names(rawParameters)] <- transformedParameters[names(rawParameters)]
if(!is.null(modifyModel$transformations)){
transformationsAre <- .getParameters(SEM,
raw = FALSE,
transformations = TRUE)
transformationsAre <- transformationsAre[!names(transformationsAre)%in%names(rawParameters)]
transformations[it,
names(transformationsAre)] <- transformationsAre
}
# save implied
if(is(SEM, "Rcpp_SEMCpp") & control$saveDetails){
implied$means[[it]] <- SEM$impliedMeans
implied$covariances[[it]] <- SEM$impliedCovariance
rownames(implied$means[[it]]) <- SEM$manifestNames
dimnames(implied$covariances[[it]]) <- list(SEM$manifestNames,
SEM$manifestNames)
}else if(is(SEM, "Rcpp_mgSEM")){
implied$means[[it]] <- NULL
implied$covariances[[it]] <- NULL
}
# set initial values for next iteration
if(is(SEM, "try-Error")){
# reset
warning(paste0("Fit for tuningParameterConfiguration = ", it,
" resulted in Error!"))
if(is(lavaanModel, "lavaan")){
SEM <- .initializeSEMForRegularization(lavaanModel = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}else if(is.vector(lavaanModel)){
SEM <- .initializeMultiGroupSEMForRegularization(lavaanModels = lavaanModel,
startingValues = startingValues,
modifyModel = modifyModel)
}
}else{
if(control$saveDetails) Hessians$Hessian[[it]] <- result$Hessian
# set Hessian for next iteration
regularizedModel$setHessian(result$Hessian)
}
}
if(is(SEM, "Rcpp_SEMCpp")) internalOptimization <- list(
"implied" = implied,
"HessiansOfDifferentiablePart" = Hessians,
"functionCalls" = SEM$functionCalls,
"gradientCalls" = SEM$gradientCalls,
"N" = SEM$sampleSize
)
if(is(SEM, "Rcpp_mgSEM")) internalOptimization <- list(
"implied" = implied,
"HessiansOfDifferentiablePart" = Hessians,
"functionCalls" = NA,
"gradientCalls" = NA,
"N" = SEM$sampleSize
)
notes <- unique(notes)
if(length(notes) > 1){
cat("\n")
rlang::inform(
notes
)
}
results <- new("regularizedSEMMixedPenalty",
penalty = penaltyType,
tuningParameterConfigurations = list(
lambda = cbind("configuration" = 1:nrow(tpGrid),
lambda),
alpha = cbind("configuration" = 1:nrow(tpGrid),
alpha)
),
parameters = parameterEstimates,
fits = fits,
parameterLabels = names(rawParameters),
weights = weights,
regularized = names(weights)[weights!=0],
transformations = transformations,
internalOptimization = internalOptimization,
inputArguments = inputArguments,
notes = notes)
return(results)
}
#' .penaltyTypes
#'
#' translates the penalty from a numeric value to the character or from
#' the character to the numeric value. The numeric value is used by the C++ backend.
#' @param penalty either a number or the name of the penalty
#' @return number corresponding to one of the penalties
#' @keywords internal
.penaltyTypes <- function(penalty){
if(is.character(penalty)){
if(penalty == "none"){
return(0)
}
if(penalty == "cappedL1"){
return(1)
}
if(penalty == "lasso"){
return(2)
}
if(penalty == "lsp"){
return(3)
}
if(penalty == "mcp"){
return(4)
}
if(penalty == "scad"){
return(5)
}
stop("Unknown penalty.")
}
if(is.numeric(penalty)){
if(penalty == 0){
return("none")
}
if(penalty == 1){
return("cappedL1")
}
if(penalty == 2){
return("lasso")
}
if(penalty == 3){
return("lsp")
}
if(penalty == 4){
return("mcp")
}
if(penalty == 5){
return("scad")
}
stop("Unknown penalty.")
}
stop("Unknown penalty.")
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.