lavaan syntax cheat sheet

Quick reference for =~, ~, , := and friends

Author
Affiliation

George Michaelides

Norwich Business School, University of East Anglia

Published

July 7, 2026

A one-page reference for the lavaan syntax used across this series. Grouped by category. For definitions of the terms, see the glossary; for the Greek symbols, see the notation key.

Model specification

Syntax Name Example Notes
=~ “is measured by” — defines a latent variable pa =~ pa1 + pa2 + pa3 The first indicator’s loading is fixed to 1 by default (marker-variable method)
~ “is regressed on” — defines a regression path creat ~ pa + con Used for structural paths and for lm()-style regressions
~~ “is correlated with” — covariance pa ~~ na Default between exogenous variables; can also free residual covariances
~ 1 intercept / mean pa ~ 1 Usually estimated automatically for observed variables; freed for latents with meanstructure = TRUE
1 * fix a parameter to 1 pa =~ 1 * pa1 Explicit form of the marker-variable default
0 * fix a parameter to 0 pa ~~ 0 * na Explicitly constrain a covariance to zero
n * fix a parameter to a numeric value x ~ 0.5 * y Rarely needed; used for a priori constraints
start() * set a starting value pa =~ start(1.2) * pa1 Helps with convergence problems
label * label a parameter (for constraints or :=) creat ~ b * pa + c * con Two parameters with the same label are constrained equal
c(a, b) * group-specific parameter pa ~ c(a1, a2) * con Used in multigroup SEM; a1 for group 1, a2 for group 2
:= define a new (derived) parameter indirect := a * b Operates on labelled parameters; used for indirect effects

Estimation options

Pass these arguments to cfa(), sem(), or lavaan():

Argument Effect Example
estimator = "MLR" Robust maximum likelihood (Satorra–Bentler). Our default. cfa(model, data = df, estimator = "MLR")
estimator = "ML" Plain maximum likelihood. Required when bootstrapping. sem(model, data = df, estimator = "ML", se = "boot")
se = "boot" Bootstrap standard errors sem(model, data = df, se = "boot", bootstrap = 1000)
bootstrap = N Number of bootstrap replications bootstrap = 1000
std.lv = TRUE Set latent variances to 1; freely estimate all loadings cfa(model, data = df, std.lv = TRUE)
orthogonal = TRUE Force all latent covariances to zero cfa(model, data = df, orthogonal = TRUE)
missing = "ml" Full-information maximum likelihood for missing data (default) sem(model, data = df, missing = "ml")
missing = "listwise" Listwise deletion sem(model, data = df, missing = "listwise")
meanstructure = TRUE Estimate intercepts and latent means cfa(model, data = df, meanstructure = TRUE)
group = "gender" Multigroup analysis cfa(model, data = df, group = "gender")

Entry-point functions

Function Use Example
cfa() Confirmatory factor analysis (measurement model only) fit <- cfa(model, data = df)
sem() Structural equation model (measurement + structural) fit <- sem(model, data = df)
lavaan() The general function; cfa() and sem() are wrappers fit <- lavaan(model, data = df)
growth() Latent growth curve model fit <- growth(model, data = df)

Reading output

Function What it returns Example
summary(fit) A compact summary of parameter estimates summary(fit)
summary(fit, standardized = TRUE) Adds the std.all, std.lv, std.nox columns summary(fit, standardized = TRUE)
summary(fit, fit.measures = TRUE) Adds the fit indices section at the top summary(fit, fit.measures = TRUE)
summary(fit, rsquare = TRUE) Adds an \(R^2\) row for each dependent variable summary(fit, rsquare = TRUE)
parameterEstimates(fit) Parameter estimates as a data frame (one row per parameter) parameterEstimates(fit)
parameterEstimates(fit, standardized = TRUE) Same, with standardised columns parameterEstimates(fit, standardized = TRUE)
parameterEstimates(fit, boot.ci.type = "bca.simple") BCa bootstrap confidence intervals Use after se = "boot"
standardizedSolution(fit) Tidy data frame of standardised parameters only standardizedSolution(fit)
fitMeasures(fit) Named vector of ~40 fit indices fitMeasures(fit)
fitMeasures(fit, c(...)) Selected fit indices fitMeasures(fit, c("chisq","df","pvalue","cfi","tli","rmsea","srmr"))
coef(fit) Just the parameter estimates as a named vector coef(fit)
confint(fit) Confidence intervals for each parameter confint(fit, level = 0.95)
fitted(fit) The model-implied covariance matrix \(\hat{\Sigma}(\theta)\) fitted(fit)
resid(fit) Residual covariance matrix \(S - \hat{\Sigma}(\theta)\) resid(fit)
lavInspect(fit, "rsquare") \(R^2\) for each observed and latent dependent variable lavInspect(fit, "rsquare")
lavInspect(fit, "sampstat") The sample covariance matrix \(S\) lavInspect(fit, "sampstat")

