Hierarchical Categorical Confirmation Factor Analysis - Private-Projects237/Statistics GitHub Wiki

Overview

This wikipage will go through an example of a hierarchical categorical confirmation factor analysis (CFA).

Step 1: Generating the data

# load in packages
library(MASS)     
library(lavaan)   

# Set seed
set.seed(321)   

# Inputs (set the sample sizes and the factor loadings/betas)
N <- 500 
beta <- c(0.80, 0.70, 0.60) 
lambda_mat <- list( 
  F1 = c(1.00, 0.90, 0.80, 1.10, 1.00),
  F2 = c(1.00, 1.10, 0.90, 1.00, 1.20),
  F3 = c(1.00, 0.90, 1.20, 1.00, 1.00)
)

# Create a custom function to transform numeric values into categorical responses
thresholds <- c(-0.5, 0.5) # two cut-points → 3 categories (0,1,2)

make_ordinal <- function(z, thr = thresholds, labels = 0:2) {
  cut(z, breaks = c(-Inf, thr, Inf), labels = labels, ordered_result = TRUE)
}

# Create a distribution for the hierarchical factor
G <- rnorm(N, 0, 1)

# Create the distributions for the first order factors
delta_sd <- sqrt(1 - beta^2)
F1 <- beta[1] * G + rnorm(N, 0, delta_sd[1])
F2 <- beta[2] * G + rnorm(N, 0, delta_sd[2])
F3 <- beta[3] * G + rnorm(N, 0, delta_sd[3])

# Genrating the responses for each items
gen_items <- function(theta, lambdas) {
  mapply(function(lam) make_ordinal(lam * theta + rnorm(N)),
         lam = lambdas,
         SIMPLIFY = FALSE)
}

items <- data.frame(
  # F1 items
  gen_items(F1, lambda_mat$F1),
  # F2 items
  gen_items(F2, lambda_mat$F2),
  # F3 items
  gen_items(F3, lambda_mat$F3)
)

# Give the columns sensible names
names(items) <- paste0("item", 1:ncol(items))

Step 2: Specifying the model

# Set the hierarchical model
hier_model <- '
  # first-order
  F1 =~ 1*item1 + item2 + item3 + item4 + item5
  F2 =~ 1*item6 + item7 + item8 + item9 + item10
  F3 =~ 1*item11 + item12 + item13 + item14 + item15

  # second-order
  G  =~ NA*F1 + F2 + F3
  
  # fix variance of the second order factor
  G ~~ 1*G
'

Step 3: Making Sense of the Model Output

summary(fit_hier, standardized = TRUE, fit.measures = TRUE)
parameterEstimates(fit_hier)
standardizedSolution(fit_hier)
inspect(fit_hier, "r2")

Step 4: Visualizing the Path Diagram

# Plot the path diagram
library(semPlot)
semPaths(fit_hier,
         what = "std",                     # Standardized estimates
         whatLabels = "std",               # Show estimated values
         layout = "tree",                  # Hierarchical layout
         edge.label.cex = .9,             # Edge label size
         node.label.cex = 1,             # Node label size
         sizeMan = 7,                      # Size of item rectangles
         sizeMan2 = 4,
         sizeLat = 6,                     # Size of factor ovals
         edge.color = "black",             # Black edges for clarity
         nodeLabels = c("Item 1", "Item 2", "Item 3", "Item 4", "Item 5",
                        "Item 6", "Item 7", "Item 8", "Item 9", "Item 10",
                        "Item 11", "Item 12", "Item 13", "Item 14", "Item 15",
                        "Factor 1", "Factor 2", "Factor 3", "G"), # Custom labels
         residuals = TRUE,                 # Show residual variances
         exoCov = TRUE,                    # Show factor correlations
         intercepts = FALSE,               # Exclude intercepts
         title = FALSE,                    # Custom title added later
         curve = 1,                      # Curved arrows for correlations
         rotation = 2,                     # Vertical orientation for items
         normalize = FALSE,            # mitigates overlap between indicators
         weighted = FALSE,             # gets rid of dumb thickness in the lines
         mar = c(3,9,3,10), # D L U R
)  
The Path Diagram of the hierarchical categorical CFA (Standardized)
Screenshot 2025-04-30 at 5 55 40 AM