Inside Confirmatory Factor Analysis

An interactive walkthrough of what CFA actually does

What's in a correlation matrix?

Suppose we administer 6 questionnaire items to a large sample. We compute the correlations between every pair. The result is a symmetric matrix with ones on the diagonal — a correlation matrix.

This one has a visible pattern: items 1–3 correlate strongly with each other, items 4–6 correlate strongly with each other, and the cross-block correlations are weaker. Something is structuring these data. The question is: what?

Observed Correlation Matrix (S)

A model of the correlations

CFA proposes an explanation: the items correlate because they share latent factors. Items 1–3 reflect Factor 1, items 4–6 reflect Factor 2, and the two factors may themselves be correlated.

The diagram below shows the true model — the actual structure that generated the observed matrix on this page. In practice, of course, you never know the true model. You hypothesize one and test it against the data. But having the ground truth here lets us see exactly what happens when parameters are right, and when they're wrong.

Each arrow from a factor to an item carries a loading (λ) — the strength of influence. The curve between factors represents their correlation (φ). Each item also has residual variance (θ) — the part not explained by the factor.

The Model

This small set of parameters — 6 loadings, 1 factor correlation — fully determines a predicted correlation matrix. The central equation is:

Σ(θ)Model-implied correlation matrix (6 × 6)
       x₁    x₂    x₃    x₄    x₅    x₆
x₁   1.00  .600  .488  .184  .223  .158
x₂    ·    1.00  .520  .196  .238  .168
x₃    ·      ·   1.00  .159  .193  .137
x₄    ·      ·     ·   1.00  .595  .420
x₅    ·      ·     ·     ·   1.00  .510
x₆    ·      ·     ·     ·     ·   1.00
= ΛFactor loading matrix (6 × 2)
       F₁    F₂
x₁   .75     0
x₂   .80     0
x₃   .65     0
x₄    0    .70
x₅    0    .85
x₆    0    .60
ΦFactor correlation matrix (2 × 2)
       F₁    F₂
F₁   1.00  .35
F₂    .35  1.00
Λ′Transpose of Λ (2 × 6)
       x₁    x₂    x₃    x₄    x₅    x₆
F₁   .75   .80   .65    0     0     0
F₂    0     0     0    .70   .85   .60
+ ΘResidual variance matrix (6 × 6 diagonal)
θᵢ = 1 − λᵢ²

       x₁    x₂    x₃    x₄    x₅    x₆
x₁   .438
x₂     ·   .360
x₃     ·     ·   .578
x₄     ·     ·     ·   .510
x₅     ·     ·     ·     ·   .278
x₆     ·     ·     ·     ·     ·   .640

where Λ is the matrix of loadings, Φ is the factor correlation matrix, and Θ is the diagonal matrix of residual variances. In the standardized case, θᵢ = 1 − λᵢ², so the loadings are the only free parameters governing each item.

From parameters to predictions

This is the core of CFA: a handful of parameters generate an entire correlation matrix. But why do we need a second matrix at all?

The observed matrix tells you what happened in your data. The model-implied matrix tells you what should have happened if your theory is correct. "Implied" means: given these specific loadings and this factor correlation, what correlations between items does the model predict? If you claim two items share a factor, the model implies they should correlate — and it tells you exactly how much. Comparing the two matrices is how you test whether your measurement theory actually fits the data.

Drag the sliders below and watch the implied matrix update in real time. Compare it to the observed matrix on the left — how close can you get?

Factor 1 Loadings
Factor 2 Loadings
Factor Correlation
Residual Variances (derived)
Observed (S)
Model-Implied (Σ)
Try it: Drag all loadings to their minimum — the implied matrix approaches an identity matrix (no correlations). Then increase them and watch correlations emerge. Set φ to 0 and notice the cross-block correlations vanish entirely.

How wrong are we?

The difference between the observed and implied matrices is the residual matrix. CFA estimation finds the parameters that make these residuals as small as possible, specifically by minimizing a fit function related to the maximum likelihood discrepancy.

Residual Matrix (S − Σ)
Fit Function Value
FML = log|Σ| + tr(SΣ⁻¹) − log|S| − p
Try it: Click "Randomize" to scramble the parameters, then "Fit the Model" to watch the optimizer converge. The sliders will animate toward the best-fitting values. Try randomizing and fitting several times — the optimizer should find roughly the same solution each time.

