# ============================================================
# FFCWS: Admixture Regression Analysis
# African Ancestry Sample (Both-Black + BW Parents)
# City-Weighted, HC1 Robust SEs
#
# Updates:
# - WAIS: conservative filter (drop only confirmed FB/Spanish)
# - Average WAIS = inclusive average of valid parent scores
# - Child TVIP at age 3 exclusion
# - 3-test g (PPVT, WJ9, WJ10); DS in LSEM/CFA only
# - Individual parent WAIS: parent self-ID, full APA sample
# - Average WAIS: couple SIRE, excl BW
# - 3-component SES (education, poverty, income)
# - LSEM + multi-group CFA measurement invariance
# ============================================================
library(haven)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(sandwich)
library(lmtest)
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(mice)
##
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
##
## filter
## The following objects are masked from 'package:base':
##
## cbind, rbind
library(sirt)
## Warning: package 'sirt' was built under R version 4.5.3
## - sirt 4.2-133 (2025-09-27 12:57:51)
library(lavaan)
## This is lavaan 0.6-19
## lavaan is FREE software! Please report any bugs.
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.5.2
library(mediation)
## Warning: package 'mediation' was built under R version 4.5.2
## Loading required package: MASS
##
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
##
## select
## Loading required package: Matrix
## Loading required package: mvtnorm
## mediation: Causal Mediation Analysis
## Version: 4.5.1
# ==============================================================
# 1. Load Data
# ==============================================================
d <- read_sav("C:/Users/mh198/Documents/Data/FFCWS/DS0001/31622-0001-Data.sav")
PGS <- read_sav("C:/Users/mh198/Documents/Data/FFCWS//DS0007/31622-0007-Data.sav")
d <- merge(d, PGS[, c("IDNUM", "K5ADMIXA", "K5PC1A", "K5PC2A",
"K5PC3A", "K5PC4A", "K5PC5A")],
by = "IDNUM", all.x = TRUE)
# ==============================================================
# 2. African Ancestry PGS Sample
# ==============================================================
d <- d[d$K5ADMIXA %in% c(0, 1), ]
# ==============================================================
# 3. Black Parent Identification
# ==============================================================
d$father_black_nh <- d$CF1ETHRACE == 2
d$mother_black_nh <- d$CM1ETHRACE == 2
d$father_black_hisp <- (d$CF1ETHRACE == 3) & (d$F1H3 == 2)
d$mother_black_hisp <- (d$CM1ETHRACE == 3) & (d$M1H3 == 2)
d$father_black_hisp[is.na(d$father_black_hisp)] <- FALSE
d$mother_black_hisp[is.na(d$mother_black_hisp)] <- FALSE
d$father_any_black <- d$father_black_nh | d$father_black_hisp
d$mother_any_black <- d$mother_black_nh | d$mother_black_hisp
d$any_black_parent <- d$father_any_black | d$mother_any_black
# ==============================================================
# 4. Continental African Exclusion
# ==============================================================
d$mother_african_born <- (d$M1H2 == 2) & (d$M1H2B == 1)
d$father_african_born <- (d$F1H2 == 2) & (d$F1H2B == 1)
d$mother_african_born[is.na(d$mother_african_born)] <- FALSE
d$father_african_born[is.na(d$father_african_born)] <- FALSE
d$gp_african <- (d$F3H1A == 101) | (d$F3H1B == 101) |
(d$M3H1A == 101) | (d$M3H1B == 101)
d$gp_african[is.na(d$gp_african)] <- FALSE
d$african_origin <- d$mother_african_born | d$father_african_born | d$gp_african
# ==============================================================
# 5. Generation & SIRE Dummies
# ==============================================================
d$parent_fb <- (d$M1H2 == 2) | (d$F1H2 == 2)
d$parent_fb[is.na(d$parent_fb)] <- FALSE
d$gp_fb <- (d$F3H1A >= 101) | (d$F3H1B >= 101) |
(d$M3H1A >= 101) | (d$M3H1B >= 101)
d$gp_fb[is.na(d$gp_fb)] <- FALSE
d$gen_2nd <- d$parent_fb & !d$african_origin
d$gen_3rd <- (!d$parent_fb) & d$gp_fb & !d$african_origin
d$sire <- NA_character_
d$sire[d$father_any_black & d$mother_any_black] <- "BB"
d$sire[(d$father_any_black & d$CM1ETHRACE == 1) |
(d$CF1ETHRACE == 1 & d$mother_any_black)] <- "BW"
d$sire[(d$father_any_black & d$CM1ETHRACE == 3 & !d$mother_black_hisp) |
(d$CF1ETHRACE == 3 & !d$father_black_hisp & d$mother_any_black)] <- "BH"
d$sire[d$any_black_parent & is.na(d$sire)] <- "BO"
d$sire[!d$any_black_parent] <- "None"
d$sire <- factor(d$sire, levels = c("BB", "BW", "BH", "BO", "None"))
# ==============================================================
# 6. Child TVIP Exclusion Flag
# ==============================================================
d$ch_tvip3_score <- ifelse(d$CH3TVIPSTD >= 0, d$CH3TVIPSTD, NA)
d$took_tvip3 <- as.numeric(!is.na(d$ch_tvip3_score))
cat("Child TVIP at age 3:", sum(d$took_tvip3), "cases flagged for exclusion\n")
## Child TVIP at age 3: 1 cases flagged for exclusion
# ==============================================================
# 7. Demographics & SES (3-component)
# ==============================================================
d$sex <- ifelse(d$CM1BSEX %in% c(1, 2), d$CM1BSEX - 1, NA)
d$age <- ifelse(d$CH5AGEM >= 0, d$CH5AGEM, NA)
d$single <- ifelse(d$CM1MARF == 0 & d$CM1COHF == 0, 1,
ifelse(d$CM1MARF %in% c(0, 1) & d$CM1COHF %in% c(0, 1), 0, NA))
d$M_edu <- NA
for (col in c("CM5EDU", "CM4EDU", "CM3EDU", "CM2EDU", "CM1EDU")) {
d$M_edu <- ifelse(is.na(d$M_edu) & d[[col]] >= 1, d[[col]], d$M_edu)
}
d$F_edu <- NA
for (col in c("CF5EDU", "CF4EDU", "CF3EDU", "CF2EDU", "CF1EDU")) {
d$F_edu <- ifelse(is.na(d$F_edu) & d[[col]] >= 1, d[[col]], d$F_edu)
}
d$mid_edu <- rowMeans(cbind(d$F_edu, d$M_edu), na.rm = TRUE)
d$mid_edu[is.na(d$F_edu) & is.na(d$M_edu)] <- NA
d$pov_bl <- ifelse(d$CM1INPOV >= 0, d$CM1INPOV, NA)
d$pov_yr1 <- ifelse(d$CM2POVCO >= 0, d$CM2POVCO, NA)
d$pov_yr3 <- ifelse(d$CM3POVCO >= 0, d$CM3POVCO, NA)
d$pov_yr5 <- ifelse(d$CM4POVCO >= 0, d$CM4POVCO, NA)
d$pov_yr9 <- ifelse(d$CM5POVCO >= 0, d$CM5POVCO, NA)
d$ave_pov <- rowMeans(cbind(d$pov_bl, d$pov_yr1, d$pov_yr3, d$pov_yr5, d$pov_yr9), na.rm = TRUE)
d$ave_pov[is.na(d$pov_bl) & is.na(d$pov_yr1) & is.na(d$pov_yr3) & is.na(d$pov_yr5) & is.na(d$pov_yr9)] <- NA
inc_vars <- c("CM1HHINC", "CM2HHINC", "CM3HHINC", "CM4HHINC", "CM5HHINC")
inc_names <- c("inc_bl", "inc_yr1", "inc_yr3", "inc_yr5", "inc_yr9")
for (i in seq_along(inc_vars)) {
d[[inc_names[i]]] <- ifelse(d[[inc_vars[i]]] >= 0, d[[inc_vars[i]]], NA)
d[[paste0("log_", inc_names[i])]] <- log(pmax(d[[inc_names[i]]], 1))
}
d$ave_log_inc <- rowMeans(d[, paste0("log_", inc_names)], na.rm = TRUE)
d$ave_log_inc[rowSums(!is.na(d[, inc_names])) == 0] <- NA
ses_indicators <- data.frame(
mid_edu = as.vector(scale(d$mid_edu)),
ave_pov = as.vector(scale(d$ave_pov)),
ave_log_inc = as.vector(scale(d$ave_log_inc))
)
n_ses_valid <- rowSums(!is.na(ses_indicators))
set.seed(54321)
ses_imp <- mice(ses_indicators, m = 5, method = "pmm", maxit = 10, printFlag = FALSE)
ses_imputed <- complete(ses_imp, action = 1)
d$SES_raw <- rowMeans(ses_imputed, na.rm = FALSE)
d$SES_raw[n_ses_valid == 0] <- NA
d$wt <- ifelse(d$K5CITYWT > 0, d$K5CITYWT, NA)
# ==============================================================
# 8. Child Cognitive Variables: g
# ==============================================================
d$PPVT <- ifelse(d$CH5PPVTSS >= 0, d$CH5PPVTSS, NA)
d$WJ9 <- ifelse(d$CH5WJ9SS >= 0, d$CH5WJ9SS, NA)
d$WJ10 <- ifelse(d$CH5WJ10SS >= 0, d$CH5WJ10SS, NA)
d$DS <- ifelse(d$CH5DSSS >= 0, d$CH5DSSS, NA)
resid_fun <- function(y, data) {
fit <- lm(y ~ age + I(age^2) + sex, data = data, na.action = na.exclude)
as.vector(residuals(fit))
}
d$PPVT_r <- resid_fun(d$PPVT, d)
d$WJ9_r <- resid_fun(d$WJ9, d)
d$WJ10_r <- resid_fun(d$WJ10, d)
d$DS_r <- resid_fun(d$DS, d)
d$PPVT_z <- as.vector(scale(d$PPVT_r))
d$WJ9_z <- as.vector(scale(d$WJ9_r))
d$WJ10_z <- as.vector(scale(d$WJ10_r))
d$DS_z <- as.vector(scale(d$DS_r))
# 3-test g (DS kept for LSEM/CFA only)
cog_imp_data <- data.frame(
PPVT_z = d$PPVT_z,
WJ9_z = d$WJ9_z,
WJ10_z = d$WJ10_z
)
has_any <- rowSums(!is.na(cog_imp_data)) >= 1
set.seed(12345)
imp <- mice(cog_imp_data, m = 5, method = "norm", maxit = 10, printFlag = FALSE)
cog_imputed <- complete(imp, action = 1)
cog_for_pca <- as.matrix(cog_imputed[has_any, ])
pca_out <- prcomp(cog_for_pca, center = FALSE, scale. = FALSE)
d$g_raw <- NA
d$g_raw[has_any] <- pca_out$x[, 1]
cat("PCA loadings (3-test g):", round(pca_out$rotation[, 1], 3), "\n")
## PCA loadings (3-test g): 0.564 0.588 0.58
cat("Variance explained:", round(summary(pca_out)$importance[2, 1] * 100, 1), "%\n")
## Variance explained: 71.7 %
# ==============================================================
# 9. Parental WAIS: Conservative USB + English Filter
# ==============================================================
d$mother_wais_raw <- ifelse(d$CM3COGSC >= 0 & d$CM3COGSC <= 15, d$CM3COGSC, NA)
d$father_wais_raw <- ifelse(d$CF3COGSC >= 0 & d$CF3COGSC <= 15, d$CF3COGSC, NA)
d$mother_confirmed_fb <- as.numeric(d$M1H2B %in% c(1, 2, 3, 4, 5))
d$father_confirmed_fb <- as.numeric(d$F1H2B %in% c(1, 2, 3, 4, 5))
d$mother_confirmed_span <- as.numeric(d$CM3SPAN == 1)
d$father_confirmed_span <- as.numeric(d$CF3SPAN == 1)
d$mother_wais <- ifelse(d$mother_confirmed_fb == 1 | d$mother_confirmed_span == 1,
NA, d$mother_wais_raw)
d$father_wais <- ifelse(d$father_confirmed_fb == 1 | d$father_confirmed_span == 1,
NA, d$father_wais_raw)
cat("\nMother WAIS: total =", sum(!is.na(d$mother_wais_raw)),
", after filter =", sum(!is.na(d$mother_wais)),
", dropped =", sum(!is.na(d$mother_wais_raw)) - sum(!is.na(d$mother_wais)), "\n")
##
## Mother WAIS: total = 1536 , after filter = 1480 , dropped = 56
cat("Father WAIS: total =", sum(!is.na(d$father_wais_raw)),
", after filter =", sum(!is.na(d$father_wais)),
", dropped =", sum(!is.na(d$father_wais_raw)) - sum(!is.na(d$father_wais)), "\n")
## Father WAIS: total = 1276 , after filter = 1231 , dropped = 45
# Age-residualize
d$mother_age <- ifelse(d$CM3AGE > 0, d$CM3AGE, NA)
d$father_age <- ifelse(d$CF3AGE > 0, d$CF3AGE, NA)
m_mask <- !is.na(d$mother_wais) & !is.na(d$mother_age)
if (sum(m_mask) > 10) {
d$mother_age_c <- d$mother_age - mean(d$mother_age[m_mask])
d$mother_age_c2 <- d$mother_age_c^2
d$M_WAIS_res <- NA_real_
d$M_WAIS_res[m_mask] <- residuals(lm(mother_wais ~ mother_age_c + mother_age_c2,
data = d[m_mask, ]))
}
f_mask <- !is.na(d$father_wais) & !is.na(d$father_age)
if (sum(f_mask) > 10) {
d$father_age_c <- d$father_age - mean(d$father_age[f_mask])
d$father_age_c2 <- d$father_age_c^2
d$F_WAIS_res <- NA_real_
d$F_WAIS_res[f_mask] <- residuals(lm(father_wais ~ father_age_c + father_age_c2,
data = d[f_mask, ]))
}
d$ave_WAIS_raw <- rowMeans(cbind(d$M_WAIS_res, d$F_WAIS_res), na.rm = TRUE)
d$ave_WAIS_raw[is.na(d$M_WAIS_res) & is.na(d$F_WAIS_res)] <- NA
cat("Average WAIS (filtered): N =", sum(!is.na(d$ave_WAIS_raw)), "\n")
## Average WAIS (filtered): N = 1533
# ==============================================================
# 10. Build Subsets (after TVIP exclusion)
# ==============================================================
d <- d[d$took_tvip3 == 0, ]
cat("After TVIP exclusion: N =", nrow(d), "\n")
## After TVIP exclusion: N = 1639
d$PC1A_z <- as.vector(scale(d$K5PC1A))
d_apa <- d[d$any_black_parent & !d$african_origin, ]
d_apa$sire <- droplevels(d_apa$sire)
# Parent self-ID flags for individual WAIS models
d_apa$mother_black_sire <- as.numeric(d_apa$CM1ETHRACE == 2)
d_apa$father_black_sire <- as.numeric(d_apa$CF1ETHRACE == 2)
d_apa_noBW <- d_apa[d_apa$sire != "BW", ]
d_apa_noBW$sire <- droplevels(d_apa_noBW$sire)
d_haa <- d[d$CF1ETHRACE == 2 & d$CM1ETHRACE == 2 &
!d$african_origin & !d$parent_fb & !d$gp_fb, ]
cat("APA:", nrow(d_apa), " | APA excl BW:", nrow(d_apa_noBW), " | HAA:", nrow(d_haa), "\n")
## APA: 1588 | APA excl BW: 1516 | HAA: 1323
# ==============================================================
# 11. LSEM Analysis — Liu et al. (2025) approach
# Progressive invariance testing with bootstrap
# ==============================================================
# DS included for model identification (4 indicators, 2 df)
# Run on post-TVIP APA sample to match regression analyses
d_apa_cog <- d_apa[rowSums(!is.na(d_apa[, c("PPVT_z", "WJ9_z", "WJ10_z", "DS_z")])) >= 1, ]
grid_points <- quantile(d_apa_cog$PC1A_z,
probs = c(0.20, 0.35, 0.50, 0.65, 0.80),
na.rm = TRUE)
lsem_model <- 'g =~ PPVT_z + WJ9_z + WJ10_z + DS_z'
# ── Define invariance constraints ────────────────────────────────────────
# Metric (weak) — loadings invariant
weak_constraints <- c("g=~PPVT_z", "g=~WJ9_z", "g=~WJ10_z", "g=~DS_z")
# Scalar (strong) — loadings + intercepts invariant
strong_constraints <- c(weak_constraints,
"PPVT_z~1", "WJ9_z~1", "WJ10_z~1", "DS_z~1")
# Strict — loadings + intercepts + residual variances
strict_constraints <- c(strong_constraints,
"PPVT_z~~PPVT_z", "WJ9_z~~WJ9_z",
"WJ10_z~~WJ10_z", "DS_z~~DS_z")
# ── Configural ───────────────────────────────────────────────────────────
parallel::detectCores()
## [1] 32
lsem_config <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = 1.5,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
est_joint = TRUE
)
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 415
## Group 2 673
## Group 3 782
## Group 4 793
## Group 5 707
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.0738287418959242
## quasi-Newton steps using NLMINB:
## objective function = 0.0738287418959242
## objective function = 0.0598101428080710
## objective function = 0.0272600001103062
## objective function = 0.0212466671924765
## objective function = 0.0164817105615486
## objective function = 0.0146555981910506
## objective function = 0.0134640527587602
## objective function = 0.0120450778257396
## objective function = 0.0111114082610116
## objective function = 0.0103842179662563
## objective function = 0.0099169893262600
## objective function = 0.0095414554179040
## objective function = 0.0092043224558610
## objective function = 0.0088560381300777
## objective function = 0.0086710435845395
## objective function = 0.0085470757202159
## objective function = 0.0084747339400978
## objective function = 0.0084164424263215
## objective function = 0.0083712735479592
## objective function = 0.0083354584727222
## objective function = 0.0083055661345523
## objective function = 0.0082787958134606
## objective function = 0.0082528837858022
## objective function = 0.0082279510463489
## objective function = 0.0082056437113315
## objective function = 0.0081888682705170
## objective function = 0.0081774151733347
## objective function = 0.0081670357714019
## objective function = 0.0081578120412134
## objective function = 0.0081502376072646
## objective function = 0.0081457014215777
## objective function = 0.0081429285617339
## objective function = 0.0081405099179554
## objective function = 0.0081382019354723
## objective function = 0.0081356263182086
## objective function = 0.0081341831132384
## objective function = 0.0081330758252586
## objective function = 0.0081323577340175
## objective function = 0.0081317153966488
## objective function = 0.0081311599940430
## objective function = 0.0081305173603691
## objective function = 0.0081300265687728
## objective function = 0.0081295696584909
## objective function = 0.0081292733296530
## objective function = 0.0081290723806016
## objective function = 0.0081289199258578
## objective function = 0.0081288115527348
## objective function = 0.0081287394835312
## objective function = 0.0081286894269027
## objective function = 0.0081286501022387
## objective function = 0.0081286206606698
## objective function = 0.0081286017375926
## objective function = 0.0081285895849612
## objective function = 0.0081285794627587
## objective function = 0.0081285704435491
## objective function = 0.0081285603182290
## objective function = 0.0081285549490478
## objective function = 0.0081285517222526
## objective function = 0.0081285502788854
## objective function = 0.0081285491366399
## objective function = 0.0081285482035613
## objective function = 0.0081285473129795
## objective function = 0.0081285467962137
## objective function = 0.0081285464321888
## objective function = 0.0081285462418201
## objective function = 0.0081285461180620
## objective function = 0.0081285460319544
## objective function = 0.0081285459611770
## objective function = 0.0081285459050836
## objective function = 0.0081285458698432
## objective function = 0.0081285458488869
## objective function = 0.0081285458365467
## objective function = 0.0081285458285124
## objective function = 0.0081285458235066
## objective function = 0.0081285458201861
## objective function = 0.0081285458177103
## objective function = 0.0081285458159828
## objective function = 0.0081285458151327
## objective function = 0.0081285458151327
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 77
## number of function evaluations [objective, gradient]: 78 78
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
summary(lsem_config)
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = 1.5,
## standardized = TRUE, est_joint = TRUE, estimator = "ML",
## meanstructure = TRUE)
##
## Date of Analysis: 2026-06-06 23:46:19.630104
## Time difference of 4.896395 secs
## Computation Time: 4.896395
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 1.5
## Bandwidth = 0.294
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.082
## 2 cfi 0.989
## 3 tli 0.967
## 4 gfi 0.992
## 5 srmr 0.020
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int lin_slo
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 2 g=~WJ9_z 2 1.312 0.086 0.078 1.210 1.435 1.410 -0.311
## 3 g=~WJ10_z 3 1.256 0.057 0.044 1.206 1.401 1.322 -0.211
## 4 g=~DS_z 4 0.875 0.031 0.023 0.856 0.958 0.908 -0.105
## 5 PPVT_z~~PPVT_z 5 0.568 0.087 0.075 0.475 0.736 0.671 -0.326
## 6 WJ9_z~~WJ9_z 6 0.322 0.023 0.014 0.301 0.386 0.347 -0.081
## 7 WJ10_z~~WJ10_z 7 0.403 0.021 0.018 0.354 0.421 0.399 0.012
## 8 DS_z~~DS_z 8 0.718 0.023 0.019 0.668 0.741 0.692 0.081
## 9 g~~g 9 0.398 0.036 0.030 0.326 0.435 0.356 0.134
## 10 PPVT_z~1 10 -0.001 0.004 0.003 -0.008 0.003 -0.001 -0.001
## 11 WJ9_z~1 11 0.001 0.003 0.002 -0.002 0.008 0.004 -0.007
## 12 WJ10_z~1 12 -0.002 0.005 0.004 -0.007 0.004 0.001 -0.009
## 13 DS_z~1 13 -0.001 0.002 0.001 -0.004 0.001 -0.001 0.000
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.643 0.045 0.039 0.554 0.691 0.589 0.171
## 16 std__g=~WJ9_z 16 0.824 0.010 0.005 0.797 0.832 0.819 0.017
## 17 std__g=~WJ10_z 17 0.780 0.011 0.009 0.769 0.802 0.780 -0.001
## 18 std__g=~DS_z 18 0.545 0.006 0.005 0.538 0.556 0.545 0.000
## 19 std__PPVT_z~~PPVT_z 19 0.585 0.057 0.050 0.522 0.693 0.653 -0.215
## 20 std__WJ9_z~~WJ9_z 20 0.321 0.016 0.009 0.308 0.366 0.329 -0.027
## 21 std__WJ10_z~~WJ10_z 21 0.392 0.017 0.014 0.357 0.409 0.391 0.002
## 22 std__DS_z~~DS_z 22 0.703 0.006 0.006 0.691 0.710 0.703 0.000
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 24 std__PPVT_z~1 24 -0.001 0.003 0.003 -0.008 0.003 -0.001 -0.001
## 25 std__WJ9_z~1 25 0.001 0.003 0.002 -0.002 0.008 0.004 -0.007
## 26 std__WJ10_z~1 26 -0.002 0.005 0.004 -0.007 0.004 0.001 -0.008
## 27 std__DS_z~1 27 -0.001 0.002 0.001 -0.004 0.001 -0.001 0.000
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## SD_nonlin
## 1 0.000
## 2 0.025
## 3 0.015
## 4 0.013
## 5 0.013
## 6 0.010
## 7 0.021
## 8 0.009
## 9 0.004
## 10 0.004
## 11 0.003
## 12 0.004
## 13 0.002
## 14 0.000
## 15 0.006
## 16 0.009
## 17 0.011
## 18 0.006
## 19 0.008
## 20 0.014
## 21 0.017
## 22 0.006
## 23 0.000
## 24 0.003
## 25 0.002
## 26 0.004
## 27 0.002
## 28 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 419.601
## 2 0.070 0.167 679.165
## 3 0.264 0.233 789.229
## 4 0.453 0.259 800.087
## 5 0.619 0.241 713.065
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 680.229 154.315 419.601 800.087
b_config <- lsem.bootstrap(lsem_config, R = 50, n.core = 14)
## Compute LSEM for bootstrap samples:
## Warning in sqrt(w): NaNs produced
# ── Metric (weak) ────────────────────────────────────────────────────────
lsem_weak <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = 1.5,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
par_invariant = weak_constraints
)
## Use joint estimation
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 415
## Group 2 673
## Group 3 782
## Group 4 793
## Group 5 707
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.0738287418959240
## quasi-Newton steps using NLMINB:
## objective function = 0.0738287418959240
## objective function = 0.0599013208206976
## objective function = 0.0269691804103185
## objective function = 0.0212689827215762
## objective function = 0.0167864214592230
## objective function = 0.0146621571547316
## objective function = 0.0135316330651613
## objective function = 0.0121010533431593
## objective function = 0.0112181943917189
## objective function = 0.0105390063987082
## objective function = 0.0101586882120909
## objective function = 0.0098461889534032
## objective function = 0.0095837421883159
## objective function = 0.0093705181903796
## objective function = 0.0092380702345748
## objective function = 0.0091564956161002
## objective function = 0.0091164433604852
## objective function = 0.0090997662457236
## objective function = 0.0090926561198851
## objective function = 0.0090893382565386
## objective function = 0.0090875587474185
## objective function = 0.0090865396129202
## objective function = 0.0090859427304812
## objective function = 0.0090855765766150
## objective function = 0.0090853643524534
## objective function = 0.0090852640158022
## objective function = 0.0090852234829650
## objective function = 0.0090852072199611
## objective function = 0.0090852008363753
## objective function = 0.0090851982579872
## objective function = 0.0090851967559427
## objective function = 0.0090851956246522
## objective function = 0.0090851949722973
## objective function = 0.0090851947207863
## objective function = 0.0090851946320781
## objective function = 0.0090851945868091
## objective function = 0.0090851945615698
## objective function = 0.0090851945501115
## objective function = 0.0090851945449507
## objective function = 0.0090851945421712
## objective function = 0.0090851945406734
## objective function = 0.0090851945400383
## objective function = 0.0090851945400383
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 41
## number of function evaluations [objective, gradient]: 42 42
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
summary(lsem_weak)
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = 1.5,
## standardized = TRUE, par_invariant = weak_constraints, estimator = "ML",
## meanstructure = TRUE)
##
## Date of Analysis: 2026-06-06 23:59:32.040555
## Time difference of 5.47321 secs
## Computation Time: 5.47321
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 1.5
## Bandwidth = 0.294
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.051
## 2 cfi 0.990
## 3 tli 0.987
## 4 gfi 0.991
## 5 srmr 0.024
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int lin_slo
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 2 g=~WJ9_z 2 1.309 0.000 0.000 1.309 1.309 1.309 0.000
## 3 g=~WJ10_z 3 1.253 0.000 0.000 1.253 1.253 1.253 0.000
## 4 g=~DS_z 4 0.872 0.000 0.000 0.872 0.872 0.872 0.000
## 5 PPVT_z~~PPVT_z 5 0.569 0.079 0.068 0.486 0.723 0.663 -0.298
## 6 WJ9_z~~WJ9_z 6 0.322 0.032 0.028 0.283 0.383 0.360 -0.119
## 7 WJ10_z~~WJ10_z 7 0.403 0.014 0.011 0.367 0.415 0.395 0.023
## 8 DS_z~~DS_z 8 0.718 0.024 0.020 0.671 0.742 0.691 0.086
## 9 g~~g 9 0.397 0.004 0.003 0.388 0.400 0.395 0.007
## 10 PPVT_z~1 10 -0.001 0.004 0.003 -0.008 0.003 -0.001 -0.001
## 11 WJ9_z~1 11 0.001 0.003 0.002 -0.002 0.008 0.004 -0.007
## 12 WJ10_z~1 12 -0.002 0.005 0.004 -0.007 0.004 0.001 -0.009
## 13 DS_z~1 13 -0.001 0.002 0.001 -0.004 0.001 -0.001 0.000
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.643 0.026 0.023 0.591 0.670 0.612 0.099
## 16 std__g=~WJ9_z 16 0.824 0.014 0.012 0.796 0.840 0.808 0.051
## 17 std__g=~WJ10_z 17 0.780 0.004 0.003 0.776 0.790 0.782 -0.006
## 18 std__g=~DS_z 18 0.544 0.006 0.005 0.539 0.553 0.551 -0.020
## 19 std__PPVT_z~~PPVT_z 19 0.586 0.033 0.029 0.551 0.651 0.626 -0.126
## 20 std__WJ9_z~~WJ9_z 20 0.321 0.022 0.019 0.294 0.366 0.347 -0.084
## 21 std__WJ10_z~~WJ10_z 21 0.392 0.006 0.005 0.376 0.397 0.389 0.010
## 22 std__DS_z~~DS_z 22 0.704 0.006 0.006 0.694 0.709 0.697 0.022
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 24 std__PPVT_z~1 24 -0.001 0.003 0.003 -0.008 0.003 -0.001 -0.001
## 25 std__WJ9_z~1 25 0.001 0.003 0.002 -0.002 0.008 0.004 -0.007
## 26 std__WJ10_z~1 26 -0.002 0.005 0.004 -0.007 0.004 0.001 -0.008
## 27 std__DS_z~1 27 -0.001 0.002 0.001 -0.004 0.001 -0.001 0.000
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## SD_nonlin
## 1 0.000
## 2 0.000
## 3 0.000
## 4 0.000
## 5 0.012
## 6 0.004
## 7 0.013
## 8 0.009
## 9 0.003
## 10 0.004
## 11 0.003
## 12 0.004
## 13 0.002
## 14 0.000
## 15 0.004
## 16 0.001
## 17 0.004
## 18 0.002
## 19 0.005
## 20 0.002
## 21 0.006
## 22 0.002
## 23 0.000
## 24 0.003
## 25 0.003
## 26 0.004
## 27 0.002
## 28 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 419.601
## 2 0.070 0.167 679.165
## 3 0.264 0.233 789.229
## 4 0.453 0.259 800.087
## 5 0.619 0.241 713.065
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 680.229 154.315 419.601 800.087
b_weak <- lsem.bootstrap(lsem_weak, R = 50, n.core = 14)
## Compute LSEM for bootstrap samples:
## Warning in sqrt(w): NaNs produced
# ── Scalar (strong) ──────────────────────────────────────────────────────
lsem_strong <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = 1.5,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
par_invariant = strong_constraints
)
## Use joint estimation
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 415
## Group 2 673
## Group 3 782
## Group 4 793
## Group 5 707
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.0738287418959241
## quasi-Newton steps using NLMINB:
## objective function = 0.0738287418959241
## objective function = 0.0576625777807168
## objective function = 0.0278663165629487
## objective function = 0.0219230937999248
## objective function = 0.0157915131715109
## objective function = 0.0145048683825754
## objective function = 0.0133541406980544
## objective function = 0.0116893750308125
## objective function = 0.0106173013333035
## objective function = 0.0097660150077022
## objective function = 0.0095128522174340
## objective function = 0.0093379163130080
## objective function = 0.0092289992042474
## objective function = 0.0091717272777997
## objective function = 0.0091427236539329
## objective function = 0.0091250148332232
## objective function = 0.0091141639064793
## objective function = 0.0091090729643519
## objective function = 0.0091069585815648
## objective function = 0.0091059798140988
## objective function = 0.0091055145710012
## objective function = 0.0091053252494740
## objective function = 0.0091052503567130
## objective function = 0.0091052125224359
## objective function = 0.0091051920715713
## objective function = 0.0091051830298506
## objective function = 0.0091051800968047
## objective function = 0.0091051793733580
## objective function = 0.0091051791858574
## objective function = 0.0091051791149114
## objective function = 0.0091051790858061
## objective function = 0.0091051790756123
## objective function = 0.0091051790713540
## objective function = 0.0091051790691325
## objective function = 0.0091051790681833
## objective function = 0.0091051790681833
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 34
## number of function evaluations [objective, gradient]: 35 35
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
summary(lsem_strong)
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = 1.5,
## standardized = TRUE, par_invariant = strong_constraints,
## estimator = "ML", meanstructure = TRUE)
##
## Date of Analysis: 2026-06-07 00:14:00.661305
## Time difference of 4.61526 secs
## Computation Time: 4.61526
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 1.5
## Bandwidth = 0.294
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.030
## 2 cfi 0.994
## 3 tli 0.995
## 4 gfi 0.991
## 5 srmr 0.024
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000
## 2 g=~WJ9_z 2 1.309 0.000 0.000 1.309 1.309 1.309
## 3 g=~WJ10_z 3 1.253 0.000 0.000 1.253 1.253 1.253
## 4 g=~DS_z 4 0.872 0.000 0.000 0.872 0.872 0.872
## 5 PPVT_z~~PPVT_z 5 0.569 0.079 0.068 0.486 0.723 0.663
## 6 WJ9_z~~WJ9_z 6 0.322 0.032 0.028 0.283 0.383 0.360
## 7 WJ10_z~~WJ10_z 7 0.403 0.014 0.011 0.367 0.415 0.395
## 8 DS_z~~DS_z 8 0.718 0.024 0.020 0.671 0.742 0.691
## 9 g~~g 9 0.397 0.004 0.003 0.388 0.400 0.395
## 10 PPVT_z~1 10 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 11 WJ9_z~1 11 0.002 0.000 0.000 0.002 0.002 0.002
## 12 WJ10_z~1 12 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 13 DS_z~1 13 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.643 0.026 0.023 0.591 0.670 0.612
## 16 std__g=~WJ9_z 16 0.824 0.014 0.012 0.796 0.840 0.808
## 17 std__g=~WJ10_z 17 0.780 0.004 0.003 0.776 0.790 0.782
## 18 std__g=~DS_z 18 0.544 0.006 0.005 0.539 0.553 0.551
## 19 std__PPVT_z~~PPVT_z 19 0.586 0.033 0.029 0.551 0.651 0.626
## 20 std__WJ9_z~~WJ9_z 20 0.321 0.022 0.019 0.294 0.366 0.347
## 21 std__WJ10_z~~WJ10_z 21 0.392 0.006 0.005 0.376 0.397 0.389
## 22 std__DS_z~~DS_z 22 0.704 0.006 0.006 0.694 0.709 0.697
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000
## 24 std__PPVT_z~1 24 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 25 std__WJ9_z~1 25 0.002 0.000 0.000 0.002 0.002 0.002
## 26 std__WJ10_z~1 26 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 27 std__DS_z~1 27 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000
## lin_slo SD_nonlin
## 1 0.000 0.000
## 2 0.000 0.000
## 3 0.000 0.000
## 4 0.000 0.000
## 5 -0.298 0.012
## 6 -0.119 0.004
## 7 0.023 0.013
## 8 0.086 0.009
## 9 0.007 0.003
## 10 0.000 0.000
## 11 0.000 0.000
## 12 0.000 0.000
## 13 0.000 0.000
## 14 0.000 0.000
## 15 0.099 0.004
## 16 0.051 0.001
## 17 -0.006 0.004
## 18 -0.020 0.002
## 19 -0.126 0.005
## 20 -0.084 0.002
## 21 0.010 0.006
## 22 0.022 0.002
## 23 0.000 0.000
## 24 0.000 0.000
## 25 0.000 0.000
## 26 0.000 0.000
## 27 0.000 0.000
## 28 0.000 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 419.601
## 2 0.070 0.167 679.165
## 3 0.264 0.233 789.229
## 4 0.453 0.259 800.087
## 5 0.619 0.241 713.065
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 680.229 154.315 419.601 800.087
b_strong <- lsem.bootstrap(lsem_strong, R = 50, n.core = 14)
## Compute LSEM for bootstrap samples:
## Warning in sqrt(w): NaNs produced
# ── Strict ───────────────────────────────────────────────────────────────
lsem_strict <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = 1.5,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
par_invariant = strict_constraints
)
## Use joint estimation
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 415
## Group 2 673
## Group 3 782
## Group 4 793
## Group 5 707
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.0738287418959241
## quasi-Newton steps using NLMINB:
## objective function = 0.0738287418959241
## objective function = 0.0607208849850779
## objective function = 0.0460368580072337
## objective function = 0.0282922463973151
## objective function = 0.0235982225693757
## objective function = 0.0204433819236764
## objective function = 0.0173405990967450
## objective function = 0.0166501691167717
## objective function = 0.0151989219212067
## objective function = 0.0149124921074795
## objective function = 0.0146599525541600
## objective function = 0.0144754567668852
## objective function = 0.0143742758813437
## objective function = 0.0143158134569081
## objective function = 0.0142990744494216
## objective function = 0.0142927067858834
## objective function = 0.0142889041360845
## objective function = 0.0142874893023398
## objective function = 0.0142871004911391
## objective function = 0.0142868849123034
## objective function = 0.0142867022819103
## objective function = 0.0142865674403357
## objective function = 0.0142865429770247
## objective function = 0.0142865385057427
## objective function = 0.0142865381148845
## objective function = 0.0142865380301765
## objective function = 0.0142865380114903
## objective function = 0.0142865379976737
## objective function = 0.0142865379870640
## objective function = 0.0142865379808151
## objective function = 0.0142865379788037
## objective function = 0.0142865379781723
## objective function = 0.0142865379781723
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 31
## number of function evaluations [objective, gradient]: 32 32
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
summary(lsem_strict)
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = 1.5,
## standardized = TRUE, par_invariant = strict_constraints,
## estimator = "ML", meanstructure = TRUE)
##
## Date of Analysis: 2026-06-07 00:27:23.443818
## Time difference of 10.26681 secs
## Computation Time: 10.26681
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 1.5
## Bandwidth = 0.294
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.034
## 2 cfi 0.990
## 3 tli 0.994
## 4 gfi 0.986
## 5 srmr 0.030
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000
## 2 g=~WJ9_z 2 1.319 0.000 0.000 1.319 1.319 1.319
## 3 g=~WJ10_z 3 1.259 0.000 0.000 1.259 1.259 1.259
## 4 g=~DS_z 4 0.877 0.000 0.000 0.877 0.877 0.877
## 5 PPVT_z~~PPVT_z 5 0.580 0.000 0.000 0.580 0.580 0.580
## 6 WJ9_z~~WJ9_z 6 0.324 0.000 0.000 0.324 0.324 0.324
## 7 WJ10_z~~WJ10_z 7 0.403 0.000 0.000 0.403 0.403 0.403
## 8 DS_z~~DS_z 8 0.715 0.000 0.000 0.715 0.715 0.715
## 9 g~~g 9 0.393 0.005 0.004 0.385 0.397 0.395
## 10 PPVT_z~1 10 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 11 WJ9_z~1 11 0.002 0.000 0.000 0.002 0.002 0.002
## 12 WJ10_z~1 12 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 13 DS_z~1 13 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.635 0.002 0.002 0.631 0.638 0.637
## 16 std__g=~WJ9_z 16 0.824 0.002 0.001 0.821 0.825 0.825
## 17 std__g=~WJ10_z 17 0.779 0.002 0.002 0.776 0.781 0.780
## 18 std__g=~DS_z 18 0.545 0.002 0.002 0.541 0.547 0.546
## 19 std__PPVT_z~~PPVT_z 19 0.596 0.003 0.003 0.593 0.601 0.595
## 20 std__WJ9_z~~WJ9_z 20 0.322 0.003 0.002 0.319 0.326 0.320
## 21 std__WJ10_z~~WJ10_z 21 0.393 0.003 0.003 0.390 0.398 0.392
## 22 std__DS_z~~DS_z 22 0.703 0.003 0.002 0.701 0.707 0.702
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000
## 24 std__PPVT_z~1 24 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 25 std__WJ9_z~1 25 0.002 0.000 0.000 0.002 0.002 0.002
## 26 std__WJ10_z~1 26 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 27 std__DS_z~1 27 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000
## lin_slo SD_nonlin
## 1 0.000 0.000
## 2 0.000 0.000
## 3 0.000 0.000
## 4 0.000 0.000
## 5 0.000 0.000
## 6 0.000 0.000
## 7 0.000 0.000
## 8 0.000 0.000
## 9 -0.009 0.004
## 10 0.000 0.000
## 11 0.000 0.000
## 12 0.000 0.000
## 13 0.000 0.000
## 14 0.000 0.000
## 15 -0.004 0.002
## 16 -0.003 0.002
## 17 -0.003 0.002
## 18 -0.004 0.002
## 19 0.005 0.003
## 20 0.005 0.003
## 21 0.005 0.003
## 22 0.005 0.002
## 23 0.000 0.000
## 24 0.000 0.000
## 25 0.000 0.000
## 26 0.000 0.000
## 27 0.000 0.000
## 28 0.000 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 419.601
## 2 0.070 0.167 679.165
## 3 0.264 0.233 789.229
## 4 0.453 0.259 800.087
## 5 0.619 0.241 713.065
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 680.229 154.315 419.601 800.087
b_strict <- lsem.bootstrap(lsem_strict, R = 50, n.core = 14)
## Compute LSEM for bootstrap samples:
## Warning in sqrt(w): NaNs produced
# ── Compare fit across invariance levels ─────────────────────────────────
invar_test <- rbind(b_config$fitstats_joint,
b_weak$fitstats_joint,
b_strong$fitstats_joint,
b_strict$fitstats_joint) %>%
mutate(ci_l = value - 1.96 * se,
ci_u = value + 1.96 * se)
invar_test$level <- c(rep("configural", nrow(b_config$fitstats_joint)),
rep("metric", nrow(b_weak$fitstats_joint)),
rep("scalar", nrow(b_strong$fitstats_joint)),
rep("strict", nrow(b_strict$fitstats_joint)))
invar_test$level <- factor(invar_test$level, levels = unique(invar_test$level))
print(invar_test)
## stat value value_bc se ci_l ci_u
## rmsea rmsea 0.08151605 0.07609162 0.019801303 0.042705495 0.12032660
## cfi cfi 0.98896606 0.99096480 0.006026830 0.977153475 1.00077865
## tli tli 0.96689819 0.97289441 0.018080490 0.931460426 1.00233595
## gfi gfi 0.99213423 0.99342406 0.003362655 0.985543427 0.99872503
## srmr srmr 0.01980587 0.01879424 0.004132555 0.011706066 0.02790568
## rmsea1 rmsea 0.05143884 0.04505043 0.015915960 0.020243560 0.08263412
## cfi1 cfi 0.99033394 0.99366876 0.006413647 0.977763196 1.00290469
## tli1 tli 0.98681901 0.99134373 0.008794299 0.969582188 1.00405584
## gfi1 gfi 0.99125614 0.99319466 0.003482912 0.984429631 0.99808265
## srmr1 srmr 0.02412126 0.01768349 0.005301589 0.013730150 0.03451238
## rmsea2 rmsea 0.03020629 0.02412655 0.014607970 0.001574674 0.05883792
## cfi2 cfi 0.99424265 0.99818689 0.006006430 0.982470044 1.00601525
## tli2 tli 0.99545472 0.99849641 0.004868532 0.985912400 1.00499704
## gfi2 gfi 0.99123627 0.99342120 0.003325152 0.984718973 0.99775357
## srmr2 srmr 0.02419120 0.01956473 0.005007583 0.014376338 0.03400606
## rmsea3 rmsea 0.03408776 0.01994021 0.012213909 0.010148502 0.05802703
## cfi3 cfi 0.98958078 1.00134079 0.010595896 0.968812828 1.01034874
## tli3 tli 0.99421155 1.00074489 0.005886609 0.982673793 1.00574930
## gfi3 gfi 0.98632823 0.99290633 0.005517029 0.975514857 0.99714161
## srmr3 srmr 0.03025903 0.01798922 0.006240058 0.018028517 0.04248954
## level
## rmsea configural
## cfi configural
## tli configural
## gfi configural
## srmr configural
## rmsea1 metric
## cfi1 metric
## tli1 metric
## gfi1 metric
## srmr1 metric
## rmsea2 scalar
## cfi2 scalar
## tli2 scalar
## gfi2 scalar
## srmr2 scalar
## rmsea3 strict
## cfi3 strict
## tli3 strict
## gfi3 strict
## srmr3 strict
# ── Plots ────────────────────────────────────────────────────────────────
p_cfi <- invar_test %>%
filter(stat == "cfi") %>%
ggplot(aes(x = level, y = value)) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = ci_l, ymax = ci_u), width = .2) +
scale_y_continuous(limits = c(.9, 1)) +
ylab("CFI") + xlab("Invariance Level") +
theme_minimal()
p_rmsea <- invar_test %>%
filter(stat == "rmsea") %>%
ggplot(aes(x = level, y = value)) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = ci_l, ymax = ci_u), width = .2) +
scale_y_continuous(limits = c(0, .15)) +
ylab("RMSEA") + xlab("Invariance Level") +
theme_minimal()
p_srmr <- invar_test %>%
filter(stat == "srmr") %>%
ggplot(aes(x = level, y = value)) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = ci_l, ymax = ci_u), width = .2) +
scale_y_continuous(limits = c(0, .15)) +
ylab("SRMR") + xlab("Invariance Level") +
theme_minimal()
print(p_cfi)