Diagnostics and respecification

Function What it returns Example
modindices(fit) All modification indices modindices(fit)
modindices(fit, minimum.value = 3.84) Only MIs above 3.84 (\(\alpha = .05\)) modindices(fit, minimum.value = 3.84)
modindices(fit, sort. = TRUE) Sorted by MI, largest first modindices(fit, minimum.value = 3.84, sort. = TRUE)
modindices(fit, op = "==~") Only MIs for factor loadings modindices(fit, op = "==~")
anova(fit1, fit2) Likelihood-ratio test between nested models anova(fit1, fit2)
lavTestScore(fit) Score test for freeing fixed parameters lavTestScore(fit)

Multigroup and measurement invariance

Fitting a model in several groups at once and testing whether parameters are equal across them. Used both for measurement invariance testing (Tutorial 17) and for moderation by a categorical variable (Tutorial 16).

Argument / function Effect Example
group = "gender" Fit the model separately in each group, freely by default cfa(model, data = df, group = "gender")
group.equal = "loadings" Constrain factor loadings equal across groups (metric / weak invariance) cfa(model, data = df, group = "g", group.equal = "loadings")
group.equal = c("loadings", "intercepts") Add equal item intercepts (scalar / strong invariance) cfa(model, data = df, group = "g", group.equal = c("loadings", "intercepts"))
group.equal = c("loadings", "intercepts", "residuals") Add equal residual variances (strict invariance) cfa(model, data = df, group = "g", group.equal = c("loadings", "intercepts", "residuals"))
group.partial = c("PA =~ pa3") Release specific equality constraints (partial invariance) cfa(model, data = df, group = "g", group.equal = "loadings", group.partial = c("PA =~ pa3"))
meanstructure = TRUE Estimate intercepts and latent means (required for scalar invariance and latent-mean comparison) cfa(model, data = df, group = "g", group.equal = c("loadings","intercepts"), meanstructure = TRUE)
lavTestLRT(fit1, fit2) Scaled \(\chi^2\) difference test between nested multigroup models lavTestLRT(fit_config, fit_metric)
lavTestScore(fit)$uni Univariate score tests: which equality constraints to release for partial invariance lavTestScore(fit_metric)$uni

The invariance hierarchy, tested in order:

fit_config <- cfa(model, data = df, group = "g", estimator = "MLR")              # 1. configural
fit_metric <- cfa(model, data = df, group = "g", group.equal = "loadings",       # 2. metric (weak)
                  estimator = "MLR")
fit_scalar <- cfa(model, data = df, group = "g",                                  # 3. scalar (strong)
                  group.equal = c("loadings", "intercepts"), estimator = "MLR")
lavTestLRT(fit_config, fit_metric)   # non-sig => metric invariance holds
lavTestLRT(fit_metric, fit_scalar)   # non-sig => scalar invariance holds

Supplement each LRT with fit-index change criteria (Chen (2007)): \(\Delta\)CFI \(\leq .010\) and \(\Delta\)RMSEA \(\leq .015\) indicate the constraint does not meaningfully worsen fit.

Tables and figures

Function What it does Example
texreg::screenreg(fit) Print a regression-style table to the screen texreg::screenreg(fit)
texreg::htmlreg(fit, file = "table.html") Write a table to an HTML file texreg::htmlreg(fit, file = "tab.html")
texreg::texreg(fit, file = "table.tex") Write a table to a LaTeX file texreg::texreg(fit, file = "tab.tex")
semPlot::semPaths(fit) Legacy path-diagram plot semPlot::semPaths(fit, whatLabels = "std")
tidySEM::graph_sem(fit) Modern path diagram from a fitted lavaan object tidySEM::graph_sem(fit)
tidySEM::prepare_graph(fit) Customisable graph object (edit before rendering) tidySEM::prepare_graph(fit) \|> tidySEM::edit_graph(...)

A complete worked example

Putting it all together — the two-factor CFA we walk through in Tutorial 07:

library(lavaan)

model <- '
  NA =~ na1 + na2 + na3
  PA =~ pa1 + pa2 + pa3
  NA ~~ PA
'

fit <- cfa(model, data = ps, estimator = "MLR")
summary(fit, standardized = TRUE, fit.measures = TRUE)
fitMeasures(fit, c("chisq", "df", "pvalue",
                   "cfi", "tli",
                   "rmsea", "rmsea.ci.lower", "rmsea.ci.upper",
                   "srmr"))
modindices(fit, minimum.value = 3.84, sort. = TRUE)
tidySEM::graph_sem(fit)

References

Chen, F. F. (2007). Sensitivity of goodness of fit indexes to lack of measurement invariance. Structural Equation Modeling: A Multidisciplinary Journal, 14(3), 464–504. https://doi.org/10.1080/10705510701301834