umx is a package that makes building, running, and
reporting Structural Equation Models (SEM) fast, efficient, and readable
in R.
This vignette shows a new user how to:
umxRAM()umxSummary()Ordinal / WLS workflows are covered in
vignette("WLS_in_umx").
This well-known dataset contains scores on nine cognitive tests for
301 school children. The variables x1 to x9
measure different aspects of cognitive ability.
Because umx imports OpenMx, the data is
easy to load:
# Load the classic Holzinger Swineford 1939 data (x1:x9)
data("HS.ability.data")
# Define the manifest (observed) variables
manifests = c("visual","cubes","flags","paragrap","sentence","wordm","addition","counting","straight")
# Quick look at the correlations
umx_cor(HS.ability.data[, manifests])
#> TODO: umx_cor assumes no missing data, n is just nrow() !!
#> lower tri = correlation; upper tri = p-value
#> visual cubes flags paragrap sentence wordm addition counting straight
#> visual NA 0.00 0.00 0.00 0.00 0.00 0.25 0.00 0
#> cubes 0.30 NA 0.00 0.01 0.02 0.00 0.19 0.11 0
#> flags 0.44 0.34 NA 0.01 0.18 0.00 0.21 0.00 0
#> paragrap 0.37 0.15 0.16 NA 0.00 0.00 0.00 0.06 0
#> sentence 0.29 0.14 0.08 0.73 NA 0.00 0.07 0.02 0
#> wordm 0.36 0.19 0.20 0.70 0.72 NA 0.03 0.01 0
#> addition 0.07 -0.08 0.07 0.18 0.10 0.12 NA 0.00 0
#> counting 0.22 0.09 0.19 0.11 0.14 0.15 0.49 NA 0
#> straight 0.39 0.21 0.33 0.21 0.23 0.21 0.34 0.45 NAumxRAM()umxRAM() lets you describe models using simple
umxPath() statements. Any variable name not in the data is
treated as a latent variable.
Here we build a model in which a single latent factor g
(general ability) accounts for the covariances among the manifests.
m1 = umxRAM("Holzinger_one_factor", data = HS.ability.data,
# Factor loadings (G predicts each x)
umxPath("g", to = manifests),
# Residual means and variances for the observed variables
umxPath(v.m. = manifests),
# Fix the latent factor variance to 1 (for identification)
umxPath(v1m0 = "g")
)That’s it — umxRAM() automatically:
umxSummary()umxSummary() gives clean output. Tip:
report = "html" gives a publication-ready table.
umxSummary(m1)
#> ?umxSummary options: std=T|F', digits=, report= 'html', uncertainty = "RobustSE", filter= 'NS' & more
#> Running Saturated Holzinger_one_factor with 54 parameters
#> Running Independence Holzinger_one_factor with 18 parameters
#>
#>
#> Table: Parameter loadings for model 'Holzinger_one_factor'
#>
#> | |name | Estimate|SE |CI |type |
#> |:--|:----------------------|--------:|:-----|:-------------------------|:---------------|
#> |1 |g_to_straight | 11.13|2.18 |11.13 [6.84, 15.41] |Factor loading |
#> |2 |g_to_counting | 4.07|1.23 |4.07 [1.65, 6.49] |Factor loading |
#> |3 |g_to_addition | 4.56|1.52 |4.56 [1.58, 7.54] |Factor loading |
#> |4 |g_to_wordm | 6.42|0.38 |6.42 [5.68, 7.15] |Factor loading |
#> |5 |g_to_sentence | 4.33|0.25 |4.33 [3.84, 4.83] |Factor loading |
#> |6 |g_to_paragrap | 2.96|0.17 |2.96 [2.62, 3.29] |Factor loading |
#> |7 |g_to_flags | 2.01|0.55 |2.01 [0.93, 3.09] |Factor loading |
#> |8 |g_to_cubes | 1.04|0.29 |1.04 [0.48, 1.6] |Factor loading |
#> |9 |g_to_visual | 3.06|0.41 |3.06 [2.26, 3.86] |Factor loading |
#> |19 |g_with_g | 1.00|0 |1 [1, 1] |Factor Variance |
#> |20 |one_to_straight | 193.44|2.09 |193.44 [189.34, 197.54] |Mean |
#> |21 |one_to_counting | 110.54|1.17 |110.54 [108.26, 112.83] |Mean |
#> |22 |one_to_addition | 96.24|1.44 |96.24 [93.42, 99.06] |Mean |
#> |23 |one_to_wordm | 15.30|0.44 |15.3 [14.43, 16.16] |Mean |
#> |24 |one_to_sentence | 17.36|0.3 |17.36 [16.78, 17.94] |Mean |
#> |25 |one_to_paragrap | 9.18|0.2 |9.18 [8.79, 9.58] |Mean |
#> |26 |one_to_flags | 18.00|0.52 |18 [16.98, 19.02] |Mean |
#> |27 |one_to_cubes | 24.35|0.27 |24.35 [23.82, 24.88] |Mean |
#> |28 |one_to_visual | 29.61|0.4 |29.61 [28.82, 30.4] |Mean |
#> |10 |straight_with_straight | 1194.59|98.87 |1194.59 [1000.8, 1388.38] |Residual |
#> |11 |counting_with_counting | 392.24|32.17 |392.24 [329.19, 455.29] |Residual |
#> |12 |addition_with_addition | 603.22|49.41 |603.22 [506.39, 700.06] |Residual |
#> |13 |wordm_with_wordm | 17.46|2.11 |17.46 [13.33, 21.58] |Residual |
#> |14 |sentence_with_sentence | 7.77|0.94 |7.77 [5.93, 9.61] |Residual |
#> |15 |paragrap_with_paragrap | 3.42|0.43 |3.42 [2.58, 4.26] |Residual |
#> |16 |flags_with_flags | 77.54|6.37 |77.54 [65.06, 90.03] |Residual |
#> |17 |cubes_with_cubes | 21.04|1.73 |21.04 [17.65, 24.42] |Residual |
#> |18 |visual_with_visual | 39.53|3.34 |39.53 [32.98, 46.07] |Residual |
#>
#> Model Fit: χ²(27) = 311.87, p < 0.001; CFI = 0.677; TLI = 0.57; RMSEA = 0.187
#> TLI is worse than desired (>.95)
#> RMSEA is worse than desired (<.06)Add std = TRUE to see standardized estimates (useful for
interpretation):
umxSummary(m1, std = TRUE)
#> ?umxSummary options: std=T|F', digits=, report= 'html', uncertainty = "RobustSE", filter= 'NS' & more
#> Running Saturated Holzinger_one_factor with 54 parameters
#> Running Independence Holzinger_one_factor with 18 parameters
#>
#>
#> Table: Parameter loadings for model 'Holzinger_one_factor'
#>
#> | |name | Std.Estimate|Std.SE |CI |type |
#> |:--|:----------------------|------------:|:------|:-----------------|:---------------|
#> |1 |g_to_straight | 0.31|0.06 |0.31 [0.2, 0.42] |Factor loading |
#> |2 |g_to_counting | 0.20|0.06 |0.2 [0.08, 0.32] |Factor loading |
#> |3 |g_to_addition | 0.18|0.06 |0.18 [0.07, 0.3] |Factor loading |
#> |4 |g_to_wordm | 0.84|0.02 |0.84 [0.79, 0.88] |Factor loading |
#> |5 |g_to_sentence | 0.84|0.02 |0.84 [0.8, 0.89] |Factor loading |
#> |6 |g_to_paragrap | 0.85|0.02 |0.85 [0.8, 0.89] |Factor loading |
#> |7 |g_to_flags | 0.22|0.06 |0.22 [0.11, 0.34] |Factor loading |
#> |8 |g_to_cubes | 0.22|0.06 |0.22 [0.11, 0.34] |Factor loading |
#> |9 |g_to_visual | 0.44|0.05 |0.44 [0.34, 0.54] |Factor loading |
#> |19 |g_with_g | 1.00|0 |1 [1, 1] |Factor Variance |
#> |20 |one_to_straight | 5.33|0.22 |5.33 [4.89, 5.77] |Mean |
#> |21 |one_to_counting | 5.47|0.23 |5.47 [5.02, 5.92] |Mean |
#> |22 |one_to_addition | 3.85|0.17 |3.85 [3.52, 4.18] |Mean |
#> |23 |one_to_wordm | 2.00|0.1 |2 [1.8, 2.19] |Mean |
#> |24 |one_to_sentence | 3.37|0.15 |3.37 [3.08, 3.66] |Mean |
#> |25 |one_to_paragrap | 2.63|0.12 |2.63 [2.4, 2.87] |Mean |
#> |26 |one_to_flags | 1.99|0.1 |1.99 [1.8, 2.19] |Mean |
#> |27 |one_to_cubes | 5.18|0.22 |5.18 [4.75, 5.61] |Mean |
#> |28 |one_to_visual | 4.23|0.18 |4.23 [3.88, 4.59] |Mean |
#> |10 |straight_with_straight | 0.91|0.03 |0.91 [0.84, 0.97] |Residual |
#> |11 |counting_with_counting | 0.96|0.02 |0.96 [0.91, 1.01] |Residual |
#> |12 |addition_with_addition | 0.97|0.02 |0.97 [0.92, 1.01] |Residual |
#> |13 |wordm_with_wordm | 0.30|0.04 |0.3 [0.22, 0.37] |Residual |
#> |14 |sentence_with_sentence | 0.29|0.04 |0.29 [0.22, 0.37] |Residual |
#> |15 |paragrap_with_paragrap | 0.28|0.04 |0.28 [0.21, 0.36] |Residual |
#> |16 |flags_with_flags | 0.95|0.03 |0.95 [0.9, 1] |Residual |
#> |17 |cubes_with_cubes | 0.95|0.03 |0.95 [0.9, 1] |Residual |
#> |18 |visual_with_visual | 0.81|0.04 |0.81 [0.72, 0.9] |Residual |
#>
#> Model Fit: χ²(27) = 311.87, p < 0.001; CFI = 0.677; TLI = 0.57; RMSEA = 0.187
#> TLI is worse than desired (>.95)
#> RMSEA is worse than desired (<.06)The output includes:
Note: A single-factor model is a poor fit to these data (as expected — the data were designed to measure three correlated abilities). This is intentional for demonstration: you can immediately see how
umxSummary()highlights areas for model improvement.
umxRAM() also accepts lavaan model syntax directly:
umxSummary(m2, std = TRUE)
#> ?umxSummary options: std=T|F', digits=, report= 'html', uncertainty = "RobustSE", filter= 'NS' & more
#> Running Saturated m1 with 54 parameters
#> Running Independence m1 with 18 parameters
#>
#>
#> Table: Parameter loadings for model 'm1'
#>
#> | |name | Std.Estimate|Std.SE |CI |type |
#> |:--|:---------------|------------:|:------|:-----------------|:---------------|
#> |1 |p9_ | 0.31|0.06 |0.31 [0.2, 0.42] |Factor loading |
#> |2 |p8_ | 0.20|0.06 |0.2 [0.08, 0.32] |Factor loading |
#> |3 |p7_ | 0.18|0.06 |0.18 [0.07, 0.3] |Factor loading |
#> |4 |p6_ | 0.84|0.02 |0.84 [0.79, 0.88] |Factor loading |
#> |5 |p5_ | 0.84|0.02 |0.84 [0.8, 0.89] |Factor loading |
#> |6 |p4_ | 0.85|0.02 |0.85 [0.8, 0.89] |Factor loading |
#> |7 |p3_ | 0.22|0.06 |0.22 [0.11, 0.34] |Factor loading |
#> |8 |p2_ | 0.22|0.06 |0.22 [0.11, 0.34] |Factor loading |
#> |9 |p1_ | 0.44|0.05 |0.44 [0.34, 0.54] |Factor loading |
#> |19 |p19_ | 1.00|0 |1 [1, 1] |Factor Variance |
#> |20 |one_to_straight | 5.33|0.22 |5.33 [4.89, 5.77] |Mean |
#> |21 |one_to_counting | 5.47|0.23 |5.47 [5.02, 5.92] |Mean |
#> |22 |one_to_addition | 3.85|0.17 |3.85 [3.52, 4.18] |Mean |
#> |23 |one_to_wordm | 2.00|0.1 |2 [1.8, 2.19] |Mean |
#> |24 |one_to_sentence | 3.37|0.15 |3.37 [3.08, 3.66] |Mean |
#> |25 |one_to_paragrap | 2.63|0.12 |2.63 [2.39, 2.87] |Mean |
#> |26 |one_to_flags | 1.99|0.1 |1.99 [1.8, 2.19] |Mean |
#> |27 |one_to_cubes | 5.18|0.22 |5.18 [4.75, 5.61] |Mean |
#> |28 |one_to_visual | 4.23|0.18 |4.23 [3.88, 4.59] |Mean |
#> |10 |p18_ | 0.91|0.03 |0.91 [0.84, 0.97] |Residual |
#> |11 |p17_ | 0.96|0.02 |0.96 [0.91, 1.01] |Residual |
#> |12 |p16_ | 0.97|0.02 |0.97 [0.92, 1.01] |Residual |
#> |13 |p15_ | 0.30|0.04 |0.3 [0.22, 0.37] |Residual |
#> |14 |p14_ | 0.29|0.04 |0.29 [0.22, 0.37] |Residual |
#> |15 |p13_ | 0.28|0.04 |0.28 [0.21, 0.36] |Residual |
#> |16 |p12_ | 0.95|0.03 |0.95 [0.9, 1] |Residual |
#> |17 |p11_ | 0.95|0.03 |0.95 [0.9, 1] |Residual |
#> |18 |p10_ | 0.81|0.04 |0.81 [0.72, 0.9] |Residual |
#>
#> Model Fit: χ²(27) = 311.87, p < 0.001; CFI = 0.677; TLI = 0.57; RMSEA = 0.187
#> TLI is worse than desired (>.95)
#> RMSEA is worse than desired (<.06)This is handy when you have a lavaan model string from elsewhere.
Interactive browser plots (DiagrammeR widgets) can hang or stall
non-interactive package checks. For vignettes and scripts, write a
PNG via graphviz → SVG → PNG, then include it. Requires
Suggested packages DiagrammeR, DiagrammeRsvg,
and rsvg.
# Prefer a stored PNG under vignettes/figures/ (no browser). Regenerate if missing.
figDir = "figures"
dir.create(figDir, showWarnings = FALSE)
gvPath = file.path(figDir, "holzinger_one_factor.gv")
pngPath = file.path(figDir, "holzinger_one_factor.png")
if (!file.exists(pngPath)) {
# digraph only; capture.output silences plot()'s cat() of the graph
dot = NULL
utils::capture.output({
dot <<- plot(m1, std = TRUE, means = FALSE, file = NA)
})
cat(dot, file = gvPath)
# 1) Graphviz CLI
dotBin = Sys.which("dot")
if (nzchar(dotBin)) {
system2(dotBin, c("-Tpng", "-o", pngPath, gvPath), stdout = FALSE, stderr = FALSE)
}
# 2) DiagrammeR → SVG → PNG (Suggests: DiagrammeRsvg, rsvg)
if (!file.exists(pngPath) &&
requireNamespace("DiagrammeR", quietly = TRUE) &&
requireNamespace("DiagrammeRsvg", quietly = TRUE) &&
requireNamespace("rsvg", quietly = TRUE)) {
svgTxt = DiagrammeRsvg::export_svg(DiagrammeR::grViz(dot))
rsvg::rsvg_png(charToRaw(svgTxt), pngPath)
}
}
if (file.exists(pngPath)) {
knitr::include_graphics(pngPath)
} else {
message(
"PNG plot unavailable (install graphviz `dot`, or DiagrammeR + DiagrammeRsvg + rsvg). ",
"Ship/regenerate ", pngPath, " under vignettes/figures/."
)
}We can also get robust measures of fit and uncertainty using
uncertainty = "RobustSE". These use sandwich-based scaling
+ Brosseau-Liard & Savalei (2012) adjustments to CFI/TLI/RMSEA:
umxSummary(m1, std = TRUE, uncertainty = "RobustSE", refModels = FALSE)
#>
#>
#> Table: Parameter loadings for model 'Holzinger_one_factor'
#>
#> | |name | Std.Estimate|Std.SE |CI |type |
#> |:--|:----------------------|------------:|:------|:-----------------|:---------------|
#> |1 |g_to_straight | 0.31|0.07 |0.31 [0.17, 0.44] |Factor loading |
#> |2 |g_to_counting | 0.20|0.07 |0.2 [0.07, 0.34] |Factor loading |
#> |3 |g_to_addition | 0.18|0.07 |0.18 [0.05, 0.31] |Factor loading |
#> |4 |g_to_wordm | 0.84|0.02 |0.84 [0.79, 0.88] |Factor loading |
#> |5 |g_to_sentence | 0.84|0.02 |0.84 [0.8, 0.88] |Factor loading |
#> |6 |g_to_paragrap | 0.85|0.02 |0.85 [0.8, 0.9] |Factor loading |
#> |7 |g_to_flags | 0.22|0.07 |0.22 [0.09, 0.36] |Factor loading |
#> |8 |g_to_cubes | 0.22|0.07 |0.22 [0.09, 0.35] |Factor loading |
#> |9 |g_to_visual | 0.44|0.06 |0.44 [0.31, 0.56] |Factor loading |
#> |19 |g_with_g | 1.00|0 |1 [1, 1] |Factor Variance |
#> |20 |one_to_straight | 5.33|0.23 |5.33 [4.87, 5.78] |Mean |
#> |21 |one_to_counting | 5.47|0.27 |5.47 [4.94, 6] |Mean |
#> |22 |one_to_addition | 3.85|0.15 |3.85 [3.57, 4.14] |Mean |
#> |23 |one_to_wordm | 2.00|0.08 |2 [1.83, 2.16] |Mean |
#> |24 |one_to_sentence | 3.37|0.15 |3.37 [3.08, 3.65] |Mean |
#> |25 |one_to_paragrap | 2.63|0.11 |2.63 [2.41, 2.86] |Mean |
#> |26 |one_to_flags | 1.99|0.07 |1.99 [1.86, 2.12] |Mean |
#> |27 |one_to_cubes | 5.18|0.22 |5.18 [4.75, 5.61] |Mean |
#> |28 |one_to_visual | 4.23|0.2 |4.23 [3.83, 4.64] |Mean |
#> |10 |straight_with_straight | 0.91|0.04 |0.91 [0.82, 0.99] |Residual |
#> |11 |counting_with_counting | 0.96|0.03 |0.96 [0.9, 1.01] |Residual |
#> |12 |addition_with_addition | 0.97|0.02 |0.97 [0.92, 1.01] |Residual |
#> |13 |wordm_with_wordm | 0.30|0.04 |0.3 [0.22, 0.37] |Residual |
#> |14 |sentence_with_sentence | 0.29|0.04 |0.29 [0.22, 0.37] |Residual |
#> |15 |paragrap_with_paragrap | 0.28|0.04 |0.28 [0.2, 0.36] |Residual |
#> |16 |flags_with_flags | 0.95|0.03 |0.95 [0.89, 1.01] |Residual |
#> |17 |cubes_with_cubes | 0.95|0.03 |0.95 [0.89, 1.01] |Residual |
#> |18 |visual_with_visual | 0.81|0.05 |0.81 [0.7, 0.92] |Residual |
#>
#> *Statistical Note*: Robust ML indices use sandwich-based scaling + Brosseau-Liard & Savalei (2012) adjustments to CFI/TLI/RMSEA (c_model = 0.011, c_null = 1.000).For ordered factors, DWLS/WLS, and Satorra–Bentler nested tests, see:
?umxPathumxRAM():
?umxRAM?umxSummary?umxComparevignette("SEs_robustSEs_and_ProfileCIs", package = "umx")umxACE(), etc.)Welcome to umx!
Holzinger, K. J., & Swineford, F. (1939). A study in factor analysis: The stability of a bi-factor solution. Supplementary Educational Monographs, 48. University of Chicago Press.