library(tidyverse)
library(psych)
library(lavaan)
employees <- read_csv(
"data/employee_survey.csv",
show_col_types = FALSE
) |>
mutate(
leadership_4 = 6 - leadership_4_reverse,
engagement_4 = 6 - engagement_4_reverse
)
item_names <- c(
"leadership_1", "leadership_2", "leadership_3", "leadership_4",
"engagement_1", "engagement_2", "engagement_3", "engagement_4",
"role_clarity_1", "role_clarity_2", "role_clarity_3", "role_clarity_4"
)
measurement_data <- employees |>
select(all_of(item_names)) |>
drop_na()5. PCA, EFA, and CFA: from many items to a measurement model
Learning goals
You will learn to:
- explain the difference between PCA, EFA, and CFA;
- use PCA for dimension reduction;
- use EFA to explore plausible latent dimensions;
- use CFA to evaluate a pre-specified factor structure;
- understand why EFA finds latent dimensions, not hidden groups of people;
- use separate exploratory and confirmatory samples in a teaching workflow.
Measurement question: Twelve survey items were designed to capture leadership, engagement, and role clarity. Do the data support a simpler structure, what latent structure appears plausible, and does the proposed three-factor theory fit a separate sample?
PCA, EFA, and CFA answer different questions
PCA — reduce
Question: Can many observed variables be summarized by fewer weighted components?
- uses total variance;
- components are mathematical combinations of observed variables;
- useful for compression and exploratory summaries;
- does not posit latent common causes.
EFA — explore
Question: What latent factor structure could explain the correlations among the observed items?
- models shared/common variance;
- number and pattern of factors are explored;
- rotation helps interpretation;
- theory still matters.
CFA — test a specified structure
Question: Does a measurement model defined in advance fit the data reasonably well?
- researcher specifies which items load on which factors;
- estimates loadings and factor correlations;
- evaluates model fit and residual evidence;
- does not “prove” the theory.
Important vocabulary: EFA explores latent dimensions/factors among variables. If the question is “which employees belong to hidden groups?”, that is closer to clustering or mixture modelling, not factor analysis.
A pedagogical sequence, not a compulsory statistical pipeline
This lesson uses PCA → EFA → CFA because the progression is intuitive for beginners:
Reduce→Explore structure→Evaluate theory
In real research, PCA is not a required prerequisite for EFA. Choose the method from the question you are trying to answer.
Prepare the measurement items
For this teaching exercise we use complete item responses so the measurement concepts remain visible.
Split exploration from confirmation
Using the same data to discover a structure and then “confirm” that structure is circular.
Part A — PCA for dimension reduction
Importance of components:
PC1 PC2 PC3 PC4 PC5 PC6 PC7
Standard deviation 2.388 1.2532 1.1163 0.74414 0.70257 0.65685 0.65367
Proportion of Variance 0.475 0.1309 0.1038 0.04615 0.04113 0.03595 0.03561
Cumulative Proportion 0.475 0.6059 0.7098 0.75590 0.79704 0.83299 0.86860
PC8 PC9 PC10 PC11 PC12
Standard deviation 0.60261 0.59243 0.57230 0.52889 0.50540
Proportion of Variance 0.03026 0.02925 0.02729 0.02331 0.02129
Cumulative Proportion 0.89886 0.92811 0.95540 0.97871 1.00000
The summary reports the proportion of total standardized variance represented by each principal component.
Scree plot
plot(
pca_fit,
type = "l",
main = "PCA scree plot"
)
Component weights
round(
pca_fit$rotation[, 1:3],
2
) PC1 PC2 PC3
leadership_1 -0.32 0.24 -0.30
leadership_2 -0.29 0.25 -0.34
leadership_3 -0.29 0.21 -0.32
leadership_4 -0.31 0.28 -0.28
engagement_1 -0.29 0.13 0.37
engagement_2 -0.30 0.09 0.36
engagement_3 -0.30 0.09 0.33
engagement_4 -0.28 0.19 0.47
role_clarity_1 -0.26 -0.43 -0.12
role_clarity_2 -0.27 -0.42 -0.05
role_clarity_3 -0.27 -0.42 -0.03
role_clarity_4 -0.27 -0.38 -0.05
Large positive or negative weights indicate variables that contribute strongly to a component. The sign itself can flip without changing the substantive component.
PCA is useful when the goal is compression. It does not tell us that a latent psychological or organizational construct caused the observed responses.
Part B — EFA for latent structure
Before extracting factors, inspect whether the correlation structure is usable.
KMO(explore_data)Kaiser-Meyer-Olkin factor adequacy
Call: KMO(r = explore_data)
Overall MSA = 0.9
MSA for each item =
leadership_1 leadership_2 leadership_3 leadership_4 engagement_1
0.89 0.90 0.93 0.90 0.91
engagement_2 engagement_3 engagement_4 role_clarity_1 role_clarity_2
0.92 0.91 0.87 0.90 0.89
role_clarity_3 role_clarity_4
0.89 0.91
cortest.bartlett(
cor(explore_data),
n = nrow(explore_data)
)$chisq
[1] 1381.526
$p.value
[1] 2.913495e-245
$df
[1] 66
These are diagnostics, not automatic decision rules.
How many factors?
Parallel analysis is usually more defensible than retaining factors only because an eigenvalue exceeds one.
fa.parallel(
explore_data,
fa = "fa",
plot = FALSE
)Parallel analysis suggests that the number of factors = 3 and the number of components = NA
Estimate an oblique three-factor solution
Loadings:
MR1 MR2 MR3
leadership_1 0.875
leadership_2 0.813
leadership_3 0.726
leadership_4 0.820
engagement_1 0.700
engagement_2 0.673
engagement_3 0.637
engagement_4 0.900
role_clarity_1 0.773
role_clarity_2 0.775
role_clarity_3 0.788
role_clarity_4 0.680
MR1 MR2 MR3
SS loadings 2.639 2.319 2.171
Proportion Var 0.220 0.193 0.181
Cumulative Var 0.220 0.413 0.594
We use an oblique rotation because leadership, engagement, and role clarity can plausibly be correlated in management settings.
When reading the loadings, look for:
- several meaningful items per factor;
- strong primary loadings;
- limited cross-loadings;
- theoretically coherent factors.
Do not choose a factor solution only because it “looks cleaner”.
Part C — CFA for a pre-specified theory
Now specify the model before looking at the confirmatory results:
cfa_model <- '
Leadership =~ leadership_1 + leadership_2 + leadership_3 + leadership_4
Engagement =~ engagement_1 + engagement_2 + engagement_3 + engagement_4
RoleClarity =~ role_clarity_1 + role_clarity_2 + role_clarity_3 + role_clarity_4
'Because the items are 1–5 ordinal responses, we use an estimator suitable for ordered indicators:
lavaan 0.7-2 ended normally after 20 iterations
Estimator DWLS
Optimization method NLMINB
Number of model parameters 63
Number of observations 222
Model Test User Model:
Standard Scaled
Test Statistic 20.757 44.047
Degrees of freedom 51 51
P-value (Unknown) NA 0.744
Scaling correction factor 0.662
Shift parameter 12.703
simple second-order correction
Model Test Baseline Model:
Test statistic 5069.102 2246.313
Degrees of freedom 66 66
P-value NA 0.000
Scaling correction factor 2.295
User Model versus Baseline Model:
Comparative Fit Index (CFI) 1.000 1.000
Tucker-Lewis Index (TLI) 1.008 1.004
Robust Comparative Fit Index (CFI) 1.000
Robust Tucker-Lewis Index (TLI) 1.007
Root Mean Square Error of Approximation:
RMSEA 0.000 0.000
90 Percent confidence interval - lower 0.000 0.000
90 Percent confidence interval - upper 0.000 0.032
P-value H_0: RMSEA <= 0.050 1.000 0.997
P-value H_0: RMSEA >= 0.080 0.000 0.000
Robust RMSEA 0.000
90 Percent confidence interval - lower 0.000
90 Percent confidence interval - upper 0.046
P-value H_0: Robust RMSEA <= 0.050 0.966
P-value H_0: Robust RMSEA >= 0.080 0.000
Standardized Root Mean Square Residual:
SRMR 0.031 0.031
Goodness of Fit Index:
Goodness of Fit Index (GFI) 1.000
90 Percent confidence interval - lower 1.000
90 Percent confidence interval - upper 1.000
Robust GFI 1.000
90 Percent confidence interval - lower 0.982
90 Percent confidence interval - upper 1.000
Parameter Estimates:
Parameterization Delta
Standard errors Robust.sem
Information Expected
Information saturated (h1) model Unstructured
Latent Variables:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
Leadership =~
leadership_1 0.854 0.029 29.848 0.000 0.854 0.854
leadership_2 0.796 0.032 25.165 0.000 0.796 0.796
leadership_3 0.810 0.032 25.421 0.000 0.810 0.810
leadership_4 0.811 0.031 25.919 0.000 0.811 0.811
Engagement =~
engagement_1 0.775 0.034 22.654 0.000 0.775 0.775
engagement_2 0.793 0.034 23.375 0.000 0.793 0.793
engagement_3 0.828 0.032 25.746 0.000 0.828 0.828
engagement_4 0.804 0.034 23.585 0.000 0.804 0.804
RoleClarity =~
role_clarity_1 0.810 0.036 22.559 0.000 0.810 0.810
role_clarity_2 0.756 0.043 17.737 0.000 0.756 0.756
role_clarity_3 0.833 0.039 21.368 0.000 0.833 0.833
role_clarity_4 0.731 0.047 15.676 0.000 0.731 0.731
Covariances:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
Leadership ~~
Engagement 0.633 0.053 11.924 0.000 0.633 0.633
RoleClarity 0.447 0.067 6.648 0.000 0.447 0.447
Engagement ~~
RoleClarity 0.521 0.065 7.972 0.000 0.521 0.521
Thresholds:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
leadershp_1|t1 -1.023 0.102 -9.983 0.000 -1.023 -1.023
leadershp_1|t2 -0.382 0.087 -4.409 0.000 -0.382 -0.382
leadershp_1|t3 0.274 0.085 3.210 0.001 0.274 0.274
leadershp_1|t4 0.898 0.098 9.170 0.000 0.898 0.898
leadershp_2|t1 -0.967 0.100 -9.642 0.000 -0.967 -0.967
leadershp_2|t2 -0.298 0.086 -3.477 0.001 -0.298 -0.298
leadershp_2|t3 0.357 0.086 4.143 0.000 0.357 0.357
leadershp_2|t4 1.042 0.103 10.094 0.000 1.042 1.042
leadershp_3|t1 -0.864 0.097 -8.928 0.000 -0.864 -0.864
leadershp_3|t2 -0.216 0.085 -2.543 0.011 -0.216 -0.216
leadershp_3|t3 0.310 0.086 3.610 0.000 0.310 0.310
leadershp_3|t4 0.832 0.096 8.683 0.000 0.832 0.832
leadershp_4|t1 -0.832 0.096 -8.683 0.000 -0.832 -0.832
leadershp_4|t2 -0.251 0.085 -2.943 0.003 -0.251 -0.251
leadershp_4|t3 0.357 0.086 4.143 0.000 0.357 0.357
leadershp_4|t4 0.915 0.098 9.289 0.000 0.915 0.915
engagemnt_1|t1 -1.062 0.104 -10.203 0.000 -1.062 -1.062
engagemnt_1|t2 -0.494 0.088 -5.599 0.000 -0.494 -0.494
engagemnt_1|t3 0.136 0.085 1.607 0.108 0.136 0.136
engagemnt_1|t4 0.832 0.096 8.683 0.000 0.832 0.832
engagemnt_2|t1 -0.950 0.100 -9.525 0.000 -0.950 -0.950
engagemnt_2|t2 -0.382 0.087 -4.409 0.000 -0.382 -0.382
engagemnt_2|t3 0.310 0.086 3.610 0.000 0.310 0.310
engagemnt_2|t4 0.915 0.098 9.289 0.000 0.915 0.915
engagemnt_3|t1 -1.123 0.107 -10.519 0.000 -1.123 -1.123
engagemnt_3|t2 -0.456 0.088 -5.203 0.000 -0.456 -0.456
engagemnt_3|t3 0.113 0.085 1.339 0.181 0.113 0.113
engagemnt_3|t4 0.898 0.098 9.170 0.000 0.898 0.898
engagemnt_4|t1 -0.848 0.096 -8.806 0.000 -0.848 -0.848
engagemnt_4|t2 -0.274 0.085 -3.210 0.001 -0.274 -0.274
engagemnt_4|t3 0.298 0.086 3.477 0.001 0.298 0.298
engagemnt_4|t4 0.898 0.098 9.170 0.000 0.898 0.898
rol_clrty_1|t1 -1.102 0.106 -10.416 0.000 -1.102 -1.102
rol_clrty_1|t2 -0.382 0.087 -4.409 0.000 -0.382 -0.382
rol_clrty_1|t3 0.274 0.085 3.210 0.001 0.274 0.274
rol_clrty_1|t4 0.950 0.100 9.525 0.000 0.950 0.950
rol_clrty_2|t1 -1.082 0.105 -10.310 0.000 -1.082 -1.082
rol_clrty_2|t2 -0.251 0.085 -2.943 0.003 -0.251 -0.251
rol_clrty_2|t3 0.228 0.085 2.676 0.007 0.228 0.228
rol_clrty_2|t4 0.816 0.095 8.560 0.000 0.816 0.816
rol_clrty_3|t1 -0.986 0.101 -9.757 0.000 -0.986 -0.986
rol_clrty_3|t2 -0.274 0.085 -3.210 0.001 -0.274 -0.274
rol_clrty_3|t3 0.216 0.085 2.543 0.011 0.216 0.216
rol_clrty_3|t4 0.950 0.100 9.525 0.000 0.950 0.950
rol_clrty_4|t1 -1.167 0.109 -10.718 0.000 -1.167 -1.167
rol_clrty_4|t2 -0.431 0.087 -4.939 0.000 -0.431 -0.431
rol_clrty_4|t3 0.263 0.085 3.077 0.002 0.263 0.263
rol_clrty_4|t4 0.864 0.097 8.928 0.000 0.864 0.864
Variances:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
.leadership_1 0.270 0.270 0.270
.leadership_2 0.367 0.367 0.367
.leadership_3 0.344 0.344 0.344
.leadership_4 0.342 0.342 0.342
.engagement_1 0.399 0.399 0.399
.engagement_2 0.371 0.371 0.371
.engagement_3 0.314 0.314 0.314
.engagement_4 0.353 0.353 0.353
.role_clarity_1 0.344 0.344 0.344
.role_clarity_2 0.428 0.428 0.428
.role_clarity_3 0.305 0.305 0.305
.role_clarity_4 0.465 0.465 0.465
Leadership 1.000 1.000 1.000
Engagement 1.000 1.000 1.000
RoleClarity 1.000 1.000 1.000
Extract a compact set of fit measures:
fitMeasures(
cfa_fit,
c("cfi", "tli", "rmsea", "srmr")
) cfi tli rmsea srmr
1.000 1.008 0.000 0.031
How to read CFA without threshold worship
CFI, TLI, RMSEA, and SRMR are useful pieces of evidence. They should be interpreted alongside:
- standardized loadings;
- residuals;
- factor correlations;
- sample size and estimator;
- theory and measurement purpose.
Do not treat “CFI > .95” or another single cutoff as proof of a valid construct. Fit indices summarize particular discrepancies under particular assumptions.
PCA versus EFA: the key mathematical idea
PCA decomposes total variance into components.
Common-factor analysis represents each observed variable as a combination of:
- common variance associated with latent factors;
- unique variance specific to the item and error.
That is why PCA and EFA can look similar in output while answering different questions.
Practice
- Run PCA and inspect the first three components.
- Run EFA with two factors and with three factors. Which is more interpretable?
- Identify one item with a strong loading on each factor.
- Fit the three-factor CFA in the confirmatory sample.
- Report CFI, TLI, RMSEA, and SRMR.
- Write three sentences: what PCA tells you, what EFA adds, and what CFA adds.
Takeaway
PCA reduces. EFA explores. CFA evaluates a specified structure. None of them replaces theory, measurement quality, or careful interpretation.
References and further reading
- UCLA: Principal Components and Exploratory Factor Analysis
- Penn State STAT 505: Principal Components Analysis
- lavaan CFA tutorial
- Fabrigar, Wegener, MacCallum & Strahan (1999), Evaluating the use of exploratory factor analysis in psychological research, Psychological Methods, 4(3), 272–299. https://doi.org/10.1037/1082-989X.4.3.272