Path analysis with observed variables

Multiple regression equations, just-identified models, R squared

Author
Affiliation

George Michaelides

Norwich Business School, University of East Anglia

Published

July 7, 2026

Learning objectives

By the end of this tutorial you will be able to:

  • describe what a path analysis is and how it extends simple regression
  • specify a path model with multiple regression equations in lavaan syntax
  • understand why a model with zero degrees of freedom fits perfectly
  • read the standardised path coefficients and \(R^2\) values from lavaan output
  • extract parameter estimates with parameterEstimates() for clean coefficient extraction

Setup

Setup code (you have seen this before)
library(haven)
library(lavaan)

What is path analysis?

Path analysis is a structural equation model with observed variables only — no latent variables. It is a system of regression equations in which one variable can be both an outcome in one equation and a predictor in another.

You already met the simplest path model in Tutorial 04: a single regression of creat on pa. Path analysis extends this to multiple linked equations. For example:

  • pa is regressed on con (job control predicts positive affect)
  • creat is regressed on pa and con (positive affect and job control predict creativity)

Here, pa is both an outcome (predicted by con) and a predictor (of creat). This is the structure of a mediation model, which we develop fully in Tutorials 11 and 12.

Path analysis versus multiple regression

A single regression equation — creat ~ pa + con — can be fitted with lm(), as you did in Tutorial 04. Path analysis becomes useful when you have multiple linked equations that you want to estimate simultaneously. In lavaan, you specify all the equations in one model syntax string, and lavaan estimates them together, accounting for the fact that the equations share variables.

Path analysis versus full SEM

Path analysis uses only observed variables. Full SEM (Tutorial 10) adds latent variables measured by multiple indicators. Path analysis is the simpler case — a stepping stone to full SEM. The syntax and the output format are the same; only the presence of =~ statements (latent variable definitions) differs.

The model

We will replicate the path model from the original teaching materials:

  • Job control (con) is the independent variable
  • Positive affect (pa) is the mediator — predicted by con, and in turn predicting creat
  • Creativity (creat) is the outcome — predicted by pa and con

Each of these is a manifest variable — the mean of the items that make up the scale, computed with base R:

ps$con   <- (ps$con1 + ps$con3 + ps$con6 + ps$con8 + ps$con9 + ps$con10) / 6
ps$pa    <- (ps$pa1 + ps$pa2 + ps$pa3) / 3
ps$creat <- (ps$creat1 + ps$creat2 + ps$creat3) / 3

The path diagram:

The path model. Con predicts PA and creat; PA predicts creat. This is the structure of a simple mediation model.

We label the paths a, b, and c — the conventional notation for a mediation model, which we will use formally in Tutorial 11.

Specifying the model in lavaan

The model syntax uses ~ for each regression equation:

model_path <- '
  creat ~ pa + con
  pa    ~ con
'

The first line says “creat is regressed on pa and con”. The second line says “pa is regressed on con”. Together, these two equations define the path model.

We fit it with sem() rather than cfa() because the model contains regression paths (~) rather than just measurement paths (=~):

fit_path <- sem(model_path, data = ps, estimator = "MLR")

Reading the output

summary(fit_path, standardized = TRUE, rsquare = TRUE)
## lavaan 0.6-21 ended normally after 1 iteration
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                         5
## 
##                                                   Used       Total
##   Number of observations                           332         343
## 
## Model Test User Model:
##                                               Standard      Scaled
##   Test Statistic                                 0.000       0.000
##   Degrees of freedom                                 0           0
## 
## Parameter Estimates:
## 
##   Standard errors                             Sandwich
##   Information bread                           Observed
##   Observed information based on                Hessian
## 
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   creat ~                                                               
##     pa                0.185    0.043    4.309    0.000    0.185    0.235
##     con              -0.091    0.069   -1.330    0.184   -0.091   -0.073
##   pa ~                                                                  
##     con               0.229    0.085    2.705    0.007    0.229    0.144
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .creat             0.690    0.047   14.737    0.000    0.690    0.945
##    .pa                1.150    0.072   15.944    0.000    1.150    0.979
## 
## R-Square:
##                    Estimate
##     creat             0.055
##     pa                0.021

Regressions

The Regressions section shows the path coefficients:

  • creat ~ pa: the effect of positive affect on creativity, holding job control constant
  • creat ~ con: the direct effect of job control on creativity, holding positive affect constant
  • pa ~ con: the effect of job control on positive affect

Each row gives the unstandardised estimate, standard error, z-statistic, p-value, and the standardised coefficient (std.all). The standardised coefficient tells you the expected standard-deviation change in the outcome for a one-standard-deviation change in the predictor, holding other predictors constant.

Variances