Why constraints matter

A subtle problem: different parameter values can produce the exact same implied matrix. Let Ψ be the factor covariance matrix — the same object as Φ in Section 2, but with the factor variances left free on the diagonal rather than fixed at 1. If we multiply all loadings by some constant c and divide the factor variances by , the product ΛΨΛ′ is unchanged. The model is underidentified.

The standard fix is to constrain the factor variances to 1 — which is exactly what we do when we work with standardized loadings. This collapses the infinite family of equivalent solutions down to one. Below, drag the scaling constant and watch two parameter sets produce identical implied correlations.

Scaling constant c: 1.00
Both produce this implied matrix:
The implied matrix never changes no matter how you drag the slider — that's the identification problem. Fixing factor variance = 1 (c = 1) selects one unique point from this infinite family of solutions.

Reading fit indices

Once the model is fitted, we need to evaluate how well it reproduces the observed correlations. Fit indices are just different summaries of the residual matrix. They all look at the same information through different lenses.

The indices below update live based on the current slider settings. Click "Fit the Model" to see what good fit looks like, then drag a loading to its minimum and watch the indices deteriorate.

Factor 1 Loadings
Factor 2 Loadings
Factor Correlation
Sample Size
N 500

χ² — The likelihood ratio test statistic. Drag the N slider and watch it scale linearly.

RMSEA — Badness-of-fit per degree of freedom. Below .06 is conventionally "good."

SRMR — Average absolute residual correlation. Below .08 is conventionally "good."

CFI — Improvement over a baseline (independence) model. Above .95 is conventionally "good."

The N slider re-scales the indices without redrawing the data — the observed matrix stays the one sample of 500 that this page loaded with. So at N = 5000 you are reading the fit statistics this residual matrix would produce in a sample that large, not statistics from a sample actually drawn. That is exactly what makes the point about χ²: nothing about the misfit changed, only the sample size multiplying it. The same slider drives the fit statistics in Sections 7 and 11, and sets how many people are plotted in Section 8.

When the model is wrong

Everything above assumes the fitted model matches the process that generated the data. But what if it doesn't? A model can converge, produce parameter estimates, and report fit indices — all while being fundamentally wrong. The residuals reveal this: under correct specification they look like random noise, but under misspecification they show structure.

Select a data-generating scenario below. The "correct" scenario uses the same 2-factor simple structure you've been working with. The others generate data from a different truth, then fit the same 2-factor model to it. Watch the residual matrix and modification indices.

Data-Generating Model
Residual Matrix (S − Σ̂)
Fit Indices (Fitted Model)
Modification Indices
MI = expected χ² decrease if this cross-loading were freed
Try it: Select "Missing Factor" and notice the block of structured residuals — the model can't reproduce correlations between items that actually belong to separate factors it doesn't know about. Select "Omitted Cross-Loading" and notice the residuals concentrate around one item. Select "Correlated Residual" and notice how the extra covariance between x₃ and x₄ (which load on different factors) leaves almost all of its mark in that one cell — a residual near +0.23, against about 0.03 in the rest of the cross-block. The optimizer raises φ slightly to absorb what it can (typically .35 → about .38), but a single pair's shared residual cannot be spread across the block. In each case, the modification indices point toward the affected items.
The lesson: Good fit indices do not mean the model is correct — they mean the model reproduces the covariance matrix reasonably well. Structured residuals and large modification indices are the diagnostic. A model that fits well by the numbers but shows patterned residuals is telling you something is missing.

What misspecification does to factor scores

The preceding sections focused on reproducing correlations and diagnosing model fit. But in practice, we usually want factor scores — estimated latent variable values for each person. These scores are computed from the model parameters. If the parameters are wrong, the scores are wrong.

Below, 500 simulated individuals are drawn from the true model — the N slider decides how many of a 5,000-person pool get plotted. Each scatterplot puts a person's factor score computed with the true parameters on the horizontal axis against the same person's score computed with whatever the sliders are currently set to on the vertical axis. Both are estimates from the same six item responses; only the parameters used to weight those responses differ. When the sliders match the true values, the dots fall on the diagonal. As you introduce misspecification, the cloud disperses.

