在心理学研究中,经常使用混合线性模型(Mixed-Effects Models)或类似的思想。假设我们研究 “年龄 (Age, IV)” 对 “外向性 (Extraversion, DV)” 的影响。
我们需要区分两个层面的变异:
题目也有其独特的属性,但往往被我们忽略。
如果存在上述的题目斜率差异(即 IV 与 Items 存在交互作用),但我们在分析时强行忽略它,会发生什么?
set.seed(123)
n_subj <- 50
age <- rnorm(n_subj, mean = 20, sd = 10) # IV
# 假设构念由2个题目测量
# Item 1: 随年龄增长稍微增加 (Slope = 0.05)
# Item 2: 随年龄增长显著减少 (Slope = -0.05)
# 平均效应 (True Effect): 0 (Items 相互抵消)
item1_scores <- 0.05 * age + rnorm(n_subj)
item2_scores <- -0.05 * age + rnorm(n_subj)
demo_data <- data.frame(
ID = rep(1:n_subj, 2),
Age = rep(age, 2),
Score = c(item1_scores, item2_scores),
Item = rep(c("Item 1 (温和社交)", "Item 2 (刺激社交)"), each = n_subj)
)
# 计算聚合分数 (Aggregation)
agg_data <- demo_data %>%
group_by(ID, Age) %>%
summarise(Mean_Score = mean(Score))
# --- 绘图 ---
p1 <- ggplot(demo_data, aes(x = Age, y = Score, color = Item, group = Item)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "lm", se = FALSE, size = 1.5) +
theme_minimal() +
labs(title = "真实情况:题目斜率异质性", subtitle = "不同题目对 Age 的反应截然不同") +
theme(legend.position = "bottom")
p2 <- ggplot(agg_data, aes(x = Age, y = Mean_Score)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "lm", color = "black", size = 1.5) +
theme_minimal() +
labs(title = "聚合分析 (Aggregation)", subtitle = "强行平均后,斜率相互抵消,掩盖了真实机制")
p1 + p2
如图所示: 左图中,IV 与 Items 存在明显的交互作用(斜率一正一负)。 右图中,使用聚合(求平均)方法时,这种丰富的交互信息丢失了。这部分未被建模的交互作用方差,在后续统计检验中会被错误地处理。
概化理论认为:为了让结论具有推广性,统计上应假设我们使用的题目是从一个无限题库 (infinite pool of items) 中随机抽取的。这意味着题目应当被视为随机效应 (Random Effect)。
在实际数据分析中陷入了一个悖论:
理论上:承认题目是随机采样的,即应该视作随机效应。
建模时:未考虑题目的随机效应,变相假定这些Items对 IV 的反应是固定效应(Fixed Effect)。
异质性(Heterogeneity)要求与同质性 (Homogeneity) 的误解:
有的研究在设计题目表达概念/构念时,为了高外部效度,会从不同角度设计题目,这些题目本身存在异质性
退一步讲,就算题目是同质的,也只代表它们“长得像”(内部相关高),不代表它们对外部变量(IV)的反应(Slope)也是一样的
无论是简单的回归(Aggregation)还是复杂的结构方程模型(SEM),都在不同程度上抹杀了这种题目层面的斜率不一致。
# ==========================================
# 1. 进阶数据生成:构造“矛盾”情境
# ==========================================
set.seed(999) # 换一个种子确保效果明显
N <- 300
J <- 5
IV <- rnorm(N, 0, 1)
# A. 生成潜变量 (Factor)
# 假设 IV 对潜变量有强正向影响 (Beta = 0.6)
Latent_F <- 0.6 * IV + rnorm(N, 0, 0.8)
# B. 生成题目 (Items)
# 关键点:所有题目在测量模型上都是“好题”(正向载荷),内部一致性高
# y = 1.0 * F + unique_effect + error
sim_data_wide <- data.frame(ID = 1:N, IV = IV)
sim_data_long <- data.frame()
# 题目 1-4:完全听从潜变量指挥
for(j in 1:4){
y <- 1.0 * Latent_F + rnorm(N, 0, 1)
sim_data_wide[[paste0("y", j)]] <- y
sim_data_long <- rbind(sim_data_long, data.frame(ID=1:N, IV=IV, Item=paste0("Item", j), Score=y))
}
# 题目 5:叛逆者 (Item Specific Effect)
# 它虽然测量同一个潜变量(有 +1.0 * F),但 IV 对它有额外的直接负面打击 (-1.5 * IV)
# 净效应(Net Slope) ≈ 0.6 (from F) - 1.5 (Direct) = -0.9
y5 <- 1.0 * Latent_F - 1.5 * IV + rnorm(N, 0, 1)
sim_data_wide$y5 <- y5
sim_data_long <- rbind(sim_data_long, data.frame(ID=1:N, IV=IV, Item="Item5", Score=y5))
# ==========================================
# 2. 模型拟合
# ==========================================
library(lavaan)
# --- A. 聚合回归 ---
sim_data_wide$Mean_Score <- rowMeans(sim_data_wide[, paste0("y", 1:5)])
agg_model <- lm(Mean_Score ~ IV, data = sim_data_wide)
agg_intercept <- coef(agg_model)[1]
agg_slope <- coef(agg_model)[2]
# --- B. SEM (Common Factor) ---
sem_syntax <- '
F1 =~ y1 + y2 + y3 + y4 + y5
F1 ~ IV
'
sem_fit <- sem(sem_syntax, data = sim_data_wide, std.lv = TRUE)
# 提取 SEM 隐含的回归线
est <- parameterEstimates(sem_fit)
gamma <- est[est$op == "~" & est$lhs == "F1" & est$rhs == "IV", "est"] # 潜变量斜率
sem_lines <- data.frame()
for(j in 1:5){
item_name <- paste0("y", j)
# 截距
nu <- est[est$op == "~1" & est$lhs == item_name, "est"]
if(length(nu)==0) nu <- mean(sim_data_wide[[item_name]])
# 载荷
lam <- est[est$op == "=~" & est$rhs == item_name, "est"]
# 这里的关键:SEM 强行认为 斜率 = 载荷 * Gamma
# 因为 y1-y4 都是正相关,Gamma 是正的,且所有题目内部正相关,
# SEM 会估计出 y5 的载荷也是正的(因为 y5 也受 Latent_F 影响)
# 于是 SEM 会错误地预测 y5 随着 IV 增加而增加
implied_slope <- lam * gamma
temp <- data.frame(IV = seq(min(IV), max(IV), length.out=100), Item=paste0("Item", j))
temp$Score <- nu + implied_slope * temp$IV
sem_lines <- rbind(sem_lines, temp)
}
# ==========================================
# 3. 绘图对比
# ==========================================
library(ggplot2)
library(patchwork)
common_theme <- theme_bw() +
theme(legend.position = "none", plot.title = element_text(face="bold"))
# Plot 1: Aggregation
p_agg <- ggplot(sim_data_long, aes(x = IV, y = Score)) +
geom_point(color="grey", alpha=0.3) +
geom_abline(intercept = agg_intercept, slope = agg_slope, color = "#D55E00", size = 2) +
labs(title = "1. Aggregation", subtitle = "简单平均,彻底掩盖 Item 5") +
ylim(-4, 4) + common_theme
# Plot 2: SEM
# 注意看紫色线 (Item 5)
p_sem <- ggplot() +
geom_point(data = sim_data_long, aes(x = IV, y = Score, color = Item), alpha = 0.1) +
geom_line(data = sem_lines, aes(x = IV, y = Score, color = Item), size = 1.2) +
labs(title = "2. SEM (Common Factor)",
subtitle = "强制一致性:\n由于 Item 5 属于该构念,SEM 强行预测它\n随 IV 增加而增加 (紫色线向上)。\n这与数据事实(散点向下)完全相反!") +
ylim(-4, 4) + common_theme
# Plot 3: Real Data / RIS
p_ris <- ggplot(sim_data_long, aes(x = IV, y = Score, color = Item)) +
geom_point(alpha = 0.2) +
geom_smooth(method = "lm", se = FALSE, size = 1.2) +
labs(title = "3. Real Data (RIS)",
subtitle = "真相:\nItem 5 (紫色) 实际上是负相关的。\n这是 SEM 无法捕捉的特异性斜率。") +
ylim(-4, 4) + common_theme
p_agg + p_sem + p_ris
my_labels <- c("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "IV", "Factor")
semPaths(object = sem_fit,
what = "std",
whatLabels = "std",
layout = "tree2",
edge.label.cex = 1,
rotation = 2,
nodeLabels = my_labels,
)
title("Common Factor Model (SEM) 路径图", adj = 0.5, line = 2)
mtext("IV 到 Item 5 没有直接路径,必须经过 F1", side = 1, line = 0, cex = 1.2)
针对第一部分提出的问题,文章正式提出了 Random Item Slope Regression (RISR) 模型。这是理解一切推断谬误的基石。
传统聚合回归 (Aggregation Model): 假设题目对 IV 的反应是固定的、一致的。 \[ y_i = \beta_0 + \beta_1 x_i + \epsilon_i \]
随机题目斜率回归 (RISR, Equation 4): 假设题目是从题库中随机抽取的,对 IV 的敏感度各不相同。 \[ y_{ij} = \beta_0 + u_{0i} + u_{0j} + (\beta_1 + \mathbf{u_{1j}})x_i + \epsilon_{ij} \]
其中核心差异在于 \(\mathbf{u_{1j}}\): * \(u_{1j}\) 代表第 \(j\) 个题目独有的斜率偏离(Random Item Slope)。 * 服从正态分布 \(u_{1j} \sim N(0, \tau_{11})\)。 * \(\tau_{11}\) 衡量了题目间对 IV 反应的异质性程度。
为什么忽略 \(u_{1j}\) 会导致严重的假阳性(Type I Error)?根本原因在于\(t\) 检验的机制失效。
在线性回归中,判断显著性的 \(t\) 值计算公式为: \[ t = \frac{\text{Estimate}}{\text{Standard Error}} = \frac{\hat{\beta}_1}{SE(\hat{\beta}_1)} \]
根据论文附录证明,两种模型对 SE 的估计截然不同:
传统模型 (Naive SE): 认为只要增加被试量 \(N\),误差就能无限缩小。 \[ SE_{naive} \approx \sqrt{\frac{\sigma^2_{residual}}{N \times J \times \sigma^2_x}} \] > 当 \(N \to \infty\), \(SE \to 0\)。
RISR 模型 (True SE): 引入了题目采样的不确定性(Generalization Error)。 \[ SE_{true}(A10变形) \approx \sqrt{\frac{\sigma^2_{residual}}{N \times J \times \sigma^2_x} + \mathbf{\frac{\tau_{11}}{J}}} \] > 当 \(N \to \infty\), 第一项趋近于0,但第二项 \(\frac{\tau_{11}}{J}\) 依然存在!
比较 真实模型 SE (RISR) 和 错误模型 SE (Aggregation/Naive) 的比率,比率越大,说明错误模型低估得越严重(即错得越离谱)。
由附录 A 的推导可以构造比率 \(K\):
\[ \text{Ratio } K = \frac{SE_{True}}{SE_{Naive}} = \sqrt{1 + \frac{I \cdot \tau_{11} \cdot s_x^2}{\sigma^2 + J \cdot \omega_{00}}} \]
残差方差 (\(\sigma^2\)) 越大,低估越严重(❌?) larger underestimation when there is larger residual error variance❌
这意味着,如果题目本身存在斜率变异(\(\tau_{11} > 0\)),而我们使用了传统模型(忽略它),我们会得到一个被严重低估的 SE。
后果:分母变小 \(\rightarrow\) t 值虚高 \(\rightarrow\) 假阳性(Type I Error)。
而且,样本量 \(N\) 越大,错得越离谱。
图解: 只要看蓝色线(传统做法),当 \(N=3000\) 时,SE 几乎为 0。此时哪怕 \(\hat{\beta}\) 只有微小的 0.01,算出来的 \(t\) 值也会巨大无比,导致必然的显著。这解释了为什么大数据集特别容易出现 Random Item Slope 导致的假阳性。
既然问题这么严重,为什么我们在做 Cronbach’s \(\alpha\) 或 SEM 模型拟合(CFI/RMSEA)时没有发现?
\(\alpha\) 系数反映的是题目分数的内部一致性。 \[ Var(y_{ij}) = \underbrace{\omega_{00}}_{\text{人与人的巨大差异}} + \tau_{00} + \underbrace{\tau_{11}x_i^2}_{\text{微小的斜率差异}} + \sigma^2 \]
Common Factor Regression虽然比聚合回归严谨,它本质上是一个Constrained Model,它强迫所有题目通过潜变量产生联系:
实证证据 (Real Data): 论文使用了 \(N=564\) 的真实数据发现,即使 CFI = 0.956, RMSEA = 0.087 这样被认为“可接受”的模型,在使用 RISR 检验后,依然发现了显著的 Random Item Slopes,且推翻了之前的显著性结论。
结论:高信度 (\(\alpha\)) 和高拟合度 (CFI) 不能 作为不存在 Random Item Slope 的证据。
在建立了理论模型后,Donnellan 等人通过两步验证了 RISR 的必要性:首先在受控环境下(模拟)证明传统模型会犯错,然后在真实环境(实测数据)中证明这种情况确实存在。
研究者构建了 72 种不同的参数组合,系统改变了样本量 (\(N\))、题目数量 (\(J\)) 和随机斜率的方差大小 (\(\tau_{11}\))。
在模拟中,设定 IV 和 DV 的真实平均关系为 0 (\(\beta_1 = 0\))。因此,任何“显著”的结果都是假阳性(Type I Error)。
图表解读: 1. 红色柱子 (Aggregation) 和 蓝色柱子 (Common Factor): - 当存在随机题目斜率(\(\tau_{11} > 0\))时,随着样本量 (\(N\)) 的增加,错误率飙升,甚至接近 100%。 - 警示:这就是前文提到的“大样本悖论”。样本越大,传统模型越“确信”一个错误的结论。 2. 绿色柱子 (RISR): - 无论样本量多大,错误率始终控制在名义水平(0.05)左右。 - 证明了 RISR 模型能有效校正标准误(SE)。
模拟显示,即使随机题目斜率的方差非常小(仅占相关误差方差的 0.549%),在大样本下(\(N=1000\))也足以导致传统模型的统计推断失效。这说明这不是一个边缘问题,而是一个普遍的隐患。
为了证明这不仅仅是数学游戏,作者收集了 \(N=564\) 的数据,使用了心理学常用的量表(如大五人格、自我实现量表等)和反应时任务。
这是一个最具戏剧性的例子。研究者探究“年龄”是否预测“自我实现”得分。
[图片占位符 2] 请在此处插入论文中的 Figure 5 (Self-Actualization Scores Predicted by Age)
结果对比: * 传统聚合回归:发现显著的负相关 (\(p < .001\))。结论:年龄越大,自我实现感越低。 * RISR 模型:结果不显著 (\(p = .066\))。 * 发生了什么? 观察 Figure 5 底部的小图可以发现,题目之间存在巨大的异质性: * Item 8 (“I fear failure”) 和 Item 14 对年龄非常敏感(强负相关)。 * 其他题目对年龄几乎没有反应,甚至呈正相关。 * 结论:所谓的“显著负相关”完全是由少数几个对年龄敏感的题目(主要关于“恐惧”)驱动的,不能推广到整个“自我实现”构念。
在测试的所有 IV-DV 组合中,随机题目斜率普遍存在。在某些情况下,RISR 估计出的随机斜率方差甚至远大于模拟中设定的水平。这表明我们过去文献中许多“小而显著”的大样本效应,可能只是由于量表中混入了几个对 IV 特别敏感的题目。
RISR 不仅仅是一个统计修正,它代表了一种不同的测量观 (Measurement Model): - Common Factor (SEM):认为题目是潜变量的“纯净”反映,题目特异性是“误差”。 - RISR:认为题目是构念的“样本”。题目本身具有因果力,允许题目与外部变量有独特的交互。我们应该把结论推广到“题库”,而不仅仅是“人”。
如何在自己的研究中实现这个模型?其实非常简单,只需要使用
lme4 包即可。
关键在于数据必须是 长数据格式 (Long Format)。每一行是一个观察值(某人回答某题的分数)。
我们将生成一个模拟数据集,并手把手演示如何由“聚合回归”转向“RISR”。
library(lme4)
library(dplyr)
library(ggplot2)
# --- 第一步:生成模拟数据 (Long Format) ---
set.seed(2025)
n_subj <- 200 # 200名被试
n_item <- 10 # 10个题目
ids <- rep(1:n_subj, each = n_item)
items <- rep(paste0("Item_", 1:n_item), times = n_subj)
# IV: 连续变量 (如 Age)
# 注意:IV 必须是 Participant-Level 的 (每人只有一个值)
IV_data <- data.frame(ID = 1:n_subj, IV = rnorm(n_subj))
data_long <- data.frame(ID = factor(ids), Item = factor(items)) %>%
left_join(IV_data, by = "ID")
# 设定真实模型:
# Fixed Slope (Beta) = 0.1 (微弱正相关)
# Random Item Slope (Tau_11) = 存在显著异质性
item_slopes <- rnorm(n_item, mean = 0.1, sd = 0.3) # 每个题目有不同的真实斜率
names(item_slopes) <- unique(data_long$Item)
# 生成分数 Y
data_long$Slope_j <- item_slopes[data_long$Item]
data_long$Y <- 0 + # Intercept
(0.5 * rnorm(n_subj)[data_long$ID]) + # Random Subj Intercept
(0.5 * rnorm(n_item)[as.numeric(data_long$Item)]) + # Random Item Intercept
data_long$Slope_j * data_long$IV + # Slope effect
rnorm(nrow(data_long), 0, 1) # Residual
# --- 第二步:传统做法 (Aggregation) ---
data_agg <- data_long %>%
group_by(ID, IV) %>%
summarise(Mean_Y = mean(Y), .groups="drop")
model_agg <- lm(Mean_Y ~ IV, data = data_agg)
print(summary(model_agg)$coefficients)
# --- 第三步:RISR 建模 (Using lme4) ---
# 语法解析:
# Y ~ IV : 固定效应 (Fixed Effect)
# (1 | ID) : 随机被试截距 (每个人基础分不同)
# (1 + IV | Item) : 随机题目效应 (核心!)
# -> "1" 代表题目难度不同 (Random Item Intercept)
# -> "IV" 代表题目对IV敏感度不同 (Random Item Slope)
model_risr <- lmer(Y ~ IV + (1 | ID) + (1 + IV | Item),
data = data_long,
control = lmerControl(optimizer = "bobyqa")) # 优化器防报错
# --- 第四步:结果对比 ---
cat("\n=== 结果对比 ===\n")
cat("1. 聚合回归 t-value:", summary(model_agg)$coefficients["IV", "t value"], "\n")
cat("2. RISR 模型 t-value:", summary(model_risr)$coefficients["IV", "t value"], "\n")
cat("注意:RISR 的 t 值通常会变小(绝对值),因为 SE 变大了。\n")
# 提取并画出题目斜率 (可选)
# ranef(model_risr)$Item 包含了每个题目的截距和斜率
item_effects <- ranef(model_risr)$Item
item_effects$Item <- rownames(item_effects)
colnames(item_effects)[1:2] <- c("Intercept", "Slope_Deviation")
# 加上固定效应才是总斜率
fixed_slope <- fixef(model_risr)["IV"]
item_effects$Total_Slope <- item_effects$Slope_Deviation + fixed_slope
print(head(item_effects[, c("Item", "Total_Slope")]))
(1 | Item) + (0 + IV | Item),或者换用 brms
(贝叶斯方法)。lme4 默认不提供 p
值(因为自由度计算有争议)。可以使用 lmerTest 包自动加载 p
值,或者像论文一样关注 t 值(t > 1.96 视为显著)。“Treat items as random samples, not fixed indicators.”
RISR 并不是要否定过去的所有研究,而是提醒我们在处理异质性题目和大样本数据时,需要更加谦卑地对待统计推断的不确定性。当我们声称发现了一个关于“构念”的真理时,请确保这个真理不是仅由其中一两个题目驱动的。