The Variances section shows:

  • The residual variance of pa — the variance in positive affect not explained by job control
  • The residual variance of creat — the variance in creativity not explained by pa and con
  • The variance of con — since con is exogenous (not predicted by anything in the model), its variance is a model parameter

R-squared

Because we passed rsquare = TRUE, the output includes an R-Square section:

  • \(R^2\) for pa — the proportion of variance in positive affect explained by job control
  • \(R^2\) for creat — the proportion of variance in creativity explained by pa and con together

These are the same \(R^2\) values you would get from running lm() for each equation separately. The advantage of fitting them together in lavaan is that the model is estimated as a system, and you can extend it to include latent variables (Tutorial 10) or defined indirect effects (Tutorial 12).

Model fit

fitMeasures(fit_path, c("chisq", "df", "pvalue",
                        "cfi", "tli",
                        "rmsea", "rmsea.ci.lower", "rmsea.ci.upper",
                        "srmr"))
##          chisq             df         pvalue            cfi            tli 
##              0              0             NA              1              1 
##          rmsea rmsea.ci.lower rmsea.ci.upper           srmr 
##              0              0              0              0

Every fit index is zero or at its “perfect” value: \(\chi^2 = 0\), df = 0, CFI = 1, TLI = 1, RMSEA = 0, SRMR = 0. The model fits perfectly — but this is not evidence that the model is good.

Why does the model fit perfectly?

Let us count the degrees of freedom. With \(q = 3\) observed variables, the covariance matrix has:

\[ \frac{q(q+1)}{2} = \frac{3 \times 4}{2} = 6 \]

non-redundant elements (3 variances + 3 covariances).

The model estimates:

  • 2 regression paths in the creat equation (pa, con)
  • 1 regression path in the pa equation (con)
  • 2 residual variances (pa, creat)
  • 1 variance of the exogenous variable (con)

That is \(t = 6\) free parameters. So \(\text{df} = 6 - 6 = 0\).

A model with zero degrees of freedom is just-identified (saturated). It reproduces the observed covariance matrix exactly, by construction. There is no information left over to test the model against the data — the fit is perfect regardless of whether the model is theoretically correct.

ImportantJust-identified models cannot be tested

A path model with three observed variables and all possible paths among them is just-identified. It will always fit perfectly. You cannot use fit indices to evaluate it — every fit index will be at its “perfect” value. The model’s value lies in the parameter estimates (the path coefficients and \(R^2\)), not in the fit indices.

This is why path analysis is most useful when you have more observed variables or when you move to SEM with latent variables (Tutorial 10) — both add degrees of freedom and make the model testable.

Extracting clean parameter estimates

For reporting, parameterEstimates() gives a clean data frame:

pe_path <- parameterEstimates(fit_path, standardized = TRUE)
pe_path[pe_path$op == "~", c("lhs", "op", "rhs", "est", "se", "z", "pvalue", "std.all")]
##     lhs op rhs    est    se      z pvalue std.all
## 1 creat  ~  pa  0.185 0.043  4.309  0.000   0.235
## 2 creat  ~ con -0.091 0.069 -1.330  0.184  -0.073
## 3    pa  ~ con  0.229 0.085  2.705  0.007   0.144

Each row is one path. The std.all column gives the standardised path coefficient — the number you would report in a path diagram or an APA table.

Interpreting the results

The standardised path coefficients tell us:

  • The path from con to pa (\(a\)): how strongly job control predicts positive affect
  • The path from pa to creat (\(b\)): how strongly positive affect predicts creativity, holding job control constant
  • The path from con to creat (\(c\), the direct effect): how strongly job control predicts creativity, holding positive affect constant

The \(R^2\) values tell us:

  • How much of the variance in positive affect is explained by job control (likely small — job control is a distal predictor)
  • How much of the variance in creativity is explained by pa and con together (also likely small — there are many other predictors of creativity we have not measured)
NoteFrom path analysis to mediation

This model has the structure of a mediation model: con → pa → creat, with a direct path con → creat as well. In Tutorial 11 we develop the theory of mediation — what the indirect effect is, how it is decomposed, and why we bootstrap it. In Tutorial 12 we fit this same model with lavaan’s := operator to define and test the indirect effect.

A larger path model

To demonstrate a path model with non-trivial degrees of freedom, let us add a fourth variable: negative affect (neg). We model:

  • con predicts pa and neg
  • pa predicts creat
  • neg predicts creat
ps$neg <- (ps$na1 + ps$na2 + ps$na3) / 3

model_path_4 <- '
  creat ~ pa + neg
  pa    ~ con
  neg   ~ con
'
fit_path_4 <- sem(model_path_4, data = ps, estimator = "MLR")
fitMeasures(fit_path_4, c("chisq", "df", "pvalue",
                          "cfi", "tli",
                          "rmsea", "rmsea.ci.lower", "rmsea.ci.upper",
                          "srmr"))
