Getting Started with umx

Introduction

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:

  1. Load classic data (Holzinger & Swineford, 1939)
  2. Build a simple one-factor model using umxRAM()
  3. View and interpret results using umxSummary()
  4. Save a path diagram as a PNG (safe for non-interactive builds)

Ordinal / WLS workflows are covered in vignette("WLS_in_umx").

The Holzinger and Swineford (1939) data

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       NA

Build a one-factor model with umxRAM()

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:

  • Detects manifests vs latents
  • Adds starting values
  • Runs the model
  • Produces an initial summary

View results with 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)

Standardized solution

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:

  • A table of standardized loadings, variances, and means
  • Model fit information (\(\chi^2\), CFI, TLI, RMSEA)
  • Helpful warnings when fit is poor

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.

Alternative: lavaan-style syntax

umxRAM() also accepts lavaan model syntax directly:

m2 = umxRAM(paste("g =~", paste(manifests, collapse = " + ")),
  data = HS.ability.data)
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.

Path diagram as PNG

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/."
    )
}
Standardized one-factor Holzinger model (PNG)
Standardized one-factor Holzinger model (PNG)

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).

Ordinal and WLS models

For ordered factors, DWLS/WLS, and Satorra–Bentler nested tests, see:

vignette("WLS_in_umx", package = "umx")

Next steps

  • Learn more about path specification: ?umxPath
  • See all the options for umxRAM(): ?umxRAM
  • Control output format: ?umxSummary
  • Compare models: ?umxCompare
  • Uncertainty (SEs, robust SEs, profile CIs): vignette("SEs_robustSEs_and_ProfileCIs", package = "umx")
  • For twin models see the twin modeling functions (umxACE(), etc.)

Welcome to umx!

References

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.