Factor 1 Loadings
Factor 2 Loadings
Factor Correlation
Sample Size
N 500
Factor 1 Scores
Factor 2 Scores
Try it: Click "Fit the Model" first — the dots should cluster tightly around the diagonal, r ≈ .99 on both plots. Now drag λ₁ to its maximum: F1 disperses to about .89 while F2 barely moves (still ≈ .99). At the fitted φ ≈ .35 the factors are nearly independent, so an error in one block stays in that block. Now fit again, raise φ to its maximum, and repeat the same drag: this time F2 falls with it, from about .91 to about .73. Misspecification propagates through the factor correlation, but only in proportion to how large that correlation is.
Note: These are regression factor scores (W = ΦΛ′Σ⁻¹), the most common method. They are not the only method — Bartlett scores and others exist and would show somewhat different sensitivity patterns. The key lesson is general: wrong parameters yield wrong scores.

Reliability from the factor model

Factor loadings tell you how much of each item's variance is shared with the factor. This directly yields reliability — the proportion of subscale score variance that is true-score variance. Two coefficients are shown below: ω (omega), which uses the actual loadings, and α (alpha), which assumes all loadings are equal.

When loadings are roughly equal, the two agree. As you make them more unequal, alpha departs from omega. Note that reliability is computed per factor — each subscale gets its own coefficient. Computing a single reliability for all 6 items across both factors would conflate two distinct constructs and produce a number that is difficult to interpret.

Factor 1 Loadings
Factor 2 Loadings

ω — Computed from the actual loadings: \(\omega = \frac{(\sum \lambda_i)^2}{(\sum \lambda_i)^2 + \sum \theta_i}\). Uses the factor model directly.

α — Computed from the mean inter-item correlation. Equivalent to omega only when all loadings are equal (tau-equivalence).

Try it: Click "Fit the Model" so the loadings match the data — notice that omega and alpha are nearly identical while the loadings within a factor are similar. Then drag one loading to its maximum and another to its minimum within the same factor. Both coefficients fall, but alpha falls about twice as far (roughly .30 against omega's .15), and the gap between them opens to about .15. That gap is the point: alpha assumes the loadings are equal, so as soon as they are not, it underestimates the reliability the factor model implies. Note the absence of the factor correlation slider: per-factor reliability depends only on the loadings within that factor.

Sampling variability

Everything on this page — the observed correlations, the fitted parameters, the fit indices — is estimated from a sample. Draw a different sample from the same population and you get different numbers. With small samples, parameter estimates can be wildly off even when the model is correct.

Click "Draw Samples" to generate 4 independent samples from the true model, fit the 2-factor model to each, and compare the recovered loadings to the true values.

Sample size: 80
Try it: Draw samples at N = 50, then at N = 500. At small N, loadings bounce around substantially and some samples may produce poor fit even though the model is correct. At large N, the estimates cluster tightly around the true values.

Comparing models

A natural question: how do we know two factors are needed? Maybe one factor is enough. Below, both models are fitted to the same observed data. The 1-factor model forces all 6 items onto a single factor. Compare the residual matrices — the 1-factor model can't hold two clusters at once, so it fits one block and leaves large positive residuals inside the other.

1-Factor Model — Residuals (S − Σ̂)
2-Factor Model — Residuals (S − Σ̂)
Why AIC/BIC? These two models are nested — the 1-factor model is the 2-factor model with φ fixed at 1 — so a chi-square difference test is available and is routinely used. But φ = 1 sits on the boundary of the admissible parameter space, which makes the usual χ²(1) reference distribution inexact. Information criteria need no reference distribution: AIC and BIC balance fit against complexity, lower values are better, and when the difference is large the conclusion is unambiguous.
This visualization is built as a pedagogical tool, this is not lavaan!

The observed matrix is a sample correlation matrix from 500 simulated observations drawn from the true model (a larger pool is generated for the factor score scatterplots). Each page load generates a fresh sample, so the exact numbers will vary. The optimizer uses gradient descent on the ML discrepancy function. All computations happen in your browser.
Citation

Persson, B. N. (2026). Confirmatory factor analysis [Interactive visualization]. https://bnpersson.github.io/visualizations/cfa/

@misc{Persson2026cfa,
  author = {Björn N. Persson},
  year = {2026},
  title = {Confirmatory Factor Analysis},
  note = {Interactive visualization},
  url = {https://bnpersson.github.io/visualizations/cfa/}}