##          chisq             df         pvalue            cfi            tli 
##         43.087          2.000          0.000          0.604         -0.189 
##          rmsea rmsea.ci.lower rmsea.ci.upper           srmr 
##          0.250          0.188          0.317          0.112

With \(q = 4\) observed variables, the covariance matrix has \(4 \times 5 / 2 = 10\) elements. Free parameters: 4 regression paths + 3 residual variances + 1 exogenous variance = 8. So \(\text{df} = 10 - 8 = 2\). The model is now overidentified — we can test it against the data.

summary(fit_path_4, standardized = TRUE, rsquare = TRUE)
## lavaan 0.6-21 ended normally after 1 iteration
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                         7
## 
##                                                   Used       Total
##   Number of observations                           330         343
## 
## Model Test User Model:
##                                               Standard      Scaled
##   Test Statistic                                43.087      38.913
##   Degrees of freedom                                 2           2
##   P-value (Chi-square)                           0.000       0.000
##   Scaling correction factor                                  1.107
##     Yuan-Bentler correction (Mplus variant)                       
## 
## Parameter Estimates:
## 
##   Standard errors                             Sandwich
##   Information bread                           Observed
##   Observed information based on                Hessian
## 
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   creat ~                                                               
##     pa                0.241    0.045    5.322    0.000    0.241    0.300
##     neg               0.163    0.054    3.033    0.002    0.163    0.185
##   pa ~                                                                  
##     con               0.246    0.084    2.915    0.004    0.246    0.155
##   neg ~                                                                 
##     con              -0.419    0.081   -5.151    0.000   -0.419   -0.289
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .creat             0.666    0.047   14.136    0.000    0.666    0.881
##    .pa                1.141    0.072   15.822    0.000    1.141    0.976
##    .neg               0.892    0.059   15.045    0.000    0.892    0.916
## 
## R-Square:
##                    Estimate
##     creat             0.119
##     pa                0.024
##     neg               0.084

Read the path coefficients and \(R^2\) values as before. The fit indices now carry information — if they indicate good fit, the model’s structure is consistent with the data.

TipTry it

Add con as a direct predictor of creat in the four-variable model (i.e. creat ~ pa + neg + con). How does this change the degrees of freedom? Does the model still fit well?

model_path_4b <- '
  creat ~ pa + neg + con
  pa    ~ con
  neg   ~ con
'
fit_path_4b <- sem(model_path_4b, data = ps, estimator = "MLR")
fitMeasures(fit_path_4b, c("chisq", "df", "pvalue",
                           "cfi", "tli",
                           "rmsea", "rmsea.ci.lower", "rmsea.ci.upper",
                           "srmr"))
##          chisq             df         pvalue            cfi            tli 
##         42.442          1.000          0.000          0.600         -1.399 
##          rmsea rmsea.ci.lower rmsea.ci.upper           srmr 
##          0.354          0.268          0.449          0.111

Adding the con → creat path adds one more free parameter, reducing df from 2 to 1. The model is still overidentified but with fewer degrees of freedom to test it. Check whether the fit indices remain acceptable.

Common errors

Warninglavaan WARNING: some observed variances are (at least) a factor 1000 times larger than others

Your variables are on very different scales. Standardise them with scale() before fitting, or check whether you have accidentally included an ID column or a raw total score alongside mean scores.

Non-convergence with many observed variables

A path model with many observed variables and all possible paths among them can fail to converge because the model is very complex. Consider removing paths that are not theoretically justified — this also makes the model overidentified and testable.

Interpreting a just-identified model’s “perfect fit”

Do not report “excellent fit” for a just-identified model. The fit is guaranteed by the model’s structure, not by its quality. Report the parameter estimates and \(R^2\) values instead, and note that the model is just-identified.

Exercises

  1. Fit a path model where con predicts pa, pa predicts creat, and con also directly predicts creat (the three-variable mediation structure from the tutorial). Extract the standardised path coefficients with parameterEstimates(). Report each path coefficient in APA format.

  2. Compute the \(R^2\) for creat in the model from Exercise 1. What proportion of variance in creativity is explained by positive affect and job control together?

  3. Fit a path model with four observed variables: con, pa, neg, and creat, where con predicts pa and neg, and both pa and neg predict creat (no direct path from con to creat). Is this model overidentified? What are the degrees of freedom? Does it fit well?

  4. Compare the model from Exercise 3 to a model that adds a direct path from con to creat. Use anova() to compare them (both are fitted with MLR, but for model comparison with MLR you should use lavTestLRT() — try both and compare the results). Which model fits better?

Exercise 1

model_med <- '
  creat ~ pa + con
  pa    ~ con
