Suppose the SEM model is specified as follows:
In which A, B, C, D, E, and F are factors, with the numbers of items comprising them being 3, 3, 4, 4, 4, and 4, respectively. In addition, we propose nine research hypotheses as follows:
H1: A has a positive effect on D (A → D)
H2: B has a negative effect on D (B → D)
H3: C has a positive effect on D (C → D)
H4: A has a positive effect on E (A → E)
H5: B has a negative effect on E (B → E)
H6: C has a negative effect on E (C → E)
H7: E has a positive effect on D (E → D)
H8: D has a positive effect on F (D → F)
H9: E has a positive effect on F (E → F)
#=============================
# Stage 1: Data Simulation
#=============================
library(lavaan)
library(psych)
library(dplyr)
set.seed(20260305)
N <- 500
# ==========================================================
# 1) Population model (continuous -> later cut to Likert)
# Designed so H1-H9 significant + correct signs
# ===========================================================
pop_model <- '
# -------------------------
# Measurement (high loadings -> alpha > .75)
# -------------------------
A =~ 0.9*A1 + 0.85*A2 + 0.80*A3
B =~ 0.9*B1 + 0.85*B2 + 0.80*B3
C =~ 0.9*C1 + 0.85*C2 + 0.80*C3 + 0.75*C4
D =~ 0.9*D1 + 0.85*D2 + 0.80*D3 + 0.75*D4
E =~ 0.9*E1 + 0.85*E2 + 0.80*E3 + 0.75*E4
F =~ 0.9*F1 + 0.85*F2 + 0.80*F3 + 0.75*F4
# -------------------------
# Structural (H1-H9 in new notation)
# -------------------------
# H1-H3
D ~ 0.55*A + (-0.45)*B + 0.35*C
# H4-H7
E ~ 0.40*A + (-0.35)*B + (-0.25)*C + 0.55*D
# H8-H9
F ~ 0.40*D + 0.60*E
# Correlations among exogenous constructs
A ~~ 0.25*B
A ~~ 0.45*C
B ~~ 0.65*C
'
# 2) Simulate continuous data:
dat <- simulateData(pop_model,
sample.nobs = N,
model.type = "sem",
standardized = TRUE)
# =========================
# 3) Convert to Likert 5
# =========================
cut_to_likert <- function(x) {
cut(x,
breaks = c(-Inf, -1, -0.3, 0.3, 1, Inf),
labels = 1:5,
ordered_result = TRUE)
}
item_names <- c(paste0("A",1:3),
paste0("B",1:3),
paste0("C",1:4),
paste0("D",1:4),
paste0("E",1:4),
paste0("F",1:4))
dat %>%
mutate(across(all_of(item_names), cut_to_likert)) %>%
mutate(across(all_of(item_names), as.integer)) -> data_Likert5#=============================
# Stage 2: SEM Estimation
#=============================
library(kableExtra)
# --------------------------
# Measurement + Structural model (new notation)
# --------------------------
fit_model <- '
A =~ A1 + A2 + A3
B =~ B1 + B2 + B3
C =~ C1 + C2 + C3 + C4
D =~ D1 + D2 + D3 + D4
E =~ E1 + E2 + E3 + E4
F =~ F1 + F2 + F3 + F4
D ~ A + B + C # H1-H3
E ~ A + B + C + D # H4-H7
F ~ D + E # H8-H9
A ~~ B + C
B ~~ C
'
# SEM
fit_ord <- sem(fit_model,
data = data_Likert5,
ordered = item_names,
estimator = "WLSMV",
parameterization = "theta",
std.lv = TRUE)
# The SEM results show that hypotheses H1 through H9 are supported:
pe <- parameterEstimates(fit_ord, standardized = TRUE)
pe %>%
filter(op == "~") %>%
select(-ci.lower, -ci.upper, -std.lv, -std.all) %>%
mutate_if(is.numeric, function(x) {round(x, 3)}) %>%
kbl(caption = "Table 1: The SEM results show that hypotheses H1 through H9 are supported (confirmed)") %>%
kable_classic(full_width = TRUE, html_font = "Cambria")| lhs | op | rhs | est | se | z | pvalue |
|---|---|---|---|---|---|---|
| D | ~ | A | 0.507 | 0.074 | 6.882 | 0.000 |
| D | ~ | B | -0.473 | 0.081 | -5.869 | 0.000 |
| D | ~ | C | 0.390 | 0.093 | 4.192 | 0.000 |
| E | ~ | A | 0.477 | 0.090 | 5.272 | 0.000 |
| E | ~ | B | -0.301 | 0.089 | -3.368 | 0.001 |
| E | ~ | C | -0.297 | 0.106 | -2.814 | 0.005 |
| E | ~ | D | 0.547 | 0.076 | 7.236 | 0.000 |
| F | ~ | D | 0.328 | 0.081 | 4.044 | 0.000 |
| F | ~ | E | 0.678 | 0.083 | 8.200 | 0.000 |
# Fit model indicators:
fitMeasures(fit_ord,
fit.measures = c("cfi", "tli", "rmsea", "gfi", "chisq", "df", "fmin", "ifi"))## cfi tli rmsea gfi chisq df fmin ifi
## 1.000 1.004 0.000 0.997 94.542 197.000 0.095 1.004
#-------------------------------
# Cronbach alpha section
#-------------------------------
cronbach_alpha <- function(var_prefix) {
data_Likert5 %>%
select(starts_with(var_prefix)) %>%
psych::alpha() -> alpha_factor
alpha_factor$total$raw_alpha
}
# Cronbach Alpha for A factor:
cronbach_alpha("A") # Factor A (A1-A3)## [1] 0.8626427