print(p_rmsea)

print(p_srmr)

#LevelRMSEACFITLISRMRConfigural.082 (.031–.131).989 (.973–1.005).967 (.919–1.015).020 (.010–.030)
#Metric.051 (.021–.082).990 (.976–1.005).987 (.967–1.006).024 (.013–.035)
#Scalar.030 (.001–.059).994 (.982–1.006).995 (.986–1.005).024 (.014–.034)
#Strict.034 (.010–.058).990 (.973–1.006).994 (.985–1.004).030 (.020–.041)
# ==============================================================
# 11b. Multi-Group CFA: Measurement Invariance (PC1A tertiles)
# ==============================================================
# Direct test of configural -> metric -> scalar -> strict.
# Complements LSEM: LSEM tests continuous parameter moderation
# but doesn't explicitly test intercepts. This confirms scalar
# invariance directly.
cfa_model <- 'g =~ PPVT_z + WJ9_z + WJ10_z + DS_z'
run_mi_cfa <- function(data, pc_var, label) {
dd <- data[complete.cases(data[, c("PPVT_z", "WJ9_z", "WJ10_z", "DS_z", pc_var)]), ]
dd$tert <- cut(dd[[pc_var]],
breaks = quantile(dd[[pc_var]], probs = c(0, 1/3, 2/3, 1)),
include.lowest = TRUE, labels = c("Low", "Mid", "High"))
cat("\n", paste(rep("=", 80), collapse = ""), "\n")
cat(label, "\n")
cat("N =", nrow(dd), " | Tertile Ns:", table(dd$tert), "\n")
cat(paste(rep("=", 80), collapse = ""), "\n")
fit_config <- cfa(cfa_model, data = dd, group = "tert", estimator = "ML",
meanstructure = TRUE)
fit_metric <- cfa(cfa_model, data = dd, group = "tert", estimator = "ML",
meanstructure = TRUE, group.equal = "loadings")
fit_scalar <- cfa(cfa_model, data = dd, group = "tert", estimator = "ML",
meanstructure = TRUE, group.equal = c("loadings", "intercepts"))
fit_strict <- cfa(cfa_model, data = dd, group = "tert", estimator = "ML",
meanstructure = TRUE, group.equal = c("loadings", "intercepts", "residuals"))
extract_fit <- function(fit, lbl) {
fm <- fitmeasures(fit, c("chisq", "df", "pvalue", "cfi", "tli", "rmsea", "srmr"))
data.frame(Model = lbl,
chisq = round(fm["chisq"], 3),
df = fm["df"],
p = round(fm["pvalue"], 4),
CFI = round(fm["cfi"], 4),
TLI = round(fm["tli"], 4),
RMSEA = round(fm["rmsea"], 4),
SRMR = round(fm["srmr"], 4),
row.names = NULL)
}
fit_table <- rbind(
extract_fit(fit_config, "1. Configural"),
extract_fit(fit_metric, "2. Metric"),
extract_fit(fit_scalar, "3. Scalar"),
extract_fit(fit_strict, "4. Strict")
)
print(fit_table, row.names = FALSE)
cfis <- sapply(list(fit_config, fit_metric, fit_scalar, fit_strict), fitmeasures, "cfi")
steps <- c("Config->Metric", "Metric->Scalar", "Scalar->Strict")
cat("\n")
for (i in 1:3) {
d_cfi <- cfis[i + 1] - cfis[i]
cat(sprintf(" %-20s ΔCFI = %+.4f %s\n", steps[i], d_cfi,
ifelse(abs(d_cfi) < 0.01, "[PASS]", "[FAIL]")))
}
cat("\n")
}
run_mi_cfa(d_apa, "K5PC1A", "APA x African Ancestry PC (PC1A) tertiles")
##
## ================================================================================
## APA x African Ancestry PC (PC1A) tertiles
## N = 1507 | Tertile Ns: 503 502 502
## ================================================================================
## Model chisq df p CFI TLI RMSEA SRMR
## 1. Configural 27.064 6 0.0001 0.9882 0.9647 0.0836 0.0202
## 2. Metric 30.133 12 0.0027 0.9899 0.9848 0.0548 0.0246
## 3. Scalar 44.392 18 0.0005 0.9853 0.9853 0.0540 0.0316
## 4. Strict 64.077 26 0.0000 0.9788 0.9853 0.0540 0.0423
##
## Config->Metric ΔCFI = +0.0016 [PASS]
## Metric->Scalar ΔCFI = -0.0046 [PASS]
## Scalar->Strict ΔCFI = -0.0065 [PASS]
run_mi_cfa(d_haa, "K5PC1A", "HAA x African Ancestry PC (PC1A) tertiles")
##
## ================================================================================
## HAA x African Ancestry PC (PC1A) tertiles
## N = 1261 | Tertile Ns: 421 420 420
## ================================================================================
## Model chisq df p CFI TLI RMSEA SRMR
## 1. Configural 24.788 6 0.0004 0.9876 0.9629 0.0863 0.0209
## 2. Metric 27.855 12 0.0058 0.9896 0.9843 0.0561 0.0257
## 3. Scalar 35.979 18 0.0071 0.9882 0.9882 0.0487 0.0302
## 4. Strict 67.081 26 0.0000 0.9729 0.9813 0.0613 0.0438
##
## Config->Metric ΔCFI = +0.0019 [PASS]
## Metric->Scalar ΔCFI = -0.0014 [PASS]
## Scalar->Strict ΔCFI = -0.0152 [FAIL]
# ==============================================================
# 11c. LSEM Bandwidth Sensitivity
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("ROBUSTNESS: LSEM bandwidth sensitivity (configural only)\n")
## ROBUSTNESS: LSEM bandwidth sensitivity (configural only)
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
for (h_val in c(1.0, 2.0)) {
lsem_h <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = h_val,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
est_joint = TRUE
)
cat("\nh =", h_val, "\n")
print(summary(lsem_h)$fitstat_joint)
}
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 260
## Group 2 469
## Group 3 568
## Group 4 592
## Group 5 526
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.4646802919463014
## quasi-Newton steps using NLMINB:
## objective function = 0.4646802919463014
## objective function = 0.4225779864181642
## objective function = 0.3030533682262448
## objective function = 0.1497680920336554
## objective function = 0.1279494179505798
## objective function = 0.0777625558671662
## objective function = 0.0798683093680342
## objective function = 0.0599892344685638
## objective function = 0.0444734341401703
## objective function = 0.0340176569516158
## objective function = 0.0270121526829740
## objective function = 0.0213671496784743
## objective function = 0.0174365298548621
## objective function = 0.0151715145285565
## objective function = 0.0138179123770358
## objective function = 0.0128967974362694
## objective function = 0.0121196769333752
## objective function = 0.0112136582771468
## objective function = 0.0107934502881314
## objective function = 0.0104370158380902
## objective function = 0.0101081358456193
## objective function = 0.0099255480230321
## objective function = 0.0097135264327441
## objective function = 0.0095350280138221
## objective function = 0.0094812292277207
## objective function = 0.0093933602798418
## objective function = 0.0093071341004641
## objective function = 0.0092492658402065
## objective function = 0.0091913130662916
## objective function = 0.0091521573609960
## objective function = 0.0091236696274345
## objective function = 0.0091040328546773
## objective function = 0.0090891759272326
## objective function = 0.0090794229625522
## objective function = 0.0090737565202157
## objective function = 0.0090704309157353
## objective function = 0.0090678149770005
## objective function = 0.0090655767130282
## objective function = 0.0090631642283111
## objective function = 0.0090613466648644
## objective function = 0.0090595663705665
## objective function = 0.0090578618798858
## objective function = 0.0090559849108183
## objective function = 0.0090545824008221
## objective function = 0.0090535150648220
## objective function = 0.0090529481224803
## objective function = 0.0090525439401810
## objective function = 0.0090522443068635
## objective function = 0.0090519155764478
## objective function = 0.0090517124764770
## objective function = 0.0090515210609698
## objective function = 0.0090513909308049
## objective function = 0.0090512890855913
## objective function = 0.0090512166962937
## objective function = 0.0090511618843839
## objective function = 0.0090511161610214
## objective function = 0.0090510805827558
## objective function = 0.0090510530777419
## objective function = 0.0090510375414917
## objective function = 0.0090510276174877
## objective function = 0.0090510216355476
## objective function = 0.0090510172627789
## objective function = 0.0090510140514977
## objective function = 0.0090510116985100
## objective function = 0.0090510101536696
## objective function = 0.0090510093647467
## objective function = 0.0090510090080444
## objective function = 0.0090510088512363
## objective function = 0.0090510087716353
## objective function = 0.0090510087316453
## objective function = 0.0090510087102711
## objective function = 0.0090510086953283
## objective function = 0.0090510086825069
## objective function = 0.0090510086730037
## objective function = 0.0090510086676137
## objective function = 0.0090510086646993
## objective function = 0.0090510086625184
## objective function = 0.0090510086606419
## objective function = 0.0090510086593741
## objective function = 0.0090510086584562
## objective function = 0.0090510086584562
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 78
## number of function evaluations [objective, gradient]: 80 79
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
##
##
## h = 1
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = h_val,
## standardized = TRUE, est_joint = TRUE, estimator = "ML",
## meanstructure = TRUE)
##
## Date of Analysis: 2026-06-07 00:39:47.125757
## Time difference of 4.79413 secs
## Computation Time: 4.79413
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 1
## Bandwidth = 0.196
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.084
## 2 cfi 0.988
## 3 tli 0.965
## 4 gfi 0.991
## 5 srmr 0.021
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int lin_slo
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 2 g=~WJ9_z 2 1.314 0.099 0.089 1.213 1.481 1.417 -0.325
## 3 g=~WJ10_z 3 1.255 0.058 0.046 1.212 1.403 1.319 -0.204
## 4 g=~DS_z 4 0.877 0.037 0.027 0.845 0.974 0.909 -0.102
## 5 PPVT_z~~PPVT_z 5 0.563 0.096 0.082 0.468 0.733 0.674 -0.354
## 6 WJ9_z~~WJ9_z 6 0.316 0.014 0.011 0.294 0.340 0.330 -0.047
## 7 WJ10_z~~WJ10_z 7 0.409 0.018 0.016 0.371 0.422 0.406 0.009
## 8 DS_z~~DS_z 8 0.723 0.034 0.030 0.664 0.760 0.686 0.118
## 9 g~~g 9 0.398 0.038 0.032 0.327 0.431 0.355 0.139
## 10 PPVT_z~1 10 0.001 0.006 0.004 -0.008 0.013 0.002 -0.001
## 11 WJ9_z~1 11 0.002 0.005 0.004 -0.002 0.013 0.003 -0.004
## 12 WJ10_z~1 12 0.000 0.004 0.003 -0.005 0.005 0.001 -0.005
## 13 DS_z~1 13 -0.001 0.002 0.002 -0.003 0.003 0.000 -0.002
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.645 0.050 0.043 0.556 0.693 0.587 0.183
## 16 std__g=~WJ9_z 16 0.827 0.008 0.007 0.809 0.838 0.826 0.003
## 17 std__g=~WJ10_z 17 0.777 0.010 0.009 0.764 0.797 0.776 0.004
## 18 std__g=~DS_z 18 0.545 0.009 0.008 0.534 0.564 0.546 -0.005
## 19 std__PPVT_z~~PPVT_z 19 0.582 0.063 0.054 0.520 0.691 0.654 -0.230
## 20 std__WJ9_z~~WJ9_z 20 0.317 0.014 0.011 0.298 0.345 0.318 -0.005
## 21 std__WJ10_z~~WJ10_z 21 0.396 0.016 0.014 0.366 0.416 0.398 -0.006
## 22 std__DS_z~~DS_z 22 0.703 0.010 0.008 0.681 0.715 0.702 0.006
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 24 std__PPVT_z~1 24 0.001 0.005 0.004 -0.007 0.012 0.002 -0.001
## 25 std__WJ9_z~1 25 0.002 0.005 0.004 -0.002 0.012 0.003 -0.003
## 26 std__WJ10_z~1 26 0.000 0.004 0.003 -0.005 0.005 0.001 -0.005
## 27 std__DS_z~1 27 -0.001 0.002 0.002 -0.003 0.003 0.000 -0.002
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## SD_nonlin
## 1 0.000
## 2 0.049
## 3 0.023
## 4 0.025
## 5 0.024
## 6 0.006
## 7 0.018
## 8 0.014
## 9 0.011
## 10 0.006
## 11 0.005
## 12 0.004
## 13 0.002
## 14 0.000
## 15 0.013
## 16 0.008
## 17 0.010
## 18 0.009
## 19 0.016
## 20 0.014
## 21 0.016
## 22 0.010
## 23 0.000
## 24 0.005
## 25 0.005
## 26 0.004
## 27 0.002
## 28 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 262.694
## 2 0.070 0.167 473.933
## 3 0.264 0.233 572.817
## 4 0.453 0.259 597.446
## 5 0.619 0.241 531.044
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 487.587 134.150 262.694 597.446
## NULL
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 575
## Group 2 840
## Group 3 936
## Group 4 934
## Group 5 846
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.0377520215371714
## quasi-Newton steps using NLMINB:
## objective function = 0.0377520215371714
## objective function = 0.0290327677763251
## objective function = 0.0157769580623049
## objective function = 0.0135850350477670
## objective function = 0.0111661433071985
## objective function = 0.0102969869167592
## objective function = 0.0096173778122135
## objective function = 0.0090299144523986
## objective function = 0.0086500877636569
## objective function = 0.0084386507079767
## objective function = 0.0081821980604341
## objective function = 0.0079682594800023
## objective function = 0.0077874145204968
## objective function = 0.0076473417731989
## objective function = 0.0075681840081152
## objective function = 0.0075008818479938
## objective function = 0.0074547260793011
## objective function = 0.0074204004740911
## objective function = 0.0073964609457680
## objective function = 0.0073790549148058
## objective function = 0.0073654892095357
## objective function = 0.0073542628474625
## objective function = 0.0073455818825337
## objective function = 0.0073385722018415
## objective function = 0.0073321982163790
## objective function = 0.0073262705208896
## objective function = 0.0073195883918944
## objective function = 0.0073150190070719
## objective function = 0.0073116937038556
## objective function = 0.0073098025573191
## objective function = 0.0073082610994214
## objective function = 0.0073068798413530
## objective function = 0.0073056859398833
## objective function = 0.0073046218316870
## objective function = 0.0073041389052434
## objective function = 0.0073037983440756
## objective function = 0.0073035995723406
## objective function = 0.0073034270148445
## objective function = 0.0073032399053524
## objective function = 0.0073030943362218
## objective function = 0.0073029564541183
## objective function = 0.0073028368078026
## objective function = 0.0073027547537983
## objective function = 0.0073026915439050
## objective function = 0.0073026415664659
## objective function = 0.0073026079342034
## objective function = 0.0073025889947602
## objective function = 0.0073025795905114
## objective function = 0.0073025739129707
## objective function = 0.0073025695569176
## objective function = 0.0073025661982127
## objective function = 0.0073025637752580
## objective function = 0.0073025619608914
## objective function = 0.0073025605012492
## objective function = 0.0073025593749970
## objective function = 0.0073025585971924
## objective function = 0.0073025580842460
## objective function = 0.0073025577256517
## objective function = 0.0073025574563816
## objective function = 0.0073025572532064
## objective function = 0.0073025571120364
## objective function = 0.0073025570269348
## objective function = 0.0073025569784277
## objective function = 0.0073025569451126
## objective function = 0.0073025569165328
## objective function = 0.0073025568923179
## objective function = 0.0073025568736596
## objective function = 0.0073025568648071
## objective function = 0.0073025568614206
## objective function = 0.0073025568601680
## objective function = 0.0073025568596323
## objective function = 0.0073025568596323
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 70
## number of function evaluations [objective, gradient]: 71 71
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
##
##
## h = 2
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = h_val,
## standardized = TRUE, est_joint = TRUE, estimator = "ML",
## meanstructure = TRUE)
##
## Date of Analysis: 2026-06-07 00:39:51.992176
## Time difference of 4.613003 secs
## Computation Time: 4.613003
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 2
## Bandwidth = 0.392
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.078
## 2 cfi 0.990
## 3 tli 0.970
## 4 gfi 0.993
## 5 srmr 0.019
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int lin_slo
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 2 g=~WJ9_z 2 1.306 0.078 0.070 1.206 1.431 1.398 -0.290
## 3 g=~WJ10_z 3 1.253 0.052 0.040 1.202 1.375 1.314 -0.194
## 4 g=~DS_z 4 0.871 0.027 0.021 0.845 0.938 0.904 -0.102
## 5 PPVT_z~~PPVT_z 5 0.569 0.078 0.067 0.481 0.720 0.661 -0.293
## 6 WJ9_z~~WJ9_z 6 0.326 0.028 0.019 0.304 0.403 0.357 -0.096
## 7 WJ10_z~~WJ10_z 7 0.398 0.018 0.016 0.355 0.416 0.394 0.013
## 8 DS_z~~DS_z 8 0.715 0.017 0.014 0.676 0.731 0.696 0.060
## 9 g~~g 9 0.399 0.034 0.029 0.331 0.438 0.359 0.129
## 10 PPVT_z~1 10 -0.002 0.002 0.002 -0.004 0.000 -0.001 -0.003
## 11 WJ9_z~1 11 0.000 0.003 0.003 -0.002 0.005 0.004 -0.011
## 12 WJ10_z~1 12 -0.002 0.004 0.004 -0.007 0.004 0.002 -0.012
## 13 DS_z~1 13 -0.002 0.001 0.001 -0.005 0.000 -0.002 0.001
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.643 0.042 0.036 0.561 0.690 0.593 0.157
## 16 std__g=~WJ9_z 16 0.822 0.010 0.006 0.792 0.827 0.814 0.024
## 17 std__g=~WJ10_z 17 0.781 0.008 0.007 0.773 0.798 0.782 -0.001
## 18 std__g=~DS_z 18 0.545 0.003 0.003 0.541 0.549 0.544 0.003
## 19 std__PPVT_z~~PPVT_z 19 0.585 0.053 0.046 0.523 0.685 0.648 -0.198
## 20 std__WJ9_z~~WJ9_z 20 0.325 0.016 0.010 0.315 0.373 0.337 -0.039
## 21 std__WJ10_z~~WJ10_z 21 0.389 0.013 0.011 0.362 0.402 0.389 0.002
## 22 std__DS_z~~DS_z 22 0.703 0.003 0.003 0.698 0.707 0.704 -0.004
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000 0.000
## 24 std__PPVT_z~1 24 -0.002 0.002 0.002 -0.005 0.000 -0.001 -0.004
## 25 std__WJ9_z~1 25 0.000 0.003 0.003 -0.003 0.005 0.004 -0.011
## 26 std__WJ10_z~1 26 -0.002 0.004 0.004 -0.007 0.004 0.002 -0.012
## 27 std__DS_z~1 27 -0.002 0.001 0.001 -0.005 0.000 -0.002 0.001
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000 0.000
## SD_nonlin
## 1 0.000
## 2 0.018
## 3 0.007
## 4 0.005
## 5 0.008
## 6 0.011
## 7 0.018
## 8 0.007
## 9 0.002
## 10 0.002
## 11 0.001
## 12 0.002
## 13 0.001
## 14 0.000
## 15 0.004
## 16 0.008
## 17 0.008
## 18 0.003
## 19 0.006
## 20 0.013
## 21 0.013
## 22 0.003
## 23 0.000
## 24 0.002
## 25 0.001
## 26 0.002
## 27 0.001
## 28 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 580.209
## 2 0.070 0.167 848.175
## 3 0.264 0.233 945.139
## 4 0.453 0.259 942.705
## 5 0.619 0.241 853.379
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 833.921 149.294 580.209 945.139
## NULL
# run invariance models for comparison
cat("\nScalar invariance at h = 1.0:\n")
##
## Scalar invariance at h = 1.0:
lsem_strong_h1 <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = 1.0,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
par_invariant = strong_constraints
)
## Use joint estimation
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 260
## Group 2 469
## Group 3 568
## Group 4 592
## Group 5 526
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.4646802919463016
## quasi-Newton steps using NLMINB:
## objective function = 0.4646802919463016
## objective function = 0.4232976035920123
## objective function = 0.3807467900417316
## objective function = 0.3409017491427810
## objective function = 0.2736828981674165
## objective function = Inf
## objective function = 0.2303475896993389
## objective function = 0.1645452349024202
## objective function = 0.1588435629690765
## objective function = 0.1492275036007128
## objective function = 0.1192368379048396
## objective function = 0.1889196912016715
## objective function = 0.1041446110785343
## objective function = 0.0944296225667279
## objective function = 0.0953728643739491
## objective function = 0.0846064049069207
## objective function = 0.0770207738115011
## objective function = 0.0596797522324349
## objective function = 0.0478058953560617
## objective function = 0.0396395832465671
## objective function = 0.0289207964272656
## objective function = 0.0230442958463596
## objective function = 0.0202570296086500
## objective function = 0.0176319461358133
## objective function = 0.0146775887716812
## objective function = 0.0132438560073690
## objective function = 0.0121594940702959
## objective function = 0.0111020306042772
## objective function = 0.0108591293427332
## objective function = 0.0106621612127983
## objective function = 0.0105922809652880
## objective function = 0.0105604957911247
## objective function = 0.0105468800176812
## objective function = 0.0105390378361020
## objective function = 0.0105350290674740
## objective function = 0.0105319129799973
## objective function = 0.0105309934565356
## objective function = 0.0105304841267851
## objective function = 0.0105303794991556
## objective function = 0.0105303258784127
## objective function = 0.0105302889909400
## objective function = 0.0105302658944006
## objective function = 0.0105302532648457
## objective function = 0.0105302486054307
## objective function = 0.0105302468587104
## objective function = 0.0105302460135668
## objective function = 0.0105302456257251
## objective function = 0.0105302454742300
## objective function = 0.0105302454077906
## objective function = 0.0105302453779024
## objective function = 0.0105302453662230
## objective function = 0.0105302453611104
## objective function = 0.0105302453585576
## objective function = 0.0105302453576604
## objective function = 0.0105302453576604
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 47
## number of function evaluations [objective, gradient]: 54 48
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
print(summary(lsem_strong_h1)$fitstat_joint)
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = 1,
## standardized = TRUE, par_invariant = strong_constraints,
## estimator = "ML", meanstructure = TRUE)
##
## Date of Analysis: 2026-06-07 00:39:56.700718
## Time difference of 4.537394 secs
## Computation Time: 4.537394
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 1
## Bandwidth = 0.196
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.026
## 2 cfi 0.996
## 3 tli 0.997
## 4 gfi 0.990
## 5 srmr 0.026
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000
## 2 g=~WJ9_z 2 1.305 0.000 0.000 1.305 1.305 1.305
## 3 g=~WJ10_z 3 1.249 0.000 0.000 1.249 1.249 1.249
## 4 g=~DS_z 4 0.871 0.000 0.000 0.871 0.871 0.871
## 5 PPVT_z~~PPVT_z 5 0.564 0.088 0.075 0.476 0.719 0.666
## 6 WJ9_z~~WJ9_z 6 0.317 0.031 0.028 0.275 0.365 0.346
## 7 WJ10_z~~WJ10_z 7 0.409 0.014 0.012 0.392 0.432 0.401
## 8 DS_z~~DS_z 8 0.724 0.036 0.033 0.671 0.762 0.685
## 9 g~~g 9 0.400 0.005 0.004 0.387 0.405 0.396
## 10 PPVT_z~1 10 0.002 0.000 0.000 0.002 0.002 0.002
## 11 WJ9_z~1 11 0.002 0.000 0.000 0.002 0.002 0.002
## 12 WJ10_z~1 12 0.000 0.000 0.000 0.000 0.000 0.000
## 13 DS_z~1 13 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.646 0.030 0.026 0.592 0.674 0.611
## 16 std__g=~WJ9_z 16 0.826 0.013 0.012 0.807 0.843 0.813
## 17 std__g=~WJ10_z 17 0.777 0.004 0.003 0.771 0.781 0.779
## 18 std__g=~DS_z 18 0.544 0.008 0.008 0.535 0.556 0.552
## 19 std__PPVT_z~~PPVT_z 19 0.582 0.038 0.033 0.545 0.650 0.626
## 20 std__WJ9_z~~WJ9_z 20 0.317 0.022 0.020 0.289 0.349 0.339
## 21 std__WJ10_z~~WJ10_z 21 0.396 0.006 0.005 0.390 0.406 0.393
## 22 std__DS_z~~DS_z 22 0.704 0.009 0.009 0.691 0.714 0.695
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000
## 24 std__PPVT_z~1 24 0.002 0.000 0.000 0.001 0.002 0.001
## 25 std__WJ9_z~1 25 0.002 0.000 0.000 0.002 0.002 0.002
## 26 std__WJ10_z~1 26 0.000 0.000 0.000 0.000 0.000 0.000
## 27 std__DS_z~1 27 -0.001 0.000 0.000 -0.001 -0.001 -0.001
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000
## lin_slo SD_nonlin
## 1 0.000 0.000
## 2 0.000 0.000
## 3 0.000 0.000
## 4 0.000 0.000
## 5 -0.325 0.023
## 6 -0.092 0.020
## 7 0.027 0.013
## 8 0.122 0.015
## 9 0.011 0.004
## 10 0.000 0.000
## 11 0.000 0.000
## 12 0.000 0.000
## 13 0.000 0.000
## 14 0.000 0.000
## 15 0.110 0.007
## 16 0.042 0.007
## 17 -0.006 0.004
## 18 -0.028 0.004
## 19 -0.140 0.010
## 20 -0.070 0.012
## 21 0.009 0.006
## 22 0.030 0.004
## 23 0.000 0.000
## 24 0.000 0.000
## 25 0.000 0.000
## 26 0.000 0.000
## 27 0.000 0.000
## 28 0.000 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 262.694
## 2 0.070 0.167 473.933
## 3 0.264 0.233 572.817
## 4 0.453 0.259 597.446
## 5 0.619 0.241 531.044
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 487.587 134.150 262.694 597.446
## NULL
cat("\nScalar invariance at h = 2.0:\n")
##
## Scalar invariance at h = 2.0:
lsem_strong_h2 <- lsem.estimate(
data = as.data.frame(d_apa_cog),
moderator = "PC1A_z",
moderator.grid = grid_points,
lavmodel = lsem_model,
h = 2.0,
estimator = "ML",
meanstructure = TRUE,
standardized = TRUE,
par_invariant = strong_constraints
)
## Use joint estimation
## ** Residualize Data
## ** Fit lavaan model
## |*****|
## |lavoptions ... done.
## lavdata ... done.
## Number of observations per group:
## Group 1 575
## Group 2 840
## Group 3 936
## Group 4 934
## Group 5 846
## lavsamplestats ... done.
## lavh1 ... start:
## lavh1 ... done.
## lavpartable bounds ... done.
## lavstart ... done.
## lavmodel ... done.
## lavoptim ... start:
## attempt 1 -- default options
## objective function = 0.0377520215371713
## quasi-Newton steps using NLMINB:
## objective function = 0.0377520215371713
## objective function = 0.0288499951086053
## objective function = 0.0168866408402162
## objective function = 0.0145662261323792
## objective function = 0.0130494283019911
## objective function = 0.0120714478582574
## objective function = 0.0111501024676180
## objective function = 0.0104613484182083
## objective function = 0.0098762110854294
## objective function = 0.0089458576972912
## objective function = 0.0086886259909628
## objective function = 0.0084626929895919
## objective function = 0.0082883301279253
## objective function = 0.0081799915134803
## objective function = 0.0081177924549689
## objective function = 0.0080909252144403
## objective function = 0.0080776480630838
## objective function = 0.0080715362430142
## objective function = 0.0080688996390857
## objective function = 0.0080677099780516
## objective function = 0.0080670851390732
## objective function = 0.0080667115829195
## objective function = 0.0080665020209305
## objective function = 0.0080663863678959
## objective function = 0.0080663297610787
## objective function = 0.0080663101079686
## objective function = 0.0080663054139723
## objective function = 0.0080663043063940
## objective function = 0.0080663039483497
## objective function = 0.0080663038368748
## objective function = 0.0080663038080177
## objective function = 0.0080663037994854
## objective function = 0.0080663037962883
## objective function = 0.0080663037951706
## objective function = 0.0080663037948385
## objective function = 0.0080663037948385
## convergence status (0=ok): 0
## nlminb message says: relative convergence (4)
## number of iterations: 33
## number of function evaluations [objective, gradient]: 35 34
## lavoptim ... done.
## lavimplied ... done.
## lavloglik ... done.
## computing TEST for test(s) = standard ... done.
## lavbaseline ... done.
## post check ... done.
##
## Use lsem.bootstrap() for computing standard errors!
##
## -|
## ** Parameter summary
print(summary(lsem_strong_h2)$fitstat_joint)
## -----------------------------------------------------------------
## Local Structural Equation Model
##
## sirt 4.2-133 (2025-09-27 12:57:51)
## lavaan 0.6-19 (2024-09-26 15:50:14 UTC)
##
## R version 4.5.1 (2025-06-13 ucrt) x86_64, mingw32 | nodename=HUANG | login=mh198
##
## Function 'sirt::lsem.estimate', type='LSEM'
##
##
## Call:
## lsem.estimate(data = as.data.frame(d_apa_cog), moderator = "PC1A_z",
## moderator.grid = grid_points, lavmodel = lsem_model, h = 2,
## standardized = TRUE, par_invariant = strong_constraints,
## estimator = "ML", meanstructure = TRUE)
##
## Date of Analysis: 2026-06-07 00:40:02.241883
## Time difference of 4.450271 secs
## Computation Time: 4.450271
##
## Number of observations in datasets = 1538
## Used observations in analysis = 1524
## Used sampling weights: FALSE
## Bandwidth factor = 2
## Bandwidth = 0.392
## Number of focal points for moderator = 5
##
## Used joint estimation: TRUE
## Used sufficient statistics: TRUE
## Used local linear smoothing: TRUE
## Used pseudo weights: FALSE
## Used lavaan package: TRUE
## Used lavaan.survey package: FALSE
##
## Mean structure modelled: TRUE
##
## lavaan Model
## g =~ PPVT_z + WJ9_z + WJ10_z + DS_z
##
## Global Fit Statistics for Joint Estimation
##
## stat value
## 1 rmsea 0.030
## 2 cfi 0.994
## 3 tli 0.995
## 4 gfi 0.992
## 5 srmr 0.023
##
##
## Parameter Estimate Summary
##
## par parindex M SD MAD Min Max lin_int
## 1 g=~PPVT_z 1 1.000 0.000 0.000 1.000 1.000 1.000
## 2 g=~WJ9_z 2 1.306 0.000 0.000 1.306 1.306 1.306
## 3 g=~WJ10_z 3 1.254 0.000 0.000 1.254 1.254 1.254
## 4 g=~DS_z 4 0.871 0.000 0.000 0.871 0.871 0.871
## 5 PPVT_z~~PPVT_z 5 0.570 0.070 0.060 0.492 0.708 0.654
## 6 WJ9_z~~WJ9_z 6 0.326 0.035 0.028 0.288 0.407 0.368
## 7 WJ10_z~~WJ10_z 7 0.398 0.014 0.010 0.362 0.409 0.391
## 8 DS_z~~DS_z 8 0.715 0.018 0.015 0.676 0.731 0.695
## 9 g~~g 9 0.397 0.004 0.002 0.386 0.400 0.394
## 10 PPVT_z~1 10 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 11 WJ9_z~1 11 0.001 0.000 0.000 0.001 0.001 0.001
## 12 WJ10_z~1 12 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 13 DS_z~1 13 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 14 g~1 14 0.000 0.000 0.000 0.000 0.000 0.000
## 15 std__g=~PPVT_z 15 0.642 0.024 0.020 0.594 0.668 0.614
## 16 std__g=~WJ9_z 16 0.822 0.015 0.012 0.786 0.838 0.804
## 17 std__g=~WJ10_z 17 0.782 0.004 0.003 0.778 0.792 0.783
## 18 std__g=~DS_z 18 0.544 0.004 0.003 0.541 0.550 0.548
## 19 std__PPVT_z~~PPVT_z 19 0.587 0.030 0.026 0.554 0.647 0.623
## 20 std__WJ9_z~~WJ9_z 20 0.324 0.025 0.019 0.298 0.382 0.353
## 21 std__WJ10_z~~WJ10_z 21 0.389 0.006 0.005 0.373 0.394 0.386
## 22 std__DS_z~~DS_z 22 0.704 0.004 0.003 0.698 0.707 0.699
## 23 std__g~~g 23 1.000 0.000 0.000 1.000 1.000 1.000
## 24 std__PPVT_z~1 24 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 25 std__WJ9_z~1 25 0.001 0.000 0.000 0.001 0.001 0.001
## 26 std__WJ10_z~1 26 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 27 std__DS_z~1 27 -0.002 0.000 0.000 -0.002 -0.002 -0.002
## 28 std__g~1 28 0.000 0.000 0.000 0.000 0.000 0.000
## lin_slo SD_nonlin
## 1 0.000 0.000
## 2 0.000 0.000
## 3 0.000 0.000
## 4 0.000 0.000
## 5 -0.266 0.008
## 6 -0.133 0.003
## 7 0.024 0.012
## 8 0.064 0.006
## 9 0.009 0.003
## 10 0.000 0.000
## 11 0.000 0.000
## 12 0.000 0.000
## 13 0.000 0.000
## 14 0.000 0.000
## 15 0.090 0.002
## 16 0.057 0.002
## 17 -0.006 0.004
## 18 -0.013 0.001
## 19 -0.114 0.003
## 20 -0.093 0.003
## 21 0.009 0.006
## 22 0.014 0.001
## 23 0.000 0.000
## 24 0.000 0.000
## 25 0.000 0.000
## 26 0.000 0.000
## 27 0.000 0.000
## 28 0.000 0.000
##
## Distribution of Moderator: Density and Effective Sample Size
##
## M=0.062 | SD=0.851
##
## moderator wgt Neff
## 1 -0.246 0.100 580.209
## 2 0.070 0.167 848.175
## 3 0.264 0.233 945.139
## 4 0.453 0.259 942.705
## 5 0.619 0.241 853.379
##
## variable M SD min max
## 1 moderator 0.062 0.851 -4.930 1.448
## 2 wgt 0.200 0.066 0.100 0.259
## 3 Neff 833.921 149.294 580.209 945.139
## NULL
# ==============================================================
# 12. Helper: WLS with Within-Model Standardization
# ==============================================================
run_model <- function(formula, data, label) {
all_vars <- all.vars(formula)
var_map <- c(
"g" = "g_raw",
"PC1A_z" = "K5PC1A",
"age_c" = "age",
"ave_WAIS_res_z" = "ave_WAIS_raw",
"M_WAIS_z" = "M_WAIS_res",
"F_WAIS_z" = "F_WAIS_res",
"SES" = "SES_raw"
)
raw_vars <- all_vars
for (i in seq_along(raw_vars)) {
if (raw_vars[i] %in% names(var_map)) {
raw_vars[i] <- var_map[raw_vars[i]]
}
}
d_complete <- data[complete.cases(data[, raw_vars]) & !is.na(data$wt), ]
if ("PC1A_z" %in% all_vars) d_complete$PC1A_z <- as.vector(scale(d_complete$K5PC1A))
if ("g" %in% all_vars) d_complete$g <- as.vector(scale(d_complete$g_raw))
if ("age_c" %in% all_vars) d_complete$age_c <- as.vector(scale(d_complete$age))
if ("ave_WAIS_res_z" %in% all_vars) d_complete$ave_WAIS_res_z <- as.vector(scale(d_complete$ave_WAIS_raw))
if ("M_WAIS_z" %in% all_vars) d_complete$M_WAIS_z <- as.vector(scale(d_complete$M_WAIS_res))
if ("F_WAIS_z" %in% all_vars) d_complete$F_WAIS_z <- as.vector(scale(d_complete$F_WAIS_res))
if ("SES" %in% all_vars) d_complete$SES <- as.vector(scale(d_complete$SES_raw))
for (v in c("PPVT", "WJ9", "WJ10")) {
if (v %in% all_vars && v %in% names(d_complete)) {
d_complete[[v]] <- as.vector(scale(d_complete[[v]]))
}
}
model <- lm(formula, data = d_complete, weights = wt)
robust <- coeftest(model, vcov = vcovHC(model, type = "HC1"))
cat("\n", paste(rep("=", 80), collapse = ""), "\n")
cat(label, "\n")
cat("R2 =", round(summary(model)$r.squared, 4),
" Adj-R2 =", round(summary(model)$adj.r.squared, 4), "\n")
cat(paste(rep("=", 80), collapse = ""), "\n")
cat(sprintf("%20s %9s %8s %8s %10s\n", "Variable", "B", "SE", "t", "p"))
cat(paste(rep("-", 60), collapse = ""), "\n")
for (i in 1:nrow(robust)) {
var_name <- rownames(robust)[i]
b <- robust[i, 1]; se <- robust[i, 2]; t <- robust[i, 3]; p <- robust[i, 4]
p_str <- ifelse(p >= 0.0001, sprintf("%.4f", p), sprintf("%.2e", p))
cat(sprintf("%20s %+9.4f %8.4f %+8.3f %10s\n", var_name, b, se, t, p_str))
}
cat("N =", nrow(d_complete), "\n")
invisible(model)
}
# ==============================================================
# 13. APA: Child g Models
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("AFRO-PAN-AMERICAN: Child g as DV\n")
## AFRO-PAN-AMERICAN: Child g as DV
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c, d_apa,
"APA M1: PC1A + SIRE + Generation + Demographics")
##
## ================================================================================
## APA M1: PC1A + SIRE + Generation + Demographics
## R2 = 0.0192 Adj-R2 = 0.014
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0047 0.0553 +0.085 0.9321
## PC1A_z -0.1285 0.0475 -2.704 0.0069
## sireBW -0.3033 0.2118 -1.432 0.1524
## sireBH +0.1106 0.1858 +0.595 0.5518
## sireBO +0.0045 0.1437 +0.031 0.9751
## gen_2ndTRUE +0.4228 0.2187 +1.933 0.0534
## gen_3rdTRUE +0.2489 0.2398 +1.038 0.2994
## sex -0.0099 0.0678 -0.146 0.8838
## age_c -0.0179 0.0307 -0.583 0.5598
## N = 1538
run_model(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c + ave_WAIS_res_z, d_apa,
"APA M2: + Parental WAIS")
##
## ================================================================================
## APA M2: + Parental WAIS
## R2 = 0.0394 Adj-R2 = 0.0334
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0101 0.0575 +0.175 0.8609
## PC1A_z -0.1026 0.0481 -2.132 0.0332
## sireBW -0.3063 0.2211 -1.385 0.1661
## sireBH +0.1611 0.2112 +0.763 0.4458
## sireBO +0.0937 0.1385 +0.676 0.4990
## gen_2ndTRUE +0.4597 0.3040 +1.512 0.1307
## gen_3rdTRUE +0.3203 0.2457 +1.304 0.1926
## sex -0.0164 0.0712 -0.230 0.8181
## age_c -0.0205 0.0314 -0.654 0.5132
## ave_WAIS_res_z +0.1515 0.0345 +4.393 1.20e-05
## N = 1450
run_model(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c + ave_WAIS_res_z + single + SES, d_apa,
"APA M3: + SES + Single Parent")
##
## ================================================================================
## APA M3: + SES + Single Parent
## R2 = 0.1358 Adj-R2 = 0.1292
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0054 0.0698 -0.078 0.9379
## PC1A_z -0.0798 0.0471 -1.694 0.0904
## sireBW -0.1868 0.2134 -0.875 0.3816
## sireBH +0.3399 0.1940 +1.753 0.0799
## sireBO +0.1100 0.1284 +0.856 0.3920
## gen_2ndTRUE +0.2873 0.2515 +1.143 0.2534
## gen_3rdTRUE +0.2694 0.2695 +1.000 0.3176
## sex +0.0062 0.0671 +0.093 0.9260
## age_c -0.0051 0.0299 -0.169 0.8655
## ave_WAIS_res_z +0.0698 0.0345 +2.021 0.0434
## single -0.0442 0.0642 -0.688 0.4916
## SES +0.3178 0.0312 +10.201 1.23e-23
## N = 1450
# ==============================================================
# 13b. Unweighted Model Sensitivity
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("ROBUSTNESS: Unweighted models\n")
## ROBUSTNESS: Unweighted models
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
d_uw <- d_apa[complete.cases(d_apa[, c("g_raw", "K5PC1A", "sire",
"gen_2nd", "gen_3rd", "sex", "age")]), ]
d_uw$PC1A_z <- as.vector(scale(d_uw$K5PC1A))
d_uw$g <- as.vector(scale(d_uw$g_raw))
d_uw$age_c <- as.vector(scale(d_uw$age))
m_uw <- lm(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c, data = d_uw)
robust_uw <- coeftest(m_uw, vcov = vcovHC(m_uw, type = "HC1"))
cat("APA unweighted M1: PC1A coefficient:\n")
## APA unweighted M1: PC1A coefficient:
print(robust_uw["PC1A_z", ])
## Estimate Std. Error t value Pr(>|t|)
## -0.118535111 0.037024865 -3.201500168 0.001395249
cat("N =", nrow(d_uw), "\n")
## N = 1538
# ==============================================================
# 14. APA: Average Parental WAIS as DV (excl BW)
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("APA (excl BW): Parental WAIS as DV\n")
## APA (excl BW): Parental WAIS as DV
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(ave_WAIS_res_z ~ PC1A_z + sire + gen_2nd + gen_3rd, d_apa_noBW,
"APA WAIS M1: PC1A + SIRE + Generation")
##
## ================================================================================
## APA WAIS M1: PC1A + SIRE + Generation
## R2 = 0.0151 Adj-R2 = 0.0116
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0558 0.0392 +1.423 0.1549
## PC1A_z -0.1165 0.0358 -3.259 0.0011
## sireBH -0.2848 0.1385 -2.057 0.0399
## sireBO -0.2225 0.1519 -1.465 0.1433
## gen_2ndTRUE +0.0986 0.1309 +0.754 0.4512
## gen_3rdTRUE -0.4637 0.2154 -2.153 0.0315
## N = 1393
# ==============================================================
# 15. APA: Mother & Father WAIS separately (full APA, parent self-ID)
# ==============================================================
# Use parent's own racial self-identification, not couple SIRE.
# Full APA sample (including BW) — child ancestry is the predictor,
# so both parents' WAIS scores are informative in their own models.
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("APA: Mother & Father WAIS separately (full sample, parent self-ID)\n")
## APA: Mother & Father WAIS separately (full sample, parent self-ID)
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(M_WAIS_z ~ PC1A_z + mother_black_sire + gen_2nd + gen_3rd, d_apa,
"APA Mother WAIS M1 (full sample, parent self-ID)")
##
## ================================================================================
## APA Mother WAIS M1 (full sample, parent self-ID)
## R2 = 0.0102 Adj-R2 = 0.0074
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0564 0.1570 +0.359 0.7194
## PC1A_z -0.1002 0.0494 -2.028 0.0428
## mother_black_sire -0.0273 0.1663 -0.164 0.8697
## gen_2ndTRUE +0.0956 0.2313 +0.413 0.6794
## gen_3rdTRUE -0.0389 0.3212 -0.121 0.9035
## N = 1410
run_model(F_WAIS_z ~ PC1A_z + father_black_sire + gen_2nd + gen_3rd, d_apa,
"APA Father WAIS M1 (full sample, parent self-ID)")
##
## ================================================================================
## APA Father WAIS M1 (full sample, parent self-ID)
## R2 = 0.0116 Adj-R2 = 0.0079
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0502 0.1120 +0.448 0.6541
## PC1A_z -0.0816 0.0315 -2.590 0.0097
## father_black_sire -0.0259 0.1179 -0.220 0.8260
## gen_2ndTRUE -0.0114 0.1315 -0.086 0.9311
## gen_3rdTRUE -0.7400 0.3857 -1.919 0.0553
## N = 1076
# ==============================================================
# 16. APA: SES as DV (excl BW)
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("APA (excl BW): SES as DV\n")
## APA (excl BW): SES as DV
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(SES ~ PC1A_z + sire + gen_2nd + gen_3rd, d_apa_noBW,
"APA SES M1")
##
## ================================================================================
## APA SES M1
## R2 = 0.0228 Adj-R2 = 0.0195
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0701 0.0359 +1.953 0.0510
## PC1A_z -0.0980 0.0354 -2.768 0.0057
## sireBH -0.5652 0.1762 -3.208 0.0014
## sireBO -0.1239 0.1089 -1.138 0.2554
## gen_2ndTRUE +0.5087 0.1679 +3.030 0.0025
## gen_3rdTRUE +0.0188 0.1853 +0.101 0.9194
## N = 1477
# ==============================================================
# 17. APA: Individual Tests (Model 1 spec)
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("APA: Individual Tests (Model 1 spec)\n")
## APA: Individual Tests (Model 1 spec)
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(PPVT ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c, d_apa, "APA: PPVT")
##
## ================================================================================
## APA: PPVT
## R2 = 0.0243 Adj-R2 = 0.0192
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0165 0.0526 -0.314 0.7537
## PC1A_z -0.1618 0.0508 -3.188 0.0015
## sireBW -0.3483 0.2267 -1.537 0.1246
## sireBH +0.1403 0.2376 +0.590 0.5551
## sireBO -0.0264 0.1595 -0.166 0.8684
## gen_2ndTRUE +0.3827 0.2128 +1.799 0.0723
## gen_3rdTRUE +0.0645 0.3968 +0.163 0.8709
## sex +0.0210 0.0661 +0.319 0.7501
## age_c -0.0286 0.0316 -0.905 0.3658
## N = 1531
run_model(WJ9 ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c, d_apa, "APA: WJ Applied Problems")
##
## ================================================================================
## APA: WJ Applied Problems
## R2 = 0.0315 Adj-R2 = 0.0264
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.1509 0.0538 -2.803 0.0051
## PC1A_z -0.0791 0.0452 -1.750 0.0803
## sireBW -0.2980 0.2103 -1.417 0.1567
## sireBH -0.0106 0.1556 -0.068 0.9456
## sireBO +0.0998 0.1559 +0.640 0.5221
## gen_2ndTRUE +0.2954 0.1849 +1.597 0.1104
## gen_3rdTRUE +0.1111 0.1836 +0.605 0.5453
## sex +0.3031 0.0641 +4.731 2.44e-06
## age_c -0.0496 0.0306 -1.618 0.1059
## N = 1523
run_model(WJ10 ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c, d_apa, "APA: WJ Letter-Word")
##
## ================================================================================
## APA: WJ Letter-Word
## R2 = 0.0222 Adj-R2 = 0.0171
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0372 0.0569 -0.655 0.5128
## PC1A_z -0.1014 0.0438 -2.315 0.0208
## sireBW -0.1976 0.1991 -0.992 0.3211
## sireBH +0.0681 0.1506 +0.452 0.6511
## sireBO -0.0490 0.1343 -0.365 0.7151
## gen_2ndTRUE +0.3742 0.2112 +1.772 0.0766
## gen_3rdTRUE +0.4718 0.1862 +2.533 0.0114
## sex +0.1105 0.0686 +1.612 0.1072
## age_c -0.0881 0.0293 -3.004 0.0027
## N = 1524
# ==============================================================
# 18. HAA: Child g Models
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("HISTORICAL AFRICAN AMERICAN: Child g as DV\n")
## HISTORICAL AFRICAN AMERICAN: Child g as DV
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(g ~ PC1A_z + sex + age_c, d_haa, "HAA M1: PC1A + Demographics")
##
## ================================================================================
## HAA M1: PC1A + Demographics
## R2 = 0.009 Adj-R2 = 0.0067
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0025 0.0588 +0.042 0.9664
## PC1A_z -0.1014 0.0317 -3.199 0.0014
## sex +0.0268 0.0726 +0.369 0.7121
## age_c +0.0067 0.0334 +0.199 0.8423
## N = 1287
run_model(g ~ PC1A_z + sex + age_c + ave_WAIS_res_z, d_haa, "HAA M2: + Parental WAIS")
##
## ================================================================================
## HAA M2: + Parental WAIS
## R2 = 0.036 Adj-R2 = 0.0329
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0143 0.0613 -0.234 0.8152
## PC1A_z -0.0936 0.0327 -2.862 0.0043
## sex +0.0559 0.0757 +0.739 0.4599
## age_c +0.0043 0.0336 +0.128 0.8985
## ave_WAIS_res_z +0.1675 0.0368 +4.547 5.99e-06
## N = 1234
run_model(g ~ PC1A_z + sex + age_c + ave_WAIS_res_z + single + SES, d_haa, "HAA M3: + SES + Single")
##
## ================================================================================
## HAA M3: + SES + Single
## R2 = 0.1212 Adj-R2 = 0.1169
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0080 0.0774 -0.104 0.9173
## PC1A_z -0.0788 0.0323 -2.437 0.0150
## sex +0.0606 0.0731 +0.829 0.4074
## age_c +0.0196 0.0329 +0.596 0.5510
## ave_WAIS_res_z +0.0880 0.0365 +2.412 0.0160
## single -0.0620 0.0710 -0.873 0.3826
## SES +0.2917 0.0300 +9.731 1.32e-21
## N = 1234
# ==============================================================
# 19. HAA: Parental WAIS as DV
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("HAA: Parental WAIS as DV\n")
## HAA: Parental WAIS as DV
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(ave_WAIS_res_z ~ PC1A_z, d_haa, "HAA WAIS M1")
##
## ================================================================================
## HAA WAIS M1
## R2 = 0.0091 Adj-R2 = 0.0083
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0456 0.0399 +1.144 0.2530
## PC1A_z -0.0979 0.0389 -2.514 0.0121
## N = 1238
# ==============================================================
# 20. HAA: SES as DV
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("HAA: SES as DV\n")
## HAA: SES as DV
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(SES ~ PC1A_z, d_haa, "HAA SES M1")
##
## ================================================================================
## HAA SES M1
## R2 = 0.0062 Adj-R2 = 0.0055
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0787 0.0371 +2.122 0.0340
## PC1A_z -0.0836 0.0400 -2.090 0.0368
## N = 1292
# ==============================================================
# 21. HAA: Individual Tests (Model 1 spec)
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("HAA: Individual Tests (Model 1 spec)\n")
## HAA: Individual Tests (Model 1 spec)
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(PPVT ~ PC1A_z + sex + age_c, d_haa, "HAA: PPVT")
##
## ================================================================================
## HAA: PPVT
## R2 = 0.0158 Adj-R2 = 0.0135
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0442 0.0558 -0.791 0.4290
## PC1A_z -0.1229 0.0317 -3.873 0.0001
## sex +0.1079 0.0719 +1.501 0.1337
## age_c -0.0139 0.0347 -0.400 0.6893
## N = 1281
run_model(WJ9 ~ PC1A_z + sex + age_c, d_haa, "HAA: WJ Applied Problems")
##
## ================================================================================
## HAA: WJ Applied Problems
## R2 = 0.0233 Adj-R2 = 0.021
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.1436 0.0565 -2.544 0.0111
## PC1A_z -0.0578 0.0321 -1.803 0.0717
## sex +0.3011 0.0689 +4.372 1.33e-05
## age_c -0.0193 0.0332 -0.579 0.5625
## N = 1273
run_model(WJ10 ~ PC1A_z + sex + age_c, d_haa, "HAA: WJ Letter-Word")
##
## ================================================================================
## HAA: WJ Letter-Word
## R2 = 0.013 Adj-R2 = 0.0107
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0305 0.0605 -0.504 0.6145
## PC1A_z -0.0788 0.0311 -2.534 0.0114
## sex +0.1241 0.0740 +1.677 0.0937
## age_c -0.0770 0.0317 -2.426 0.0154
## N = 1277
# ==============================================================
# 21b. Formal Mediation: PC1A -> Child g -> SES
# ==============================================================
# APA (excl BW): PC1A -> g -> SES
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("MEDIATION: PC1A -> Child g -> SES\n")
## MEDIATION: PC1A -> Child g -> SES
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
# Prepare APA_noBW data (SES models exclude BW)
d_med_apa <- d_apa_noBW[complete.cases(d_apa_noBW[, c(
"g_raw", "K5PC1A", "SES_raw", "sire", "gen_2nd", "gen_3rd", "sex", "age"
)]) & !is.na(d_apa_noBW$wt), ]
d_med_apa$PC1A_z <- as.vector(scale(d_med_apa$K5PC1A))
d_med_apa$g <- as.vector(scale(d_med_apa$g_raw))
d_med_apa$SES <- as.vector(scale(d_med_apa$SES_raw))
d_med_apa$age_c <- as.vector(scale(d_med_apa$age))
# Model M: PC1A -> g (mediator model)
med_M_apa <- lm(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_apa, weights = wt)
# Model Y: g -> SES, controlling for PC1A (outcome model)
out_Y_apa <- lm(SES ~ g + PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_apa, weights = wt)
# Mediation
set.seed(99999)
med_apa <- mediate(med_M_apa, out_Y_apa, treat = "PC1A_z", mediator = "g",
boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nAPA (excl BW): PC1A -> g -> SES\n")
##
## APA (excl BW): PC1A -> g -> SES
summary(med_apa)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.0294316 -0.0522345 -0.0081046 0.006 **
## ADE -0.0647590 -0.1340491 0.0076539 0.078 .
## Total Effect -0.0941907 -0.1643797 -0.0158924 0.018 *
## Prop. Mediated 0.3124687 0.0761676 1.1046894 0.020 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1472
##
##
## Simulations: 1000
# HAA: PC1A -> g -> SES
d_med_haa <- d_haa[complete.cases(d_haa[, c(
"g_raw", "K5PC1A", "SES_raw", "sex", "age"
)]) & !is.na(d_haa$wt), ]
d_med_haa$PC1A_z <- as.vector(scale(d_med_haa$K5PC1A))
d_med_haa$g <- as.vector(scale(d_med_haa$g_raw))
d_med_haa$SES <- as.vector(scale(d_med_haa$SES_raw))
d_med_haa$age_c <- as.vector(scale(d_med_haa$age))
med_M_haa <- lm(g ~ PC1A_z + sex + age_c, data = d_med_haa, weights = wt)
out_Y_haa <- lm(SES ~ g + PC1A_z + sex + age_c, data = d_med_haa, weights = wt)
set.seed(99999)
med_haa <- mediate(med_M_haa, out_Y_haa, treat = "PC1A_z", mediator = "g",
boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nHAA: PC1A -> g -> SES\n")
##
## HAA: PC1A -> g -> SES
summary(med_haa)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.0321070 -0.0533454 -0.0111253 0.002 **
## ADE -0.0460194 -0.1230032 0.0376638 0.254
## Total Effect -0.0781264 -0.1559398 0.0051217 0.060 .
## Prop. Mediated 0.4109626 -0.4618822 2.5562452 0.062 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1287
##
##
## Simulations: 1000
# Also test with PVT (adolescent verbal ability analogue)
# For temporal ordering: PVT at age 9 predicts SES at age 9
d_med_apa2 <- d_apa_noBW[complete.cases(d_apa_noBW[, c(
"PPVT", "K5PC1A", "SES_raw", "sire", "gen_2nd", "gen_3rd", "sex", "age"
)]) & !is.na(d_apa_noBW$wt), ]
d_med_apa2$PC1A_z <- as.vector(scale(d_med_apa2$K5PC1A))
d_med_apa2$PPVT_z <- as.vector(scale(d_med_apa2$PPVT))
d_med_apa2$SES <- as.vector(scale(d_med_apa2$SES_raw))
d_med_apa2$age_c <- as.vector(scale(d_med_apa2$age))
med_M_apa2 <- lm(PPVT_z ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_apa2, weights = wt)
out_Y_apa2 <- lm(SES ~ PPVT_z + PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_apa2, weights = wt)
set.seed(99999)
med_apa2 <- mediate(med_M_apa2, out_Y_apa2, treat = "PC1A_z",
mediator = "PPVT_z", boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nAPA (excl BW): PC1A -> PPVT -> SES\n")
##
## APA (excl BW): PC1A -> PPVT -> SES
summary(med_apa2)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.036926 -0.064245 -0.012995 <2e-16 ***
## ADE -0.057619 -0.125074 0.011993 0.110
## Total Effect -0.094545 -0.162477 -0.022694 0.012 *
## Prop. Mediated 0.390567 0.122464 1.175160 0.012 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1466
##
##
## Simulations: 1000
# WJ9 mediation (check whether the mediation is driven by verbal ability or is general across cognitive domains)
d_med_wj9 <- d_apa_noBW[complete.cases(d_apa_noBW[, c(
"WJ9", "K5PC1A", "SES_raw", "sire", "gen_2nd", "gen_3rd", "sex", "age"
)]) & !is.na(d_apa_noBW$wt), ]
d_med_wj9$PC1A_z <- as.vector(scale(d_med_wj9$K5PC1A))
d_med_wj9$WJ9_z <- as.vector(scale(d_med_wj9$WJ9))
d_med_wj9$SES <- as.vector(scale(d_med_wj9$SES_raw))
d_med_wj9$age_c <- as.vector(scale(d_med_wj9$age))
med_M_wj9 <- lm(WJ9_z ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_wj9, weights = wt)
out_Y_wj9 <- lm(SES ~ WJ9_z + PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_wj9, weights = wt)
set.seed(99999)
med_wj9 <- mediate(med_M_wj9, out_Y_wj9, treat = "PC1A_z",
mediator = "WJ9_z", boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nAPA (excl BW): PC1A -> WJ9 -> SES\n")
##
## APA (excl BW): PC1A -> WJ9 -> SES
summary(med_wj9)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.01515051 -0.03143462 0.00015771 0.056 .
## ADE -0.07939725 -0.15599497 -0.01320386 0.024 *
## Total Effect -0.09454775 -0.17160220 -0.02401644 0.006 **
## Prop. Mediated 0.16024184 -0.01010715 0.55842552 0.062 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1457
##
##
## Simulations: 1000
# WJ10 mediation
d_med_wj10 <- d_apa_noBW[complete.cases(d_apa_noBW[, c(
"WJ10", "K5PC1A", "SES_raw", "sire", "gen_2nd", "gen_3rd", "sex", "age"
)]) & !is.na(d_apa_noBW$wt), ]
d_med_wj10$PC1A_z <- as.vector(scale(d_med_wj10$K5PC1A))
d_med_wj10$WJ10_z <- as.vector(scale(d_med_wj10$WJ10))
d_med_wj10$SES <- as.vector(scale(d_med_wj10$SES_raw))
d_med_wj10$age_c <- as.vector(scale(d_med_wj10$age))
med_M_wj10 <- lm(WJ10_z ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_wj10, weights = wt)
out_Y_wj10 <- lm(SES ~ WJ10_z + PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_wj10, weights = wt)
set.seed(99999)
med_wj10 <- mediate(med_M_wj10, out_Y_wj10, treat = "PC1A_z",
mediator = "WJ10_z", boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nAPA (excl BW): PC1A -> WJ10 -> SES\n")
##
## APA (excl BW): PC1A -> WJ10 -> SES
summary(med_wj10)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.0206058 -0.0389887 -0.0040922 0.014 *
## ADE -0.0720674 -0.1385549 -0.0073242 0.036 *
## Total Effect -0.0926732 -0.1624166 -0.0248395 0.012 *
## Prop. Mediated 0.2223486 0.0387608 0.6955832 0.022 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1458
##
##
## Simulations: 1000
# ==============================================================
# 21c. Mediation: PC1A -> Parental WAIS -> Child g
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("MEDIATION: PC1A -> Parental WAIS -> Child g\n")
## MEDIATION: PC1A -> Parental WAIS -> Child g
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
# APA (exclude BW)
d_med_pw_apa <- d_apa_noBW[complete.cases(d_apa_noBW[, c(
"g_raw", "K5PC1A", "ave_WAIS_raw", "sire", "gen_2nd", "gen_3rd", "sex", "age"
)]) & !is.na(d_apa_noBW$wt), ]
d_med_pw_apa$PC1A_z <- as.vector(scale(d_med_pw_apa$K5PC1A))
d_med_pw_apa$g <- as.vector(scale(d_med_pw_apa$g_raw))
d_med_pw_apa$W <- as.vector(scale(d_med_pw_apa$ave_WAIS_raw))
d_med_pw_apa$age_c <- as.vector(scale(d_med_pw_apa$age))
# M: PC1A -> parental WAIS
med_M_pw_apa <- lm(W ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_pw_apa, weights = wt)
# Y: parental WAIS -> g, controlling for PC1A
out_Y_pw_apa <- lm(g ~ W + PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_med_pw_apa, weights = wt)
set.seed(88888)
med_pw_apa <- mediate(med_M_pw_apa, out_Y_pw_apa, treat = "PC1A_z",
mediator = "W", boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nAPA (excl BW): PC1A -> Parental WAIS -> Child g\n")
##
## APA (excl BW): PC1A -> Parental WAIS -> Child g
summary(med_pw_apa)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.0168859 -0.0319882 -0.0049938 0.002 **
## ADE -0.0716505 -0.1363979 -0.0028324 0.038 *
## Total Effect -0.0885363 -0.1568614 -0.0176233 0.018 *
## Prop. Mediated 0.1907226 0.0522423 0.7133941 0.020 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1389
##
##
## Simulations: 1000
# HAA
d_med_pw_haa <- d_haa[complete.cases(d_haa[, c(
"g_raw", "K5PC1A", "ave_WAIS_raw", "sex", "age"
)]) & !is.na(d_haa$wt), ]
d_med_pw_haa$PC1A_z <- as.vector(scale(d_med_pw_haa$K5PC1A))
d_med_pw_haa$g <- as.vector(scale(d_med_pw_haa$g_raw))
d_med_pw_haa$W <- as.vector(scale(d_med_pw_haa$ave_WAIS_raw))
d_med_pw_haa$age_c <- as.vector(scale(d_med_pw_haa$age))
med_M_pw_haa <- lm(W ~ PC1A_z + sex + age_c, data = d_med_pw_haa, weights = wt)
out_Y_pw_haa <- lm(g ~ W + PC1A_z + sex + age_c, data = d_med_pw_haa, weights = wt)
set.seed(88888)
med_pw_haa <- mediate(med_M_pw_haa, out_Y_pw_haa, treat = "PC1A_z",
mediator = "W", boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
cat("\nHAA: PC1A -> Parental WAIS -> Child g\n")
##
## HAA: PC1A -> Parental WAIS -> Child g
summary(med_pw_haa)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.0143167 -0.0284416 -0.0019552 0.028 *
## ADE -0.0935652 -0.1586100 -0.0265247 0.008 **
## Total Effect -0.1078818 -0.1758379 -0.0428944 <2e-16 ***
## Prop. Mediated 0.1327068 0.0168790 0.3966624 0.028 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 1234
##
##
## Simulations: 1000
# ==============================================================
# 21d. Non-Linearity Test: Quadratic Ancestry Term
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("ROBUSTNESS: Quadratic ancestry term\n")
## ROBUSTNESS: Quadratic ancestry term
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
run_model(g ~ PC1A_z + I(PC1A_z^2) + sire + gen_2nd + gen_3rd + sex + age_c,
d_apa, "APA: g ~ PC1A + PC1A² + controls")
##
## ================================================================================
## APA: g ~ PC1A + PC1A² + controls
## R2 = 0.02 Adj-R2 = 0.0142
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0086 0.0578 -0.149 0.8816
## PC1A_z -0.1035 0.0566 -1.828 0.0678
## I(PC1A_z^2) +0.0230 0.0268 +0.856 0.3922
## sireBW -0.4721 0.2760 -1.710 0.0874
## sireBH +0.1028 0.1905 +0.539 0.5896
## sireBO -0.0091 0.1398 -0.065 0.9480
## gen_2ndTRUE +0.4123 0.2185 +1.887 0.0594
## gen_3rdTRUE +0.2570 0.2391 +1.075 0.2826
## sex -0.0121 0.0679 -0.179 0.8582
## age_c -0.0195 0.0309 -0.632 0.5275
## N = 1538
run_model(g ~ PC1A_z + I(PC1A_z^2) + sex + age_c,
d_haa, "HAA: g ~ PC1A + PC1A² + controls")
##
## ================================================================================
## HAA: g ~ PC1A + PC1A² + controls
## R2 = 0.009 Adj-R2 = 0.0059
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0000 0.0632 -0.000 0.9999
## PC1A_z -0.0978 0.0433 -2.260 0.0240
## I(PC1A_z^2) +0.0028 0.0159 +0.175 0.8611
## sex +0.0262 0.0721 +0.364 0.7161
## age_c +0.0061 0.0335 +0.182 0.8557
## N = 1287
# Also for PPVT (strongest individual test effect)
run_model(PPVT ~ PC1A_z + I(PC1A_z^2) + sire + gen_2nd + gen_3rd + sex + age_c,
d_apa, "APA: PPVT ~ PC1A + PC1A² + controls")
##
## ================================================================================
## APA: PPVT ~ PC1A + PC1A² + controls
## R2 = 0.0262 Adj-R2 = 0.0205
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0360 0.0559 -0.644 0.5194
## PC1A_z -0.1250 0.0551 -2.270 0.0233
## I(PC1A_z^2) +0.0335 0.0332 +1.011 0.3122
## sireBW -0.5960 0.3582 -1.664 0.0963
## sireBH +0.1288 0.2472 +0.521 0.6023
## sireBO -0.0463 0.1560 -0.297 0.7666
## gen_2ndTRUE +0.3674 0.2137 +1.720 0.0857
## gen_3rdTRUE +0.0764 0.3956 +0.193 0.8470
## sex +0.0178 0.0660 +0.269 0.7877
## age_c -0.0310 0.0316 -0.980 0.3271
## N = 1531
# ==============================================================
# 21e. Sex-stratified models
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("ROBUSTNESS: Sex-stratified ancestry models\n")
## ROBUSTNESS: Sex-stratified ancestry models
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
# APA: Interaction
run_model(g ~ PC1A_z * sex + sire + gen_2nd + gen_3rd + age_c,
d_apa, "APA: g ~ PC1A × Sex interaction")
##
## ================================================================================
## APA: g ~ PC1A × Sex interaction
## R2 = 0.0214 Adj-R2 = 0.0156
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0028 0.0553 +0.051 0.9595
## PC1A_z -0.1767 0.0591 -2.989 0.0028
## sex -0.0130 0.0676 -0.192 0.8477
## sireBW -0.2899 0.2108 -1.375 0.1692
## sireBH +0.1080 0.1788 +0.604 0.5459
## sireBO +0.0014 0.1417 +0.010 0.9919
## gen_2ndTRUE +0.4424 0.2176 +2.033 0.0422
## gen_3rdTRUE +0.2447 0.2368 +1.033 0.3016
## age_c -0.0183 0.0306 -0.598 0.5498
## PC1A_z:sex +0.1021 0.0598 +1.707 0.0880
## N = 1538
# APA: Males only
run_model(g ~ PC1A_z + sire + gen_2nd + gen_3rd + age_c,
d_apa[d_apa$sex == 0, ], "APA Males: g ~ PC1A + controls")
##
## ================================================================================
## APA Males: g ~ PC1A + controls
## R2 = 0.0359 Adj-R2 = 0.0272
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0265 0.0549 +0.483 0.6291
## PC1A_z -0.2103 0.0648 -3.244 0.0012
## sireBW -0.5919 0.2825 -2.095 0.0365
## sireBH +0.2727 0.1843 +1.480 0.1394
## sireBO -0.0898 0.2348 -0.383 0.7021
## gen_2ndTRUE +0.4819 0.2601 +1.853 0.0643
## gen_3rdTRUE -0.0487 0.0691 -0.705 0.4813
## age_c +0.0294 0.0447 +0.659 0.5103
## N = 782
# APA: Females only
run_model(g ~ PC1A_z + sire + gen_2nd + gen_3rd + age_c,
d_apa[d_apa$sex == 1, ], "APA Females: g ~ PC1A + controls")
##
## ================================================================================
## APA Females: g ~ PC1A + controls
## R2 = 0.017 Adj-R2 = 0.0078
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0354 0.0445 -0.795 0.4268
## PC1A_z -0.0335 0.0586 -0.571 0.5679
## sireBW +0.0577 0.2752 +0.210 0.8340
## sireBH -0.2370 0.2470 -0.960 0.3376
## sireBO +0.0977 0.1831 +0.534 0.5937
## gen_2ndTRUE +0.3725 0.3721 +1.001 0.3171
## gen_3rdTRUE +0.4018 0.3497 +1.149 0.2510
## age_c -0.0768 0.0414 -1.853 0.0643
## N = 756
# HAA: Interaction
run_model(g ~ PC1A_z * sex + age_c,
d_haa, "HAA: g ~ PC1A × Sex interaction")
##
## ================================================================================
## HAA: g ~ PC1A × Sex interaction
## R2 = 0.0102 Adj-R2 = 0.0071
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0001 0.0588 -0.001 0.9990
## PC1A_z -0.1358 0.0463 -2.934 0.0034
## sex +0.0239 0.0725 +0.330 0.7417
## age_c +0.0053 0.0333 +0.159 0.8736
## PC1A_z:sex +0.0753 0.0621 +1.213 0.2255
## N = 1287
# HAA: Males only
run_model(g ~ PC1A_z + age_c,
d_haa[d_haa$sex == 0, ], "HAA Males: g ~ PC1A + controls")
##
## ================================================================================
## HAA Males: g ~ PC1A + controls
## R2 = 0.0157 Adj-R2 = 0.0127
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) +0.0273 0.0547 +0.498 0.6183
## PC1A_z -0.1263 0.0429 -2.946 0.0033
## age_c +0.0491 0.0500 +0.981 0.3270
## N = 654
# HAA: Females only
run_model(g ~ PC1A_z + age_c,
d_haa[d_haa$sex == 1, ], "HAA Females: g ~ PC1A + controls")
##
## ================================================================================
## HAA Females: g ~ PC1A + controls
## R2 = 0.0057 Adj-R2 = 0.0026
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0026 0.0454 -0.057 0.9543
## PC1A_z -0.0628 0.0448 -1.401 0.1617
## age_c -0.0463 0.0427 -1.084 0.2787
## N = 633
# ==============================================================
# 21f. Complete-case sensitivity check
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("ROBUSTNESS: Complete-case analysis (no imputation)\n")
## ROBUSTNESS: Complete-case analysis (no imputation)
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
# Identify cases with complete raw (non-imputed) data
d_apa_cc <- d_apa[!is.na(d_apa$PPVT) & !is.na(d_apa$WJ9) & !is.na(d_apa$WJ10) &
!is.na(d_apa$K5PC1A) & !is.na(d_apa$sex) & !is.na(d_apa$age) &
!is.na(d_apa$wt) & !is.na(d_apa$M_edu) & !is.na(d_apa$ave_log_inc), ]
# Build g score without imputation (PCA on complete cases)
cog_raw <- as.matrix(d_apa_cc[, c("PPVT_r", "WJ9_r", "WJ10_r")])
cog_raw_z <- apply(cog_raw, 2, scale)
pca_cc <- prcomp(cog_raw_z, center = FALSE, scale. = FALSE)
d_apa_cc$g_cc <- as.vector(pca_cc$x[, 1])
d_apa_cc$PC1A_z <- as.vector(scale(d_apa_cc$K5PC1A))
d_apa_cc$g <- as.vector(scale(d_apa_cc$g_cc))
d_apa_cc$age_c <- as.vector(scale(d_apa_cc$age))
m_cc <- lm(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
data = d_apa_cc, weights = wt)
robust_cc <- coeftest(m_cc, vcov = vcovHC(m_cc, type = "HC1"))
cat("APA complete-case: PC1A coefficient:\n")
## APA complete-case: PC1A coefficient:
print(robust_cc["PC1A_z", ])
## Estimate Std. Error t value Pr(>|t|)
## -0.136713314 0.048642753 -2.810558734 0.005009909
cat("N =", nrow(d_apa_cc), "\n")
## N = 1507
# ==============================================================
# 22. Descriptive Table
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("TABLE: Sample Composition\n")
## TABLE: Sample Composition
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
wt_mean <- function(x, w) {
keep <- !is.na(x) & !is.na(w) & w > 0
if (sum(keep) == 0) return(list(m = NA, n = 0))
list(m = weighted.mean(x[keep], w[keep]), n = sum(keep))
}
print_row <- function(label, subset, apa_status, haa_status) {
n <- nrow(subset)
pct <- 100 * n / nrow(d)
pc1a_m <- mean(subset$K5PC1A, na.rm = TRUE)
pc1a_sd <- sd(subset$K5PC1A, na.rm = TRUE)
g_res <- wt_mean(subset$g_raw, subset$wt)
w_res <- wt_mean(subset$ave_WAIS_raw, subset$wt)
g_str <- ifelse(is.na(g_res$m), " --", sprintf("%+.3f", g_res$m))
w_str <- ifelse(is.na(w_res$m), " --", sprintf("%.2f", w_res$m))
sd_str <- ifelse(is.na(pc1a_sd) | n < 2, " --", sprintf("%.4f", pc1a_sd))
cat(sprintf(" %-25s %5d %5.1f%% %+.4f %7s %4s %4s %5d %7s %5d %7s\n",
label, n, pct, pc1a_m, sd_str, apa_status, haa_status,
g_res$n, g_str, w_res$n, w_str))
}
cat(sprintf("\n%-27s %5s %6s %7s %7s %4s %4s %5s %7s %5s %7s\n",
"Category", "N", "%", "PC1A", "SD", "APA", "HAA", "N(g)", "Wt.g", "N(W)", "Wt.W"))
##
## Category N % PC1A SD APA HAA N(g) Wt.g N(W) Wt.W
cat(paste(rep("-", 105), collapse = ""), "\n")
## ---------------------------------------------------------------------------------------------------------
cat("Panel A: Analytic Sample\n")
## Panel A: Analytic Sample
bb_apa <- d[d$any_black_parent & !d$african_origin & d$sire == "BB", ]
print_row("Black x Black", bb_apa, "Yes", "Yes*")
## Black x Black 1418 86.5% +0.0058 0.0128 Yes Yes* 1376 -0.019 1303 0.04
bw_apa <- d[d$any_black_parent & !d$african_origin & d$sire == "BW", ]
print_row("Black x White", bw_apa, "Yes", "Excl")
## Black x White 72 4.4% -0.0671 0.0178 Yes Excl 66 +0.187 62 0.62
bh_apa <- d[d$any_black_parent & !d$african_origin & d$sire == "BH", ]
print_row("Black x Hispanic", bh_apa, "Yes", "Excl")
## Black x Hispanic 32 2.0% -0.0137 0.0304 Yes Excl 32 +0.489 29 -0.26
bo_apa <- d[d$any_black_parent & !d$african_origin & d$sire == "BO", ]
print_row("Black x Other/Missing", bo_apa, "Yes", "Excl")
## Black x Other/Missing 66 4.0% -0.0064 0.0266 Yes Excl 64 +0.174 61 -0.14
cat("\nPanel B: Continental African Origin\n")
##
## Panel B: Continental African Origin
afr <- d[d$any_black_parent & d$african_origin, ]
if (nrow(afr) > 0) print_row("African origin (excl)", afr, "Excl", "Excl")
## African origin (excl) 21 1.3% +0.0031 0.0414 Excl Excl 18 +0.071 11 0.71
cat("\nPanel C: No Black Parent\n")
##
## Panel C: No Black Parent
no_black <- d[!d$any_black_parent, ]
print_row("No Black parent", no_black, "Excl", "Excl")
## No Black parent 30 1.8% -0.0818 0.0418 Excl Excl 28 +0.551 22 0.14
cat(sprintf("\n*HAA = BB 3rd+ gen only: N=%d\n", nrow(d_haa)))
##
## *HAA = BB 3rd+ gen only: N=1323
# ==============================================================
# 23. Regression Plots
# ==============================================================
partial_resid <- function(model, data, xvar) {
r <- residuals(model)
b <- coef(model)[xvar]
r + b * data[[xvar]]
}
plot_ancestry <- function(model, data, xvar, color, ylab, main_title) {
pr <- partial_resid(model, data, xvar)
x <- data[[xvar]]
plot(x, pr, pch = 16, cex = 0.4,
col = adjustcolor("gray30", alpha.f = 0.2),
xlab = "African Ancestry PC (std.)", ylab = ylab, main = main_title)
lo <- loess(pr ~ x, span = 0.75)
x_seq <- seq(min(x), max(x), length.out = 200)
lo_pred <- predict(lo, newdata = data.frame(x = x_seq), se = TRUE)
polygon(c(x_seq, rev(x_seq)),
c(lo_pred$fit + 1.96 * lo_pred$se.fit, rev(lo_pred$fit - 1.96 * lo_pred$se.fit)),
col = adjustcolor(color, alpha.f = 0.15), border = NA)
lines(x_seq, lo_pred$fit, col = color, lwd = 2.5)
abline(lm(pr ~ x), col = color, lwd = 1.5, lty = 2)
b <- coef(model)[xvar]
p <- summary(model)$coefficients[xvar, "Pr(>|t|)"]
p_str <- ifelse(p < 0.001, "p < .001", sprintf("p = %.3f", p))
legend("topright", legend = bquote(beta == .(sprintf("%.3f", b)) ~ "," ~ .(p_str)),
bty = "n", cex = 0.9)
}
d_apa_g <- d_apa[complete.cases(d_apa[, c("g_raw", "K5PC1A", "sire", "gen_2nd", "gen_3rd", "sex", "age")]) & !is.na(d_apa$wt), ]
d_apa_g$g <- as.vector(scale(d_apa_g$g_raw)); d_apa_g$PC1A_z <- as.vector(scale(d_apa_g$K5PC1A)); d_apa_g$age_c <- as.vector(scale(d_apa_g$age))
m_apa_g <- lm(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c, data = d_apa_g, weights = d_apa_g$wt)
d_haa_g <- d_haa[complete.cases(d_haa[, c("g_raw", "K5PC1A", "sex", "age")]) & !is.na(d_haa$wt), ]
d_haa_g$g <- as.vector(scale(d_haa_g$g_raw)); d_haa_g$PC1A_z <- as.vector(scale(d_haa_g$K5PC1A)); d_haa_g$age_c <- as.vector(scale(d_haa_g$age))
m_haa_g <- lm(g ~ PC1A_z + sex + age_c, data = d_haa_g, weights = d_haa_g$wt)
d_apa_w <- d_apa_noBW[complete.cases(d_apa_noBW[, c("ave_WAIS_raw", "K5PC1A", "sire", "gen_2nd", "gen_3rd")]) & !is.na(d_apa_noBW$wt), ]
d_apa_w$ave_WAIS_res_z <- as.vector(scale(d_apa_w$ave_WAIS_raw)); d_apa_w$PC1A_z <- as.vector(scale(d_apa_w$K5PC1A))
m_apa_w <- lm(ave_WAIS_res_z ~ PC1A_z + sire + gen_2nd + gen_3rd, data = d_apa_w, weights = d_apa_w$wt)
d_haa_w <- d_haa[complete.cases(d_haa[, c("ave_WAIS_raw", "K5PC1A")]) & !is.na(d_haa$wt), ]
d_haa_w$ave_WAIS_res_z <- as.vector(scale(d_haa_w$ave_WAIS_raw)); d_haa_w$PC1A_z <- as.vector(scale(d_haa_w$K5PC1A))
m_haa_w <- lm(ave_WAIS_res_z ~ PC1A_z, data = d_haa_w, weights = d_haa_w$wt)
pdf("admixture_regression_plots.pdf", width = 10, height = 8)
par(mfrow = c(2, 2), mar = c(4.5, 4.5, 3, 1.5))
plot_ancestry(m_apa_g, d_apa_g, "PC1A_z", "blue", "Child g (z)", "A. Pan-African American: Ancestry -> Child g")
plot_ancestry(m_haa_g, d_haa_g, "PC1A_z", "blue", "Child g (z)", "B. Historical AA: Ancestry -> Child g")
plot_ancestry(m_apa_w, d_apa_w, "PC1A_z", "darkred", "Parental WAIS (z)", "C. Pan-African American: Ancestry -> Parental WAIS")
plot_ancestry(m_haa_w, d_haa_w, "PC1A_z", "darkred", "Parental WAIS (z)", "D. Historical AA: Ancestry -> Parental WAIS")
dev.off()
## png
## 2
cat("\nPlots saved to: admixture_regression_plots.pdf\n")
##
## Plots saved to: admixture_regression_plots.pdf
#_______________________________________________________________________________________
#Exploratory analysis
# ==============================================================
# Epigenetic Clock Analysis
# ==============================================================
epi <- read_sav("C:/Users/mh198/Documents/Data/FFCWS/DS0008/31622-0008-Data.sav")
# Merge with main dataset
d <- merge(d, epi[, c("IDNUM", "K5MK_POAM45", "K5MK_POAM38",
"K5MK_PHENOAGE", "K5MK_GRIM", "K5MK_HORVATH",
"K5MK_SKINBLOOD", "K5MK_PEDBE", "K5MK_HANNUM",
"K5MK_AGE", "K5MK_BATCH")],
by = "IDNUM", all.x = TRUE)
# Clean: set negative codes to NA
epi_vars <- c("K5MK_POAM45", "K5MK_POAM38", "K5MK_PHENOAGE", "K5MK_GRIM",
"K5MK_HORVATH", "K5MK_SKINBLOOD", "K5MK_PEDBE", "K5MK_HANNUM",
"K5MK_AGE", "K5MK_BATCH")
for (v in epi_vars) {
d[[v]] <- ifelse(d[[v]] < -5, NA, d[[v]])
}
cat("Epigenetic data merged. DunedinPACE available: N =", sum(!is.na(d$K5MK_POAM45)), "\n")
## Epigenetic data merged. DunedinPACE available: N = 485
# Rebuild subsets with epi data
d_apa <- d[d$any_black_parent & !d$african_origin, ]
d_apa$sire <- droplevels(d_apa$sire)
d_apa$mother_black_sire <- as.numeric(d_apa$CM1ETHRACE == 2)
d_apa$father_black_sire <- as.numeric(d_apa$CF1ETHRACE == 2)
d_apa_noBW <- d_apa[d_apa$sire != "BW", ]
d_apa_noBW$sire <- droplevels(d_apa_noBW$sire)
d_haa <- d[d$CF1ETHRACE == 2 & d$CM1ETHRACE == 2 &
!d$african_origin & !d$parent_fb & !d$gp_fb, ]
cat("\nDunedinPACE by sample:\n")
##
## DunedinPACE by sample:
cat(sprintf(" APA: N = %d\n", sum(!is.na(d_apa$K5MK_POAM45))))
## APA: N = 469
cat(sprintf(" APA excl BW: N = %d\n", sum(!is.na(d_apa_noBW$K5MK_POAM45))))
## APA excl BW: N = 457
cat(sprintf(" HAA: N = %d\n", sum(!is.na(d_haa$K5MK_POAM45))))
## HAA: N = 400
# ==============================================================
# Full Diagnostics: All Clocks
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("Epigenetic Clocks: Full Diagnostics\n")
## Epigenetic Clocks: Full Diagnostics
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
clock_vars <- c("K5MK_POAM45", "K5MK_POAM38", "K5MK_PHENOAGE", "K5MK_GRIM",
"K5MK_HORVATH", "K5MK_SKINBLOOD", "K5MK_PEDBE", "K5MK_HANNUM")
clock_labels <- c("DunedinPACE", "DunedinPoAm38", "PhenoAge", "GrimAge",
"Horvath", "SkinBlood", "PedBE", "Hannum")
# APA
cat("\nCorrelations: Clocks with Ancestry and Outcomes (APA)\n")
##
## Correlations: Clocks with Ancestry and Outcomes (APA)
cat(sprintf("%-15s %5s %9s %9s %9s %9s %9s %9s\n",
"Clock", "N", "r(PC1A)", "r(g)", "r(PPVT)", "r(WAIS)", "r(SES)", "r(WJ9)"))
## Clock N r(PC1A) r(g) r(PPVT) r(WAIS) r(SES) r(WJ9)
cat(paste(rep("-", 80), collapse = ""), "\n")
## --------------------------------------------------------------------------------
for (i in seq_along(clock_vars)) {
v <- clock_vars[i]
mask_pc <- !is.na(d_apa[[v]]) & !is.na(d_apa$K5PC1A)
mask_g <- !is.na(d_apa[[v]]) & !is.na(d_apa$g_raw)
mask_ppvt <- !is.na(d_apa[[v]]) & !is.na(d_apa$PPVT)
mask_wais <- !is.na(d_apa[[v]]) & !is.na(d_apa$ave_WAIS_raw)
mask_ses <- !is.na(d_apa[[v]]) & !is.na(d_apa$SES_raw)
mask_wj9 <- !is.na(d_apa[[v]]) & !is.na(d_apa$WJ9)
n <- sum(mask_pc)
if (n < 10) next
r_pc <- cor(d_apa[[v]][mask_pc], d_apa$K5PC1A[mask_pc])
r_g <- cor(d_apa[[v]][mask_g], d_apa$g_raw[mask_g])
r_ppvt <- cor(d_apa[[v]][mask_ppvt], d_apa$PPVT[mask_ppvt])
r_wais <- cor(d_apa[[v]][mask_wais], d_apa$ave_WAIS_raw[mask_wais])
r_ses <- cor(d_apa[[v]][mask_ses], d_apa$SES_raw[mask_ses])
r_wj9 <- cor(d_apa[[v]][mask_wj9], d_apa$WJ9[mask_wj9])
cat(sprintf("%-15s %5d %+9.4f %+9.4f %+9.4f %+9.4f %+9.4f %+9.4f\n",
clock_labels[i], n, r_pc, r_g, r_ppvt, r_wais, r_ses, r_wj9))
}
## DunedinPACE 469 +0.1137 +0.0247 +0.0240 -0.0152 -0.0032 -0.0216
## DunedinPoAm38 469 +0.1263 -0.0312 -0.0835 -0.0516 -0.0690 -0.0087
## PhenoAge 146 +0.1252 -0.0190 +0.0486 -0.0096 -0.0241 -0.0142
## GrimAge 469 +0.0509 +0.0321 +0.0539 -0.0018 +0.0066 -0.0270
## Horvath 469 +0.0023 -0.0398 -0.0148 -0.0163 +0.0216 -0.0532
## SkinBlood 469 +0.0584 +0.0075 +0.0161 +0.0119 +0.0255 +0.0338
## PedBE 469 +0.0210 +0.0116 +0.0481 -0.0053 +0.0576 +0.0214
## Hannum 469 +0.0282 +0.0552 +0.0539 -0.0226 +0.0146 -0.0015
# HAA
cat("\nCorrelations: Clocks with Ancestry and Outcomes (HAA)\n")
##
## Correlations: Clocks with Ancestry and Outcomes (HAA)
cat(sprintf("%-15s %5s %9s %9s %9s %9s %9s %9s\n",
"Clock", "N", "r(PC1A)", "r(g)", "r(PPVT)", "r(WAIS)", "r(SES)", "r(WJ9)"))
## Clock N r(PC1A) r(g) r(PPVT) r(WAIS) r(SES) r(WJ9)
cat(paste(rep("-", 80), collapse = ""), "\n")
## --------------------------------------------------------------------------------
for (i in seq_along(clock_vars)) {
v <- clock_vars[i]
mask_pc <- !is.na(d_haa[[v]]) & !is.na(d_haa$K5PC1A)
mask_g <- !is.na(d_haa[[v]]) & !is.na(d_haa$g_raw)
mask_ppvt <- !is.na(d_haa[[v]]) & !is.na(d_haa$PPVT)
mask_wais <- !is.na(d_haa[[v]]) & !is.na(d_haa$ave_WAIS_raw)
mask_ses <- !is.na(d_haa[[v]]) & !is.na(d_haa$SES_raw)
mask_wj9 <- !is.na(d_haa[[v]]) & !is.na(d_haa$WJ9)
n <- sum(mask_pc)
if (n < 10) next
r_pc <- cor(d_haa[[v]][mask_pc], d_haa$K5PC1A[mask_pc])
r_g <- cor(d_haa[[v]][mask_g], d_haa$g_raw[mask_g])
r_ppvt <- cor(d_haa[[v]][mask_ppvt], d_haa$PPVT[mask_ppvt])
r_wais <- cor(d_haa[[v]][mask_wais], d_haa$ave_WAIS_raw[mask_wais])
r_ses <- cor(d_haa[[v]][mask_ses], d_haa$SES_raw[mask_ses])
r_wj9 <- cor(d_haa[[v]][mask_wj9], d_haa$WJ9[mask_wj9])
cat(sprintf("%-15s %5d %+9.4f %+9.4f %+9.4f %+9.4f %+9.4f %+9.4f\n",
clock_labels[i], n, r_pc, r_g, r_ppvt, r_wais, r_ses, r_wj9))
}
## DunedinPACE 400 +0.0907 +0.0333 +0.0410 -0.0125 +0.0004 -0.0218
## DunedinPoAm38 400 +0.0581 -0.0014 -0.0272 -0.0518 -0.0659 +0.0002
## PhenoAge 122 +0.0485 +0.0230 +0.0695 -0.0396 -0.0024 +0.0431
## GrimAge 400 +0.0511 +0.0264 +0.0424 +0.0085 +0.0278 -0.0216
## Horvath 400 -0.0658 -0.0333 -0.0261 -0.0012 +0.0246 -0.0343
## SkinBlood 400 +0.0498 +0.0161 +0.0219 +0.0184 +0.0457 +0.0518
## PedBE 400 +0.0022 -0.0037 +0.0482 -0.0017 +0.0373 +0.0048
## Hannum 400 +0.0337 +0.0574 +0.0568 -0.0110 +0.0207 +0.0067
# ==============================================================
# Mediation: DunedinPACE
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("Mediation: Does DunedinPACE attenuate ancestry-cognition?\n")
## Mediation: Does DunedinPACE attenuate ancestry-cognition?
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
d_apa$PACE_z <- as.vector(scale(d_apa$K5MK_POAM45))
d_haa$PACE_z <- as.vector(scale(d_haa$K5MK_POAM45))
# APA: without PACE (restricted to epi sample)
run_model(g ~ PC1A_z + sire + gen_2nd + gen_3rd + sex + age_c,
d_apa[!is.na(d_apa$K5MK_POAM45), ],
"APA M1 (no PACE, epi subsample)")
##
## ================================================================================
## APA M1 (no PACE, epi subsample)
## R2 = 0.037 Adj-R2 = 0.0202
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0267 0.1162 -0.230 0.8185
## PC1A_z -0.1605 0.0831 -1.931 0.0541
## sireBW -0.1006 0.4037 -0.249 0.8033
## sireBH +0.3733 0.2190 +1.704 0.0890
## sireBO +0.1721 0.2403 +0.716 0.4741
## gen_2ndTRUE +0.6592 0.3450 +1.911 0.0567
## gen_3rdTRUE +0.0166 0.2649 +0.063 0.9501
## sex +0.0964 0.1254 +0.769 0.4424
## age_c +0.0686 0.0544 +1.262 0.2077
## N = 467
# APA: with PACE
run_model(g ~ PC1A_z + PACE_z + sire + gen_2nd + gen_3rd + sex + age_c, d_apa,
"APA M1 + DunedinPACE")
##
## ================================================================================
## APA M1 + DunedinPACE
## R2 = 0.0375 Adj-R2 = 0.0186
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0301 0.1108 -0.272 0.7856
## PC1A_z -0.1558 0.0839 -1.857 0.0640
## PACE_z -0.0242 0.0844 -0.287 0.7745
## sireBW -0.0893 0.4013 -0.223 0.8240
## sireBH +0.3748 0.2172 +1.726 0.0851
## sireBO +0.1833 0.2331 +0.786 0.4321
## gen_2ndTRUE +0.6749 0.3480 +1.939 0.0531
## gen_3rdTRUE +0.0130 0.2735 +0.047 0.9622
## sex +0.0992 0.1219 +0.814 0.4159
## age_c +0.0689 0.0548 +1.259 0.2086
## N = 467
# HAA: without PACE (restricted to epi sample)
run_model(g ~ PC1A_z + sex + age_c,
d_haa[!is.na(d_haa$K5MK_POAM45), ],
"HAA M1 (no PACE, epi subsample)")
##
## ================================================================================
## HAA M1 (no PACE, epi subsample)
## R2 = 0.0162 Adj-R2 = 0.0087
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0246 0.1189 -0.207 0.8362
## PC1A_z -0.1087 0.0576 -1.888 0.0597
## sex +0.1451 0.1347 +1.077 0.2821
## age_c +0.0830 0.0588 +1.411 0.1591
## N = 398
# HAA: with PACE
run_model(g ~ PC1A_z + PACE_z + sex + age_c, d_haa,
"HAA M1 + DunedinPACE")
##
## ================================================================================
## HAA M1 + DunedinPACE
## R2 = 0.0177 Adj-R2 = 0.0077
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0312 0.1118 -0.279 0.7806
## PC1A_z -0.1048 0.0581 -1.803 0.0722
## PACE_z -0.0442 0.0943 -0.468 0.6397
## sex +0.1538 0.1274 +1.208 0.2279
## age_c +0.0833 0.0591 +1.410 0.1594
## N = 398
# ==============================================================
# Mediation: GrimAge
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("Mediation: Does GrimAge attenuate ancestry-cognition?\n")
## Mediation: Does GrimAge attenuate ancestry-cognition?
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
d_apa$GRIM_z <- as.vector(scale(d_apa$K5MK_GRIM))
d_haa$GRIM_z <- as.vector(scale(d_haa$K5MK_GRIM))
run_model(g ~ PC1A_z + GRIM_z + sire + gen_2nd + gen_3rd + sex + age_c, d_apa,
"APA M1 + GrimAge")
##
## ================================================================================
## APA M1 + GrimAge
## R2 = 0.0371 Adj-R2 = 0.0181
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0268 0.1177 -0.227 0.8202
## PC1A_z -0.1607 0.0834 -1.928 0.0545
## GRIM_z +0.0020 0.0674 +0.029 0.9769
## sireBW -0.1017 0.4031 -0.252 0.8010
## sireBH +0.3733 0.2196 +1.700 0.0898
## sireBO +0.1713 0.2382 +0.719 0.4724
## gen_2ndTRUE +0.6590 0.3450 +1.910 0.0567
## gen_3rdTRUE +0.0161 0.2654 +0.061 0.9516
## sex +0.0968 0.1309 +0.740 0.4597
## age_c +0.0685 0.0548 +1.250 0.2121
## N = 467
run_model(g ~ PC1A_z + GRIM_z + sex + age_c, d_haa,
"HAA M1 + GrimAge")
##
## ================================================================================
## HAA M1 + GrimAge
## R2 = 0.0162 Adj-R2 = 0.0062
## ================================================================================
## Variable B SE t p
## ------------------------------------------------------------
## (Intercept) -0.0247 0.1213 -0.204 0.8385
## PC1A_z -0.1088 0.0577 -1.885 0.0601
## GRIM_z +0.0019 0.0738 +0.025 0.9798
## sex +0.1454 0.1395 +1.042 0.2981
## age_c +0.0828 0.0593 +1.397 0.1631
## N = 398
# ==============================================================
# Mediation: All clocks (loop)
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("Mediation Summary: All Clocks (HAA)\n")
## Mediation Summary: All Clocks (HAA)
cat("PC1A_z coefficient with and without each clock\n")
## PC1A_z coefficient with and without each clock
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
cat(sprintf("\n%-15s %5s %9s %9s %9s\n",
"Clock", "N", "PC1A(no)", "PC1A(+clk)", "Change"))
##
## Clock N PC1A(no) PC1A(+clk) Change
cat(paste(rep("-", 50), collapse = ""), "\n")
## --------------------------------------------------
for (i in seq_along(clock_vars)) {
v <- clock_vars[i]
dd <- d_haa[!is.na(d_haa[[v]]) & !is.na(d_haa$g_raw) &
!is.na(d_haa$K5PC1A) & !is.na(d_haa$age) &
!is.na(d_haa$sex) & !is.na(d_haa$wt), ]
if (nrow(dd) < 30) next
dd$PC1A_z <- as.vector(scale(dd$K5PC1A))
dd$g <- as.vector(scale(dd$g_raw))
dd$age_c <- as.vector(scale(dd$age))
dd$clock_z <- as.vector(scale(dd[[v]]))
m0 <- lm(g ~ PC1A_z + sex + age_c, data = dd, weights = wt)
m1 <- lm(g ~ PC1A_z + clock_z + sex + age_c, data = dd, weights = wt)
b0 <- coef(m0)["PC1A_z"]
b1 <- coef(m1)["PC1A_z"]
change_pct <- 100 * (b1 - b0) / b0
cat(sprintf("%-15s %5d %+9.4f %+9.4f %+8.1f%%\n",
clock_labels[i], nrow(dd), b0, b1, change_pct))
}
## DunedinPACE 398 -0.1087 -0.1048 -3.6%
## DunedinPoAm38 398 -0.1087 -0.1076 -1.1%
## PhenoAge 121 -0.1026 -0.1031 +0.6%
## GrimAge 398 -0.1087 -0.1088 +0.1%
## Horvath 398 -0.1087 -0.1145 +5.3%
## SkinBlood 398 -0.1087 -0.1085 -0.2%
## PedBE 398 -0.1087 -0.1092 +0.4%
## Hannum 398 -0.1087 -0.1088 +0.0%
# ==============================================================
# Epigenetic Formal Mediation: PC1A -> DunedinPACE -> g
# ==============================================================
cat("\n", paste(rep("#", 80), collapse = ""), "\n")
##
## ################################################################################
cat("FORMAL MEDIATION: PC1A -> DunedinPACE -> g (HAA)\n")
## FORMAL MEDIATION: PC1A -> DunedinPACE -> g (HAA)
cat(paste(rep("#", 80), collapse = ""), "\n")
## ################################################################################
d_epi_haa <- d_haa[!is.na(d_haa$K5MK_POAM45) & !is.na(d_haa$g_raw) &
!is.na(d_haa$K5PC1A) & !is.na(d_haa$age) &
!is.na(d_haa$sex) & !is.na(d_haa$wt), ]
d_epi_haa$PC1A_z <- as.vector(scale(d_epi_haa$K5PC1A))
d_epi_haa$g <- as.vector(scale(d_epi_haa$g_raw))
d_epi_haa$PACE_z <- as.vector(scale(d_epi_haa$K5MK_POAM45))
d_epi_haa$age_c <- as.vector(scale(d_epi_haa$age))
med_M_epi <- lm(PACE_z ~ PC1A_z + sex + age_c, data = d_epi_haa, weights = wt)
out_Y_epi <- lm(g ~ PACE_z + PC1A_z + sex + age_c, data = d_epi_haa, weights = wt)
set.seed(77777)
med_epi <- mediate(med_M_epi, out_Y_epi, treat = "PC1A_z", mediator = "PACE_z",
boot = TRUE, sims = 1000)
## Running nonparametric bootstrap
summary(med_epi)
##
## Causal Mediation Analysis
##
## Nonparametric Bootstrap Confidence Intervals with the Percentile Method
##
## Estimate 95% CI Lower 95% CI Upper p-value
## ACME -0.0039344 -0.0294527 0.0141085 0.694
## ADE -0.1047899 -0.2179852 0.0076368 0.070 .
## Total Effect -0.1087243 -0.2193958 0.0061326 0.058 .
## Prop. Mediated 0.0361871 -0.2512920 0.4752066 0.696
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Sample Size Used: 398
##
##
## Simulations: 1000
################
#"Epigenetic pace-of-aging measures (DunedinPACE, DunedinPoAm38, GrimAge, Horvath, and others) did not attenuate the ancestry-cognition association (maximum change: 3.6%), and showed near-zero correlations with cognitive outcomes (|r| < .05 for all clocks).
#These results do not support epigenetic aging as a mediator of ancestry-related cognitive differences in this sample."