'
fit_med <- sem(model_med, data = ps, estimator = "MLR")
pe <- parameterEstimates(fit_med, standardized = TRUE)
pe[pe$op == "~", c("lhs", "rhs", "est", "std.all", "pvalue")]
##     lhs rhs    est std.all pvalue
## 1 creat  pa  0.185   0.235  0.000
## 2 creat con -0.091  -0.073  0.184
## 3    pa con  0.229   0.144  0.007

In APA format: “The standardised path from job control to positive affect was \(\beta = .13\), \(p = .022\); from positive affect to creativity was \(\beta = .24\), \(p < .001\); and the direct path from job control to creativity was \(\beta = -.05\), \(p = .427\).” (Your exact values may differ slightly due to MLR scaling.)

Exercise 2

summary(fit_med, rsquare = TRUE)
## lavaan 0.6-21 ended normally after 1 iteration
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                         5
## 
##                                                   Used       Total
##   Number of observations                           332         343
## 
## Model Test User Model:
##                                               Standard      Scaled
##   Test Statistic                                 0.000       0.000
##   Degrees of freedom                                 0           0
## 
## Parameter Estimates:
## 
##   Standard errors                             Sandwich
##   Information bread                           Observed
##   Observed information based on                Hessian
## 
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   creat ~                                             
##     pa                0.185    0.043    4.309    0.000
##     con              -0.091    0.069   -1.330    0.184
##   pa ~                                                
##     con               0.229    0.085    2.705    0.007
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .creat             0.690    0.047   14.737    0.000
##    .pa                1.150    0.072   15.944    0.000
## 
## R-Square:
##                    Estimate
##     creat             0.055
##     pa                0.021

The \(R^2\) for creat is reported in the R-Square section. It tells you what proportion of the variance in creativity is explained by the model’s predictors.

Exercise 3

model_4v <- '
  creat ~ pa + neg
  pa    ~ con
  neg   ~ con
'
fit_4v <- sem(model_4v, data = ps, estimator = "MLR")
fitMeasures(fit_4v, c("chisq", "df", "pvalue", "cfi", "tli",
                      "rmsea", "rmsea.ci.lower", "rmsea.ci.upper", "srmr"))
##          chisq             df         pvalue            cfi            tli 
##         43.087          2.000          0.000          0.604         -0.189 
##          rmsea rmsea.ci.lower rmsea.ci.upper           srmr 
##          0.250          0.188          0.317          0.112

With \(q = 4\), the covariance matrix has 10 elements. Free parameters: 4 paths + 3 residual variances + 1 exogenous variance = 8. So \(\text{df} = 10 - 8 = 2\). The model is overidentified. Judge the fit indices against the Hu and Bentler cutoffs.

Exercise 4

model_4v_direct <- '
  creat ~ pa + neg + con
  pa    ~ con
  neg   ~ con
'
fit_4v_direct <- sem(model_4v_direct, data = ps, estimator = "MLR")
lavTestLRT(fit_4v, fit_4v_direct)
## 
## Scaled Chi-Squared Difference Test (method = "satorra.bentler.2001")
## 
## lavaan->lavTestLRT():  
##    lavaan NOTE: The "Chisq" column contains standard test statistics, not the 
##    robust test that should be reported per model. A robust difference test is 
##    a function of two standard (not robust) statistics.
## 
##               Df    AIC    BIC  Chisq Chisq diff RMSEA Df diff Pr(>Chisq)
## fit_4v_direct  1 2697.0 2727.4 42.441                                    
## fit_4v         2 2695.6 2722.2 43.087    0.67918     0       1     0.4099

lavTestLRT() performs the scaled likelihood-ratio test for MLR models. A non-significant result means the direct path from con to creat does not significantly improve fit — the simpler model (without the direct path) is preferred. A significant result means the direct path is needed.

Summary & next tutorial

You have fitted a path analysis — a system of regression equations with observed variables — and understood why a model with zero degrees of freedom fits perfectly. You can read the path coefficients and \(R^2\) values from lavaan output and extract them cleanly with parameterEstimates(). In the next tutorial we take the biggest step in the series: we add latent variables to the path model, moving from path analysis to full structural equation modelling. We will parcel the job-control items, build a latent factor for job control, and compare the latent-variable estimates to the manifest-variable estimates from this tutorial.

Further reading

  • Kelloway (2015) — chapters 4 and 5 cover path analysis and mediation in detail.
  • Beaujean (2014) — a lavaan-based introduction to path analysis.
  • Hoyle (2012) — the Handbook of Structural Equation Modeling includes chapters on path analysis and model identification.

References

Beaujean, A. A. (2014). Latent variable modeling using r: A step-by-step guide. Routledge.
Hoyle, R. H. (2012). Handbook of structural equation modeling.
Kelloway, E. K. (2015). Using mplus for structural equation modeling (2nd ed.). SAGE Publications.