关于数据与用途 本教程中的所有个体记录、效应和检验结果均由固定随机种子模拟,只用于统计学教学,不包含真实患者信息,也不能作为任何治疗有效或安全的临床证据。
本教程不是一张按变量名称机械查找检验的菜单。每一节都遵循同一条路径:明确研究问题与目标估计量 → 识别研究设计和数据结构 → 检查数据质量与方法假设 → 估计效应及其不确定性 → 必要时进行检验 → 结合临床意义报告。
建议先阅读“检验选择总览”,再学习与你的数据结构相关的章节。代码默认显示,可逐块运行;每个例子都尽量同时给出效应量、95% 置信区间和 p 值。
完成本教程后,你应能够:
统计检验衡量数据与某个零假设及其模型假设的相容程度。它不能修复选择偏倚、失访、错误测量、未控制混杂、错误的时间顺序或不合适的研究问题。
在计算任何 p 值前,应写清楚:
最常见的起点错误 “我的变量正态吗,所以该用哪种检验?”通常不是第一个问题。应先确认比较对象、独立性、配对关系、结局尺度和目标估计量;这些因素往往比边际分布是否完美正态更重要。
| 研究问题与结构 | 常用主要方法 | 主要效应或估计量 | 重要提醒 |
|---|---|---|---|
| 一组连续值与参考值比较 | 单样本 t 检验 | 均值与参考值之差 | 对均值推断;检查异常值与抽样机制 |
| 两个独立组的连续结局 | Welch t 检验 | 均值差 | 默认不要求两组方差相等 |
| 同一对象前后连续结局 | 配对 t 检验 | 个体差值的均值 | 分析的是“差值”,不能当成独立组 |
| 两独立组的有序或明显偏态结局 | Wilcoxon 秩和检验 | 分布位置/概率优势 | 通常不是“中位数检验” |
| 配对有序或偏态差值 | Wilcoxon 符号秩检验 | 差值分布的位置 | 需要差值分布近似对称;零值处理要说明 |
| 三个及以上独立组连续结局 | 单因素/Welch ANOVA | 组均值差异 | 总体检验后再做预设或校正的比较 |
| 三个及以上独立组有序或偏态结局 | Kruskal–Wallis 检验 | 秩分布差异 | 显著后需校正的两两比较 |
| 三个及以上重复测量 | 重复测量模型/Friedman 检验 | 时间或条件差异 | 必须保留患者内相关性 |
| 两个独立分类变量 | Pearson 卡方检验 | 比例关联 | 小期望频数时考虑 Fisher 精确检验 |
| 两个独立组的稀疏 2×2 表 | Fisher 精确检验 | 优势比及精确推断 | 不等于风险比;设计固定边际的假设要理解 |
| 同一对象前后二分类结局 | McNemar 检验 | 不一致配对的方向差 | 只使用不一致配对的信息 |
| 两个连续变量的线性关联 | Pearson 相关 | 相关系数 | 检查散点图、异常值与独立性 |
| 单调但非线性/有序关联 | Spearman 或 Kendall 相关 | 秩相关 | 不是因果效应,也不等于一致性 |
| 两组删失事件时间 | Kaplan–Meier + log-rank | 生存曲线差异 | 同时报告生存概率、时间和风险集 |
核心原则:检验回答“数据与零假设是否相容”,效应估计回答“差异有多大、方向如何”。医学报告通常需要后者及其置信区间,而不仅是一颗显著性星号。
单样本 t 检验用于比较一个总体的均值与预先指定的参考值 :
示例问题:这组患者的平均 12 周收缩压下降是否不同于预先设定的 8 mmHg?参考值应来自方案、历史标准或临床问题,不能在看完数据后挑选。
new_treatment_reduction <- subset(
trial_data,
treatment == "新治疗"
)$sbp_reduction
one_sample_result <- t.test(
new_treatment_reduction,
mu = 8,
conf.level = 0.95
)
data.frame(
样本量 = length(new_treatment_reduction),
样本均值 = mean(new_treatment_reduction),
均值与8的差 = mean(new_treatment_reduction) - 8,
CI下限 = unname(one_sample_result$conf.int[1] - 8),
CI上限 = unname(one_sample_result$conf.int[2] - 8),
p值 = one_sample_result$p.value,
check.names = FALSE
) |>
knitr::kable(
digits = 3,
caption = "新治疗组平均收缩压下降与 8 mmHg 的比较"
)| 样本量 | 样本均值 | 均值与8的差 | CI下限 | CI上限 | p值 |
|---|---|---|---|---|---|
| 160 | 11.6 | 3.62 | 2.23 | 5.01 | 0 |
关键条件包括:观察独立;结局的均值有明确意义;没有足以支配均值和标准误的严重错误值;样本来自与推断目标相符的过程。小样本时还需差值分布近似正态;样本量较大时,均值的抽样分布通常更稳健,但极端偏态和异常值仍需认真处理。
当研究目标是两个独立组的均值差时,Welch t 检验通常是合理默认选择。它不强迫两组总体方差相等,在方差确实相等时效率损失通常很小。
welch_result <- t.test(
sbp_reduction ~ treatment,
data = trial_data,
var.equal = FALSE
)
group_summary <- do.call(
rbind,
lapply(split(trial_data$sbp_reduction, trial_data$treatment), function(x) {
c(n = length(x), mean = mean(x), sd = sd(x))
})
)
group_summary <- data.frame(
treatment = rownames(group_summary),
group_summary,
row.names = NULL
)
knitr::kable(
group_summary,
digits = 2,
col.names = c("治疗组", "n", "均值", "标准差"),
caption = "各组收缩压下降的描述统计"
)| 治疗组 | n | 均值 | 标准差 |
|---|---|---|---|
| 标准治疗 | 160 | 7.07 | 8.24 |
| 新治疗 | 160 | 11.62 | 8.90 |
data.frame(
对比 = "标准治疗 − 新治疗",
均值差 = unname(welch_result$estimate[1] - welch_result$estimate[2]),
CI下限 = unname(welch_result$conf.int[1]),
CI上限 = unname(welch_result$conf.int[2]),
自由度 = unname(welch_result$parameter),
p值 = welch_result$p.value,
check.names = FALSE
) |>
knitr::kable(
digits = 3,
caption = "Welch 两独立样本 t 检验"
)| 对比 | 均值差 | CI下限 | CI上限 | 自由度 | p值 |
|---|---|---|---|---|---|
| 标准治疗 − 新治疗 | -4.55 | -6.43 | -2.66 | 316 | 0 |
t.test()
中因子第一水平减去第二水平,所以这里的差值是“标准治疗 −
新治疗”。如果希望正值代表新治疗获得更大下降,应在报告表中明确反转方向,不能只抄输出而不核对编码。
原始单位的均值差通常最容易判断临床意义。标准化均值差可用于不同量表间比较,但会受到研究人群变异程度影响。
standard_values <- subset(
trial_data,
treatment == "标准治疗"
)$sbp_reduction
new_values <- subset(
trial_data,
treatment == "新治疗"
)$sbp_reduction
n0 <- length(standard_values)
n1 <- length(new_values)
pooled_sd <- sqrt(
((n0 - 1) * var(standard_values) + (n1 - 1) * var(new_values)) /
(n0 + n1 - 2)
)
cohens_d <- (mean(new_values) - mean(standard_values)) / pooled_sd
small_sample_correction <- 1 - 3 / (4 * (n0 + n1) - 9)
hedges_g <- small_sample_correction * cohens_d
data.frame(
指标 = c("新治疗 − 标准治疗的均值差(mmHg)", "Hedges g"),
估计值 = c(mean(new_values) - mean(standard_values), hedges_g)
) |>
knitr::kable(digits = 3, caption = "原始与标准化效应量")| 指标 | 估计值 |
|---|---|
| 新治疗 − 标准治疗的均值差(mmHg) | 4.548 |
| Hedges g | 0.529 |
推荐报告句式 新治疗组与标准治疗组各纳入多少人;分别报告均值(SD);随后报告预先规定方向的均值差、95% CI 与 Welch t 检验 p 值;最后说明该区间相对于最小临床重要差异意味着什么。
配对 t 检验将每位患者的“治疗后 − 治疗前”先变为一个差值,再检验平均差值是否为零。它假定不同患者的差值相互独立,并关注差值分布,而不是分别要求治疗前和治疗后都正态。
paired_result <- t.test(
trial_data$followup_sbp,
trial_data$baseline_sbp,
paired = TRUE
)
within_person_change <- trial_data$followup_sbp - trial_data$baseline_sbp
data.frame(
样本量 = length(within_person_change),
平均变化 = mean(within_person_change),
变化SD = sd(within_person_change),
CI下限 = unname(paired_result$conf.int[1]),
CI上限 = unname(paired_result$conf.int[2]),
p值 = paired_result$p.value,
check.names = FALSE
) |>
knitr::kable(
digits = 3,
caption = "全体参与者随访值减基线值的配对 t 检验"
)| 样本量 | 平均变化 | 变化SD | CI下限 | CI上限 | p值 |
|---|---|---|---|---|---|
| 320 | -9.35 | 8.86 | -10.3 | -8.37 | 0 |
plot_ids <- seq_len(50)
matplot(
x = c(1, 2),
y = t(as.matrix(trial_data[plot_ids, c("baseline_sbp", "followup_sbp")])),
type = "l",
lty = 1,
col = grDevices::adjustcolor(palette_test[["gray"]], alpha.f = 0.32),
xaxt = "n",
xlab = "时点",
ylab = "收缩压(mmHg)"
)
axis(1, at = c(1, 2), labels = c("基线", "第 12 周"))
points(
c(1, 2),
c(mean(trial_data$baseline_sbp), mean(trial_data$followup_sbp)),
type = "b",
pch = 19,
lwd = 3,
col = palette_test[["vermillion"]]
)每位参与者的基线与 12 周收缩压。细线表示同一人。
前后显著性不同不等于组间变化不同 在一个治疗组内“前后显著”、另一个组内“前后不显著”,不能证明两组变化不同。应直接比较两组的个体变化,或更常见地用基线调整模型比较随访结局,并保留随机化分组。
当结局是有序变量,或连续结局存在强烈偏态而秩比较与问题相符时,可使用 Wilcoxon 秩和检验(Mann–Whitney U 检验)。它利用两组混合排序后的秩。
rank_sum_result <- wilcox.test(
crp_week12 ~ treatment,
data = trial_data,
exact = FALSE,
conf.int = TRUE
)
crp_summary <- do.call(
rbind,
lapply(split(trial_data$crp_week12, trial_data$treatment), function(x) {
c(
n = length(x),
median = median(x),
q1 = unname(quantile(x, 0.25)),
q3 = unname(quantile(x, 0.75))
)
})
)
knitr::kable(
data.frame(治疗组 = rownames(crp_summary), crp_summary, row.names = NULL),
digits = 2,
col.names = c("治疗组", "n", "中位数", "Q1", "Q3"),
caption = "12 周 CRP 的中位数与四分位数"
)| 治疗组 | n | 中位数 | Q1 | Q3 |
|---|---|---|---|---|
| 标准治疗 | 160 | 3.09 | 1.96 | 5.53 |
| 新治疗 | 160 | 2.88 | 1.62 | 4.69 |
data.frame(
W统计量 = unname(rank_sum_result$statistic),
位置差估计 = unname(rank_sum_result$estimate),
CI下限 = unname(rank_sum_result$conf.int[1]),
CI上限 = unname(rank_sum_result$conf.int[2]),
p值 = rank_sum_result$p.value,
check.names = FALSE
) |>
knitr::kable(
digits = 3,
caption = "Wilcoxon 秩和检验及 Hodges–Lehmann 型位置差"
)| W统计量 | 位置差估计 | CI下限 | CI上限 | p值 |
|---|---|---|---|---|
| 14232 | 0.36 | -0.06 | 0.81 | 0.084 |
若两组分布形状相同、主要差别可由整体平移描述,结果可解释为位置差。一般情况下,它检验的是两组分布是否具有相同的秩结构,不能自动写成中位数相等的检验。即使使用秩检验,也要画图并报告各组分布。
boxplot(
crp_week12 ~ treatment,
data = trial_data,
col = c(palette_test[["sky"]], palette_test[["orange"]]),
border = palette_test[["navy"]],
ylab = "CRP(mg/L)",
xlab = "治疗组",
outline = FALSE
)
stripchart(
crp_week12 ~ treatment,
data = trial_data,
vertical = TRUE,
method = "jitter",
pch = 16,
cex = 0.55,
col = grDevices::adjustcolor(palette_test[["navy"]], alpha.f = 0.32),
add = TRUE
)两组 12 周 CRP 的右偏分布。
Wilcoxon 符号秩检验适用于配对差值的秩分析。它除独立配对外,还通常需要差值分布围绕中心近似对称。若对称性难以辩护,可用只看正负号的精确符号检验,但它会丢弃差值大小信息。
signed_rank_result <- wilcox.test(
trial_data$followup_sbp,
trial_data$baseline_sbp,
paired = TRUE,
exact = FALSE,
conf.int = TRUE
)
nonzero_change <- within_person_change[within_person_change != 0]
negative_changes <- sum(nonzero_change < 0)
sign_test_result <- binom.test(
negative_changes,
length(nonzero_change),
p = 0.5
)
data.frame(
方法 = c("Wilcoxon 符号秩检验", "精确符号检验"),
统计量或计数 = c(
unname(signed_rank_result$statistic),
negative_changes
),
p值 = c(signed_rank_result$p.value, sign_test_result$p.value),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "两种配对非参数检验")| 方法 | 统计量或计数 | p值 |
|---|---|---|
| Wilcoxon 符号秩检验 | 3260 | 0 |
| 精确符号检验 | 276 | 0 |
一项随机试验比较两组患者住院天数。大量患者住院 1–3 天,少数患者住院超过 60 天。研究者最关心平均住院资源占用。应否因为分布偏态就自动改用 Wilcoxon 检验?
答案:不应自动更换。 若目标估计量是平均住院天数,Wilcoxon 检验回答的不是同一个问题。可先核对极端值是否真实,再考虑稳健或自助法均值差区间、置换方法、适合正偏结局的模型,以及同时报告均值与分布。方法必须与目标估计量一致。单因素方差分析的总体零假设是所有组的总体均值相等。它回答“是否至少有一个组均值不同”,但不会自动告诉我们具体哪些组不同。传统 ANOVA 假定独立误差、合理的均值模型、组内残差近似正态和总体方差相等;Welch ANOVA 放宽方差相等要求。
dose_summary <- do.call(
rbind,
lapply(split(dose_data$hemoglobin_change, dose_data$dose), function(x) {
c(n = length(x), mean = mean(x), sd = sd(x))
})
)
knitr::kable(
data.frame(组别 = rownames(dose_summary), dose_summary, row.names = NULL),
digits = 3,
col.names = c("组别", "n", "均值", "标准差"),
caption = "三组血红蛋白变化的描述统计"
)| 组别 | n | 均值 | 标准差 |
|---|---|---|---|
| 安慰剂 | 60 | 0.075 | 0.773 |
| 低剂量 | 60 | 0.695 | 0.847 |
| 高剂量 | 60 | 1.219 | 1.146 |
traditional_anova <- aov(hemoglobin_change ~ dose, data = dose_data)
welch_anova <- oneway.test(
hemoglobin_change ~ dose,
data = dose_data,
var.equal = FALSE
)
anova_table <- summary(traditional_anova)[[1]]
ss_between <- anova_table["dose", "Sum Sq"]
ss_total <- sum(anova_table[, "Sum Sq"])
ms_error <- anova_table["Residuals", "Mean Sq"]
k_groups <- nlevels(dose_data$dose)
omega_squared <- max(
0,
(ss_between - (k_groups - 1) * ms_error) / (ss_total + ms_error)
)
data.frame(
方法 = c("传统单因素 ANOVA", "Welch ANOVA"),
统计量 = c(
anova_table["dose", "F value"],
unname(welch_anova$statistic)
),
分子自由度 = c(
anova_table["dose", "Df"],
unname(welch_anova$parameter[1])
),
分母自由度 = c(
anova_table["Residuals", "Df"],
unname(welch_anova$parameter[2])
),
p值 = c(
anova_table["dose", "Pr(>F)"],
welch_anova$p.value
),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "传统 ANOVA 与 Welch ANOVA")| 方法 | 统计量 | 分子自由度 | 分母自由度 | p值 |
|---|---|---|---|---|
| 传统单因素 ANOVA | 22.5 | 2 | 177 | 0 |
| Welch ANOVA | 22.3 | 2 | 115 | 0 |
data.frame(
效应量 = "omega-squared",
估计值 = omega_squared
) |>
knitr::kable(digits = 3, caption = "传统单因素 ANOVA 的总体效应量")| 效应量 | 估计值 |
|---|---|
| omega-squared | 0.193 |
近似描述结局总变异中可归因于组别差异的比例,但不应脱离研究背景套用固定“小、中、大”阈值。它也不能代替具体组间均值差。
若方案预先指定“低剂量对安慰剂”和“高剂量对安慰剂”,这些对比比先做一次总体检验、显著后再穷举所有组合更贴近研究问题。若确实关心全部两两比较,可使用 Tukey 方法;方差不等时可进行各组 Welch t 检验并校正多重性。
tukey_result <- TukeyHSD(traditional_anova, "dose")$dose
knitr::kable(
data.frame(比较 = rownames(tukey_result), tukey_result, row.names = NULL),
digits = 3,
col.names = c("比较", "均值差", "同时CI下限", "同时CI上限", "调整后p值"),
caption = "Tukey 全部两两比较"
)| 比较 | 均值差 | 同时CI下限 | 同时CI上限 | 调整后p值 |
|---|---|---|---|---|
| 低剂量-安慰剂 | 0.620 | 0.216 | 1.024 | 0.001 |
| 高剂量-安慰剂 | 1.144 | 0.740 | 1.548 | 0.000 |
| 高剂量-低剂量 | 0.524 | 0.120 | 0.928 | 0.007 |
pairwise_welch <- pairwise.t.test(
dose_data$hemoglobin_change,
dose_data$dose,
p.adjust.method = "holm",
pool.sd = FALSE
)
pairwise_welch$p.value |>
knitr::kable(
digits = 3,
caption = "两两 Welch t 检验的 Holm 调整 p 值"
)| 安慰剂 | 低剂量 | |
|---|---|---|
| 低剂量 | 0 | NA |
| 高剂量 | 0 | 0.005 |
注意:pairwise.t.test() 上表提供调整后的 p
值,但没有同时置信区间。正式报告若以多重对比为主要结论,应使用能生成与校正方法一致的同时区间的模型后对比工具,而不是并列展示调整
p 值与未调整区间。
Kruskal–Wallis 检验将 Wilcoxon 秩和方法扩展到三个及以上独立组。其零假设涉及各组秩分布相同;只有在分布形状相近、差异可视为位置平移时,才适合简化为位置比较。
kruskal_result <- kruskal.test(
inflammation_score ~ dose,
data = dose_data
)
h_statistic <- unname(kruskal_result$statistic)
epsilon_squared <- max(
0,
(h_statistic - k_groups + 1) / (nrow(dose_data) - k_groups)
)
rank_summary <- do.call(
rbind,
lapply(split(dose_data$inflammation_score, dose_data$dose), function(x) {
c(
n = length(x),
median = median(x),
q1 = unname(quantile(x, 0.25)),
q3 = unname(quantile(x, 0.75))
)
})
)
knitr::kable(
data.frame(组别 = rownames(rank_summary), rank_summary, row.names = NULL),
digits = 2,
col.names = c("组别", "n", "中位数", "Q1", "Q3"),
caption = "三组炎症评分的描述统计"
)| 组别 | n | 中位数 | Q1 | Q3 |
|---|---|---|---|---|
| 安慰剂 | 60 | 2.96 | 1.40 | 5.43 |
| 低剂量 | 60 | 2.51 | 1.65 | 3.70 |
| 高剂量 | 60 | 2.58 | 1.60 | 4.14 |
data.frame(
H统计量 = h_statistic,
自由度 = unname(kruskal_result$parameter),
p值 = kruskal_result$p.value,
秩epsilon平方 = epsilon_squared,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "Kruskal–Wallis 检验")| H统计量 | 自由度 | p值 | 秩epsilon平方 |
|---|---|---|---|
| 1.46 | 2 | 0.483 | 0 |
pairwise_rank <- pairwise.wilcox.test(
dose_data$inflammation_score,
dose_data$dose,
p.adjust.method = "holm",
exact = FALSE
)
pairwise_rank$p.value |>
knitr::kable(
digits = 3,
caption = "两两 Wilcoxon 秩和检验的 Holm 调整 p 值"
)| 安慰剂 | 低剂量 | |
|---|---|---|
| 低剂量 | 0.839 | NA |
| 高剂量 | 0.839 | 0.967 |
总体显著不是每一对都不同 ANOVA 或 Kruskal–Wallis 的小 p 值只说明数据不支持“所有组完全相同”的总体零假设。具体组间差异必须通过预先指定或经过恰当多重性控制的对比来估计。
同一患者在多个时点的观测通常正相关。完整且平衡的连续资料可用重复测量 ANOVA;Friedman 检验是完整区组的秩方法。真实纵向研究若存在不等时间、缺失随访、时变协变量或组间比较,混合效应模型或广义估计方程通常更合适。
repeated_summary <- do.call(
rbind,
lapply(split(repeated_data$pain_score, repeated_data$time), function(x) {
c(n = length(x), mean = mean(x), sd = sd(x), median = median(x))
})
)
knitr::kable(
data.frame(时点 = rownames(repeated_summary), repeated_summary, row.names = NULL),
digits = 2,
col.names = c("时点", "n", "均值", "标准差", "中位数"),
caption = "三个时点疼痛评分的描述统计"
)| 时点 | n | 均值 | 标准差 | 中位数 |
|---|---|---|---|---|
| 基线 | 90 | 6.53 | 1.59 | 6.55 |
| 第4周 | 90 | 4.97 | 1.59 | 5.05 |
| 第12周 | 90 | 4.08 | 1.48 | 4.10 |
repeated_anova <- aov(
pain_score ~ time + Error(patient_id / time),
data = repeated_data
)
summary(repeated_anova)##
## Error: patient_id
## Df Sum Sq Mean Sq F value Pr(>F)
## Residuals 89 541 6.08
##
## Error: patient_id:time
## Df Sum Sq Mean Sq F value Pr(>F)
## time 2 276 137.9 235 <2e-16 ***
## Residuals 178 104 0.6
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
上述经典重复测量 ANOVA
对三个以上时点还涉及球形性假设。aov() 的简化输出不自动提供
Mauchly 检验或 Greenhouse–Geisser
校正,因此在需要正式推断时应使用能明确处理球形性或协方差结构的方法。
friedman_result <- friedman.test(
pain_score ~ time | patient_id,
data = repeated_data
)
data.frame(
卡方统计量 = unname(friedman_result$statistic),
自由度 = unname(friedman_result$parameter),
p值 = friedman_result$p.value,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "Friedman 重复测量秩检验")| 卡方统计量 | 自由度 | p值 |
|---|---|---|
| 134 | 2 | 0 |
如需探索哪些时点不同,可对配对比较的 p 值进行校正:
time_pairs <- combn(colnames(pain_matrix), 2, simplify = FALSE)
raw_p <- vapply(time_pairs, function(pair) {
wilcox.test(
pain_matrix[, pair[1]],
pain_matrix[, pair[2]],
paired = TRUE,
exact = FALSE
)$p.value
}, numeric(1))
repeated_pairwise <- data.frame(
比较 = vapply(time_pairs, paste, collapse = " vs ", FUN.VALUE = character(1)),
原始p值 = raw_p,
Holm调整p值 = p.adjust(raw_p, method = "holm"),
check.names = FALSE
)
knitr::kable(
repeated_pairwise,
digits = 3,
caption = "三个时点配对秩检验的多重性校正"
)| 比较 | 原始p值 | Holm调整p值 |
|---|---|---|
| 基线 vs 第4周 | 0 | 0 |
| 基线 vs 第12周 | 0 | 0 |
| 第4周 vs 第12周 | 0 | 0 |
随机试验有基线测量时 两组随机试验通常不应只比较各组内部前后 p 值。对于连续随访结局,预先规定的基线调整 ANCOVA 常能直接比较组间随访均值、提高精度并处理偶然基线不平衡;若有多个随访时点,再考虑纵向模型。
若 80 名患者中 62 名达到治疗反应,可比较真实反应比例是否为预设值
0.70。binom.test()
使用精确二项分布,prop.test()
给出大样本得分型近似;两者的区间和 p 值可能略有不同。
x_response <- 62
n_response <- 80
exact_proportion <- binom.test(x_response, n_response, p = 0.70)
score_proportion <- prop.test(
x_response,
n_response,
p = 0.70,
correct = FALSE
)
data.frame(
方法 = c("精确二项", "得分近似"),
观察比例 = x_response / n_response,
CI下限 = c(exact_proportion$conf.int[1], score_proportion$conf.int[1]),
CI上限 = c(exact_proportion$conf.int[2], score_proportion$conf.int[2]),
p值 = c(exact_proportion$p.value, score_proportion$p.value),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "单个反应比例的两种推断方法")| 方法 | 观察比例 | CI下限 | CI上限 | p值 |
|---|---|---|---|---|
| 精确二项 | 0.775 | 0.668 | 0.861 | 0.179 |
| 得分近似 | 0.775 | 0.672 | 0.853 | 0.143 |
二分类结局不能只报告卡方 p 值。随机试验或队列研究中,风险差(RD)和风险比(RR)通常更直接;病例对照研究由于按结局抽样,通常以优势比(OR)为主要关联尺度。
response_table <- table(trial_data$treatment, trial_data$response)
response_table |>
addmargins() |>
knitr::kable(caption = "治疗组与 12 周反应的 2×2 表")| 否 | 是 | Sum | |
|---|---|---|---|
| 标准治疗 | 67 | 93 | 160 |
| 新治疗 | 49 | 111 | 160 |
| Sum | 116 | 204 | 320 |
response_risks <- prop.table(response_table, margin = 1)[, "是"]
data.frame(
治疗组 = names(response_risks),
反应风险 = unname(response_risks)
) |>
knitr::kable(digits = 3, caption = "各组观察到的反应风险")| 治疗组 | 反应风险 |
|---|---|
| 标准治疗 | 0.581 |
| 新治疗 | 0.694 |
chi_result <- chisq.test(response_table, correct = FALSE)
prop_result <- prop.test(
x = response_table[, "是"],
n = rowSums(response_table),
correct = FALSE
)
data.frame(
方法 = c("Pearson 卡方检验", "两样本比例检验"),
统计量 = c(unname(chi_result$statistic), unname(prop_result$statistic)),
自由度 = c(unname(chi_result$parameter), unname(prop_result$parameter)),
p值 = c(chi_result$p.value, prop_result$p.value),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "两独立比例的总体检验")| 方法 | 统计量 | 自由度 | p值 |
|---|---|---|---|
| Pearson 卡方检验 | 4.38 | 1 | 0.036 |
| 两样本比例检验 | 4.38 | 1 | 0.036 |
| 指标 | 估计值 | CI下限 | CI上限 |
|---|---|---|---|
| 新治疗风险 | 0.694 | NA | NA |
| 标准治疗风险 | 0.581 | NA | NA |
| 风险差 | 0.112 | 0.008 | 0.217 |
| 风险比 | 1.194 | 1.010 | 1.411 |
| 优势比 | 1.632 | 1.030 | 2.585 |
风险差的零值是 0,风险比和优势比的零效应值是 1。上述手工区间用于演示;稀疏数据、聚类资料或调整后分析应使用与设计和模型匹配的方法。
卡方近似依赖期望频数足够支持渐近分布,不能用“总样本量小于 30”这样的固定口号判断。先查看期望频数和稀疏程度:
| 否 | 是 | |
|---|---|---|
| 标准治疗 | 58 | 102 |
| 新治疗 | 58 | 102 |
cramers_v <- sqrt(
unname(chi_result$statistic) /
(sum(response_table) * min(nrow(response_table) - 1, ncol(response_table) - 1))
)
data.frame(效应量 = "Cramér's V", 估计值 = cramers_v) |>
knitr::kable(digits = 3, caption = "分类关联的标准化效应量")| 效应量 | 估计值 |
|---|---|
| Cramér’s V | 0.117 |
罕见严重不良事件常产生小单元格。Fisher 精确检验可避免依赖大样本卡方近似,但仍需报告原始分母、绝对风险与效应区间。
rare_event_table <- matrix(
c(1, 39, 7, 33),
nrow = 2,
byrow = TRUE,
dimnames = list(
治疗 = c("新治疗", "标准治疗"),
严重不良事件 = c("是", "否")
)
)
fisher_result <- fisher.test(rare_event_table)
knitr::kable(
addmargins(rare_event_table),
caption = "稀疏严重不良事件 2×2 表"
)| 是 | 否 | Sum | |
|---|---|---|---|
| 新治疗 | 1 | 39 | 40 |
| 标准治疗 | 7 | 33 | 40 |
| Sum | 8 | 72 | 80 |
data.frame(
条件优势比估计 = unname(fisher_result$estimate),
CI下限 = unname(fisher_result$conf.int[1]),
CI上限 = unname(fisher_result$conf.int[2]),
p值 = fisher_result$p.value,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "Fisher 精确检验")| 条件优势比估计 | CI下限 | CI上限 | p值 |
|---|---|---|---|
| 0.124 | 0.003 | 1.04 | 0.057 |
fisher.test() 返回的条件最大似然 OR
估计可能与简单交叉乘积
略有不同。零单元格还可能使简单 OR 为 0
或无穷;不应只为得到有限数字而随意加
0.5,除非分析方法已预先说明并有合理依据。
同一患者干预前后是否有症状是配对二分类资料。McNemar 检验只使用两类不一致配对:前有后无与前无后有。
| 无症状 | 有症状 | Sum | |
|---|---|---|---|
| 无症状 | 49 | 6 | 55 |
| 有症状 | 45 | 40 | 85 |
| Sum | 94 | 46 | 140 |
mcnemar_result <- mcnemar.test(paired_symptom, correct = TRUE)
b <- paired_symptom["无症状", "有症状"]
c <- paired_symptom["有症状", "无症状"]
exact_mcnemar <- binom.test(b, b + c, p = 0.5)
data.frame(
方法 = c("McNemar 卡方近似(连续性校正)", "不一致配对的精确二项检验"),
统计量或计数 = c(unname(mcnemar_result$statistic), b),
p值 = c(mcnemar_result$p.value, exact_mcnemar$p.value),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "配对二分类结局的检验")| 方法 | 统计量或计数 | p值 |
|---|---|---|
| McNemar 卡方近似(连续性校正) | 28.3 | 0 |
| 不一致配对的精确二项检验 | 6.0 | 0 |
before_risk <- mean(symptom_before)
after_risk <- mean(symptom_after)
paired_risk_difference <- after_risk - before_risk
matched_or <- b / c
data.frame(
指标 = c("干预前症状比例", "干预后症状比例", "后 − 前风险差", "不一致配对优势比"),
估计值 = c(before_risk, after_risk, paired_risk_difference, matched_or)
) |>
knitr::kable(digits = 3, caption = "配对二分类资料的描述性效应")| 指标 | 估计值 |
|---|---|
| 干预前症状比例 | 0.607 |
| 干预后症状比例 | 0.329 |
| 后 − 前风险差 | -0.279 |
| 不一致配对优势比 | 0.133 |
若不一致对很少,应优先报告精确结果,并诚实呈现宽区间。配对 OR 只描述不一致对方向,不能与独立 2×2 表 OR 混为一谈。
当剂量水平有科学上预先规定的顺序,prop.trend.test()
可检验比例是否呈线性趋势。它比无序卡方检验更聚焦,但不能发现所有非线性模式。
responders_by_dose <- c(21, 31, 43)
total_by_dose <- c(60, 60, 60)
trend_result <- prop.trend.test(
responders_by_dose,
total_by_dose,
score = c(0, 1, 2)
)
data.frame(
剂量 = c("安慰剂", "低剂量", "高剂量"),
反应人数 = responders_by_dose,
总人数 = total_by_dose,
反应比例 = responders_by_dose / total_by_dose
) |>
knitr::kable(digits = 3, caption = "按有序剂量分组的反应比例")| 剂量 | 反应人数 | 总人数 | 反应比例 |
|---|---|---|---|
| 安慰剂 | 21 | 60 | 0.350 |
| 低剂量 | 31 | 60 | 0.517 |
| 高剂量 | 43 | 60 | 0.717 |
data.frame(
趋势卡方 = unname(trend_result$statistic),
自由度 = unname(trend_result$parameter),
p值 = trend_result$p.value,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "Cochran–Armitage 型比例趋势检验")| 趋势卡方 | 自由度 | p值 |
|---|---|---|
| 16.2 | 1 | 0 |
若治疗与结局的 2×2 表按中心或一个预先规定的混杂因素分层,可用 Mantel–Haenszel 方法估计共同 OR 并检验条件关联。它假定各层 OR 足够相似;若效应在各层明显不同,一个共同 OR 会掩盖效应修饰。
mh_array <- array(
c(
28, 22, 19, 31,
34, 16, 25, 25,
19, 31, 13, 37
),
dim = c(2, 2, 3),
dimnames = list(
治疗 = c("新治疗", "标准治疗"),
反应 = c("是", "否"),
中心 = c("中心A", "中心B", "中心C")
)
)
mh_result <- mantelhaen.test(mh_array)
data.frame(
共同优势比 = unname(mh_result$estimate),
CI下限 = unname(mh_result$conf.int[1]),
CI上限 = unname(mh_result$conf.int[2]),
p值 = mh_result$p.value,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "按研究中心分层的 Mantel–Haenszel 分析")| 共同优势比 | CI下限 | CI上限 | p值 |
|---|---|---|---|
| 1.98 | 1.24 | 3.18 | 0.007 |
一项研究让每名患者先后接受两种快速检测,并比较两种检测的阳性率。应使用 Pearson 卡方检验还是 McNemar 检验?
答案:McNemar 检验。 两种检测来自同一名患者,结果配对。普通 Pearson 卡方检验会错误地把两组结果视为独立。若研究目标是比较灵敏度,则分析还必须限于按参考标准确诊者,并继续保留配对结构。当各参与者随访时间不同,单纯比较“发生过事件的比例”可能丢失信息。若关注事件发生速率,应报告事件数、总风险人时和明确单位下的发病率。
假设新治疗组在 410 人年中发生 18 次事件,标准治疗组在 395 人年中发生 31 次事件:
event_counts <- c(新治疗 = 18, 标准治疗 = 31)
person_years <- c(新治疗 = 410, 标准治疗 = 395)
rate_result <- poisson.test(event_counts, person_years)
rate_table <- data.frame(
组别 = names(event_counts),
事件数 = unname(event_counts),
人年 = unname(person_years),
每100人年发生率 = 100 * unname(event_counts / person_years)
)
knitr::kable(
rate_table,
digits = 2,
caption = "两组事件数、人时和发生率"
)| 组别 | 事件数 | 人年 | 每100人年发生率 |
|---|---|---|---|
| 新治疗 | 18 | 410 | 4.39 |
| 标准治疗 | 31 | 395 | 7.85 |
data.frame(
率比 = unname(rate_result$estimate),
CI下限 = unname(rate_result$conf.int[1]),
CI上限 = unname(rate_result$conf.int[2]),
p值 = rate_result$p.value,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "两样本精确 Poisson 率检验")| 率比 | CI下限 | CI上限 | p值 | |
|---|---|---|---|---|
| 新治疗 | 0.559 | 0.295 | 1.03 | 0.062 |
请核对 poisson.test() 的率比方向与输入顺序一致。简单
Poisson
方法假定事件按与人时相匹配的过程发生,且没有未处理的过度离散、患者内复发相关性或聚类。若同一患者可反复发生事件,或需调整年龄、中心和随访特征,应考虑
Poisson/负二项回归、稳健方差或专门的复发事件模型。
相关分析描述两个变量共同变化的程度:
plot(
trial_data$age,
trial_data$baseline_sbp,
pch = 16,
cex = 0.7,
col = grDevices::adjustcolor(palette_test[["blue"]], alpha.f = 0.45),
xlab = "年龄(岁)",
ylab = "基线收缩压(mmHg)"
)
abline(
lm(baseline_sbp ~ age, data = trial_data),
col = palette_test[["vermillion"]],
lwd = 2.5
)基线年龄与收缩压的散点图及线性拟合。
pearson_result <- cor.test(
trial_data$age,
trial_data$baseline_sbp,
method = "pearson"
)
spearman_result <- cor.test(
trial_data$age,
trial_data$baseline_sbp,
method = "spearman",
exact = FALSE
)
kendall_result <- cor.test(
trial_data$age,
trial_data$baseline_sbp,
method = "kendall",
exact = FALSE
)
data.frame(
方法 = c("Pearson", "Spearman", "Kendall"),
相关估计 = c(
unname(pearson_result$estimate),
unname(spearman_result$estimate),
unname(kendall_result$estimate)
),
CI下限 = c(pearson_result$conf.int[1], NA, NA),
CI上限 = c(pearson_result$conf.int[2], NA, NA),
p值 = c(
pearson_result$p.value,
spearman_result$p.value,
kendall_result$p.value
),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "三种常用相关分析")| 方法 | 相关估计 | CI下限 | CI上限 | p值 |
|---|---|---|---|---|
| Pearson | 0.001 | -0.109 | 0.111 | 0.985 |
| Spearman | -0.006 | NA | NA | 0.918 |
| Kendall | -0.003 | NA | NA | 0.935 |
base R 为 Pearson 相关直接给出常用置信区间;若需要秩相关区间,可使用与抽样设计匹配的自助法或专门软件。相关系数接近 0 只排除相应类型的关联,不排除所有关系。
相关不等于一致,也不等于因果 两台仪器即使 Pearson 相关接近 1,也可能存在固定偏差或随测量水平变化的偏差。评价连续测量一致性应结合差值图、Bland–Altman 界限或适当 ICC;分类判定一致性可考虑 kappa。相关还可能由混杂、范围限制、选择机制或共同时间趋势产生,不能单凭 p 值解释为因果关系。
生存资料同时包含事件是否发生、事件或删失发生的时间以及随时间变化的风险集。把删失者当作“无事件”,或只比较观察到事件者的平均时间,都会错误使用信息。
log-rank 检验比较两组完整生存曲线,在比例风险型差异下通常有较好效率。其零假设可理解为随访期间各事件时点的观察事件数与两组生存经历相同的预期相容。
survival_object <- survival::Surv(
survival_data$time_months,
survival_data$event
)
km_fit <- survival::survfit(
survival_object ~ group,
data = survival_data
)
logrank_result <- survival::survdiff(
survival_object ~ group,
data = survival_data
)
logrank_p <- pchisq(
logrank_result$chisq,
df = length(logrank_result$n) - 1,
lower.tail = FALSE
)
data.frame(
组别 = names(logrank_result$n),
样本量 = unname(logrank_result$n),
观察事件 = unname(logrank_result$obs),
零假设期望事件 = unname(logrank_result$exp)
) |>
knitr::kable(digits = 2, caption = "log-rank 检验的观察与期望事件数")| 组别 | 样本量.Var1 | 样本量.Freq | 观察事件 | 零假设期望事件 |
|---|---|---|---|---|
| group=标准治疗 | A | 130 | 86 | 63 |
| group=新治疗 | B | 130 | 61 | 84 |
data.frame(
卡方统计量 = unname(logrank_result$chisq),
自由度 = length(logrank_result$n) - 1,
p值 = logrank_p,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "两组生存曲线的 log-rank 检验")| 卡方统计量 | 自由度 | p值 |
|---|---|---|
| 15.1 | 1 | 0 |
plot(
km_fit,
col = c(palette_test[["navy"]], palette_test[["vermillion"]]),
lwd = 2.5,
mark.time = TRUE,
xlab = "随访时间(月)",
ylab = "无事件概率",
conf.int = FALSE
)
legend(
"bottomleft",
legend = levels(survival_data$group),
col = c(palette_test[["navy"]], palette_test[["vermillion"]]),
lwd = 2.5,
bty = "n"
)两组 Kaplan–Meier 生存曲线;短线表示删失。
km_12 <- summary(km_fit, times = 12, extend = TRUE)
km_12_table <- data.frame(
组别 = sub("group=", "", km_12$strata),
时间月 = km_12$time,
在险人数 = km_12$n.risk,
无事件概率 = km_12$surv,
CI下限 = km_12$lower,
CI上限 = km_12$upper
)
knitr::kable(
km_12_table,
digits = 3,
caption = "12 个月 Kaplan–Meier 无事件概率"
)| 组别 | 时间月 | 在险人数 | 无事件概率 | CI下限 | CI上限 |
|---|---|---|---|---|---|
| 标准治疗 | 12 | 40 | 0.423 | 0.342 | 0.522 |
| 新治疗 | 12 | 57 | 0.656 | 0.576 | 0.745 |
cox_model <- survival::coxph(
survival::Surv(time_months, event) ~ group,
data = survival_data
)
cox_ci <- exp(confint(cox_model))
data.frame(
比较 = "新治疗 vs 标准治疗",
风险率比 = exp(coef(cox_model)),
CI下限 = cox_ci[1, 1],
CI上限 = cox_ci[1, 2],
p值 = summary(cox_model)$coefficients[1, "Pr(>|z|)"],
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "未调整 Cox 模型的风险率比")| 比较 | 风险率比 | CI下限 | CI上限 | p值 | |
|---|---|---|---|---|---|
| group新治疗 | 新治疗 vs 标准治疗 | 0.523 | 0.375 | 0.73 | 0 |
log-rank 检验本身不提供效应大小,所以应同时给出 Kaplan–Meier 绝对生存概率、时间点、风险集和区间;若报告恒定 HR,则需检查比例风险假设。曲线明显交叉时,单个 log-rank p 值或 HR 可能难以概括差异,可考虑限制平均生存时间等与问题相符的估计量。
更多关于时间零点、删失、比例风险诊断、Cox PH 与 AFT 模型的内容,可继续阅读项目中的生存分析专题。
这里的“统计检验”不要与“诊断试验”混淆。评价一个检测需要预先定义目标人群、参考标准、阈值、时间关系以及不确定性。
diagnostic_table <- matrix(
c(86, 14, 27, 173),
nrow = 2,
byrow = TRUE,
dimnames = list(
参考标准 = c("患病", "未患病"),
新检测 = c("阳性", "阴性")
)
)
tp <- diagnostic_table["患病", "阳性"]
fn <- diagnostic_table["患病", "阴性"]
fp <- diagnostic_table["未患病", "阳性"]
tn <- diagnostic_table["未患病", "阴性"]
sensitivity_ci <- binom.test(tp, tp + fn)$conf.int
specificity_ci <- binom.test(tn, tn + fp)$conf.int
ppv_ci <- binom.test(tp, tp + fp)$conf.int
npv_ci <- binom.test(tn, tn + fn)$conf.int
knitr::kable(
addmargins(diagnostic_table),
caption = "新检测与参考标准的交叉表"
)| 阳性 | 阴性 | Sum | |
|---|---|---|---|
| 患病 | 86 | 14 | 100 |
| 未患病 | 27 | 173 | 200 |
| Sum | 113 | 187 | 300 |
diagnostic_metrics <- data.frame(
指标 = c("灵敏度", "特异度", "阳性预测值", "阴性预测值"),
估计值 = c(
tp / (tp + fn),
tn / (tn + fp),
tp / (tp + fp),
tn / (tn + fn)
),
CI下限 = c(
sensitivity_ci[1], specificity_ci[1], ppv_ci[1], npv_ci[1]
),
CI上限 = c(
sensitivity_ci[2], specificity_ci[2], ppv_ci[2], npv_ci[2]
)
)
knitr::kable(
diagnostic_metrics,
digits = 3,
caption = "诊断性能及精确二项 95% 置信区间"
)| 指标 | 估计值 | CI下限 | CI上限 |
|---|---|---|---|
| 灵敏度 | 0.860 | 0.776 | 0.921 |
| 特异度 | 0.865 | 0.810 | 0.909 |
| 阳性预测值 | 0.761 | 0.672 | 0.836 |
| 阴性预测值 | 0.925 | 0.878 | 0.958 |
灵敏度和特异度按参考标准分层;阳性和阴性预测值则以检测结果为条件,并随目标人群患病率变化。病例对照式抽样可估计灵敏度和特异度,但样本中的人为病例比例通常不能直接用于估计临床场景下的 PPV 和 NPV。
如果同一患者接受两种检测,比较阳性率、灵敏度或特异度时是配对设计。例如比较灵敏度时,应在参考标准确诊者中对两项检测的成对结果使用 McNemar 思路,而不是把两项检测当作两组独立样本。
最低限度的数据审核应包括:
data.frame(
变量 = names(trial_data),
类型 = vapply(trial_data, function(x) class(x)[1], character(1)),
缺失数 = vapply(trial_data, function(x) sum(is.na(x)), integer(1)),
唯一值数 = vapply(trial_data, function(x) length(unique(x)), integer(1)),
check.names = FALSE
) |>
knitr::kable(caption = "模拟试验数据的基本完整性检查")| 变量 | 类型 | 缺失数 | 唯一值数 | |
|---|---|---|---|---|
| participant_id | participant_id | character | 0 | 320 |
| treatment | treatment | factor | 0 | 2 |
| age | age | numeric | 0 | 56 |
| sex | sex | factor | 0 | 2 |
| baseline_sbp | baseline_sbp | numeric | 0 | 243 |
| followup_sbp | followup_sbp | numeric | 0 | 251 |
| sbp_reduction | sbp_reduction | numeric | 0 | 212 |
| crp_week12 | crp_week12 | numeric | 0 | 261 |
| response | response | factor | 0 | 2 |
| adverse_event | adverse_event | factor | 0 | 2 |
异常值不能因为“删掉后显著”就删除。应先查明它是录入错误、测量错误、符合方案但罕见的真实值,还是代表目标人群之外的观察;保留或排除规则应基于数据质量和研究方案,并用敏感性分析展示影响。
t 检验或 ANOVA 的相关正态假设针对模型误差、组内分布或配对差值,而不是把所有组混在一起后的结局。应结合 Q–Q 图、样本量、偏态程度、异常值以及目标估计量判断。
anova_residuals <- residuals(traditional_anova)
qqnorm(
anova_residuals,
pch = 16,
col = grDevices::adjustcolor(palette_test[["blue"]], alpha.f = 0.55),
main = "ANOVA 残差 Q–Q 图"
)
qqline(anova_residuals, col = palette_test[["vermillion"]], lwd = 2.5)三组 ANOVA 残差的正态 Q–Q 图。
shapiro_result <- shapiro.test(anova_residuals)
fligner_result <- fligner.test(
hemoglobin_change ~ dose,
data = dose_data
)
data.frame(
检查 = c("Shapiro–Wilk 残差正态性检验", "Fligner–Killeen 方差齐性检验"),
统计量 = c(
unname(shapiro_result$statistic),
unname(fligner_result$statistic)
),
p值 = c(shapiro_result$p.value, fligner_result$p.value),
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "两个常见假设检查检验")| 检查 | 统计量 | p值 |
|---|---|---|
| Shapiro–Wilk 残差正态性检验 | 0.99 | 0.235 |
| Fligner–Killeen 方差齐性检验 | 9.05 | 0.011 |
不要把 Shapiro–Wilk 当作自动开关 小样本时该检验可能无法发现重要偏离,大样本时又可能对无实质影响的轻微偏离给出很小 p 值。更不应按“先检验正态;若不显著就 t 检验,否则 Wilcoxon”的两阶段规则自动选择,因为两个方法的目标估计量与假设并不相同。
同理,不必先用方差齐性检验决定是否允许 Welch t 检验;Welch 方法通常可直接作为两个独立均值比较的默认。Bartlett 检验对非正态敏感,Fligner–Killeen 更稳健,但任何方差检验都不能替代残差图和领域判断。
若在同一检验族中执行许多独立的 检验,至少出现一个假阳性的概率会增加。多重性不仅来自两两比较,还包括多个结局、时间点、亚组、阈值、模型版本和反复查看数据。
raw_p_values <- c(
主要结局 = 0.012,
次要结局A = 0.041,
次要结局B = 0.007,
次要结局C = 0.18,
探索性结局 = 0.049
)
p_adjust_table <- data.frame(
检验 = names(raw_p_values),
原始p值 = unname(raw_p_values),
Bonferroni = p.adjust(raw_p_values, method = "bonferroni"),
Holm = p.adjust(raw_p_values, method = "holm"),
BH_FDR = p.adjust(raw_p_values, method = "BH"),
check.names = FALSE
)
knitr::kable(
p_adjust_table,
digits = 3,
caption = "三种常见多重性调整示例"
)| 检验 | 原始p值 | Bonferroni | Holm | BH_FDR | |
|---|---|---|---|---|---|
| 主要结局 | 主要结局 | 0.012 | 0.060 | 0.048 | 0.030 |
| 次要结局A | 次要结局A | 0.041 | 0.205 | 0.123 | 0.061 |
| 次要结局B | 次要结局B | 0.007 | 0.035 | 0.035 | 0.030 |
| 次要结局C | 次要结局C | 0.180 | 0.900 | 0.180 | 0.180 |
| 探索性结局 | 探索性结局 | 0.049 | 0.245 | 0.123 | 0.061 |
最有力的做法不是事后寻找最宽松的校正,而是在方案中明确主要结局、主要时间点、主要对比和同一检验族。调整 p 值时,置信区间也应与同一多重性策略一致。
传统双侧优效性检验未拒绝“差异为零”,只表示数据对非零差异的证据不足。它不能证明两种治疗等效,也不能证明不存在临床重要差异。
等效性需要事先给定下、上等效界值,并证明效应区间完全落在该区间内。两个单侧检验(TOST)在显著性水平 下,等价于检查相应的 置信区间是否完全位于等效界值之间。
mean_difference <- mean(new_values) - mean(standard_values)
standard_error <- sqrt(var(new_values) / n1 + var(standard_values) / n0)
welch_df <- (var(new_values) / n1 + var(standard_values) / n0)^2 /
((var(new_values) / n1)^2 / (n1 - 1) +
(var(standard_values) / n0)^2 / (n0 - 1))
lower_equivalence_bound <- -6
upper_equivalence_bound <- 6
t_lower <- (mean_difference - lower_equivalence_bound) / standard_error
t_upper <- (mean_difference - upper_equivalence_bound) / standard_error
p_lower <- pt(t_lower, df = welch_df, lower.tail = FALSE)
p_upper <- pt(t_upper, df = welch_df, lower.tail = TRUE)
tost_p <- max(p_lower, p_upper)
ci90 <- mean_difference +
c(-1, 1) * qt(0.95, df = welch_df) * standard_error
data.frame(
均值差 = mean_difference,
`90%CI下限` = ci90[1],
`90%CI上限` = ci90[2],
等效下界 = lower_equivalence_bound,
等效上界 = upper_equivalence_bound,
TOST_p值 = tost_p,
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "教学性 TOST 等效性示例")| 均值差 | 90%CI下限 | 90%CI上限 | 等效下界 | 等效上界 | TOST_p值 |
|---|---|---|---|---|---|
| 4.55 | 2.97 | 6.13 | -6 | 6 | 0.066 |
等效界值必须来自临床判断、既往证据和方案,而不是为了让区间“刚好放进去”而在看完数据后设置。一个效应可能同时在统计上不同于 0,又在预先规定的宽等效区间内;“有差异”和“差异小到可视为等效”回答的是不同问题。
非劣效性则只关心一个预先规定方向:例如证明新治疗的疗效不比对照低超过界值 。方向、界值、分析人群以及意向治疗和符合方案分析的角色都应事先定义。观察结果后改用单侧检验或重新选择界值会破坏错误率控制。
许多基础检验可写成回归模型:
| 基础检验 | 相应模型视角 | 扩展能力 |
|---|---|---|
| 两独立样本 t 检验 | 只有二分类组别的线性模型 | 加入基线值、协变量、交互和非线性 |
| 单因素 ANOVA | 以组别因子为预测变量的线性模型 | 预设对比、不平衡设计、协变量调整 |
| 两比例/2×2 表分析 | 二元回归模型 | 调整混杂、估计 OR/RR/RD、交互 |
| Poisson 率检验 | 带人时 offset 的 Poisson 模型 | 多变量率比、过度离散和聚类处理 |
| log-rank 检验 | 未调整生存曲线比较 | Cox、AFT、RMST 与协变量调整 |
test_as_model <- lm(sbp_reduction ~ treatment, data = trial_data)
model_ci <- confint(test_as_model)["treatment新治疗", ]
data.frame(
方法 = "线性模型:新治疗 vs 标准治疗",
均值差 = coef(test_as_model)["treatment新治疗"],
CI下限 = model_ci[1],
CI上限 = model_ci[2],
p值 = summary(test_as_model)$coefficients["treatment新治疗", "Pr(>|t|)"],
check.names = FALSE
) |>
knitr::kable(digits = 3, caption = "两组均值比较的线性模型表达")| 方法 | 均值差 | CI下限 | CI上限 | p值 | |
|---|---|---|---|---|---|
| treatment新治疗 | 线性模型:新治疗 vs 标准治疗 | 4.55 | 2.66 | 6.43 | 0 |
这里普通 lm() 的经典标准误对应等方差模型,因此数值不必与
Welch t 检验完全相同。通过合适的方差估计和模型结构可以放宽假设。
当研究包含多因素、中心效应、聚类、非线性、交互、删失或缺失随访时,单个基础检验往往不足。还要牢记:某亚组 p 值小、另一亚组 p 值大,并不证明效应在亚组间不同;应直接估计交互作用及其区间。
样本量应围绕主要目标估计量、最小临床重要差异、预期变异、显著性水平、目标检验效能、分配比例、失访和设计效应事先规划。
continuous_power <- power.t.test(
delta = 5,
sd = 9,
sig.level = 0.05,
power = 0.80,
type = "two.sample",
alternative = "two.sided"
)
binary_power <- power.prop.test(
p1 = 0.55,
p2 = 0.70,
sig.level = 0.05,
power = 0.80,
alternative = "two.sided"
)
data.frame(
场景 = c("两独立均值差 5,SD 9", "两独立比例 0.55 vs 0.70"),
每组所需完整资料人数 = ceiling(c(continuous_power$n, binary_power$n))
) |>
knitr::kable(caption = "简化假设下的样本量示例")| 场景 | 每组所需完整资料人数 |
|---|---|
| 两独立均值差 5,SD 9 | 52 |
| 两独立比例 0.55 vs 0.70 | 163 |
这些函数适用于简化的独立两组设计。配对、聚类、重复测量、生存结局、不等分配、多个主要结局或复杂模型需要专门公式或仿真。计算出的完整资料人数还应按合理失访率膨胀。
不推荐在结果出来后用观察到的效应计算“事后功效”来解释不显著结果,因为它通常只是 p 值的重新表达。此时应查看效应估计和 95% CI:区间是否排除了临床重要获益、伤害或两者都没有?
p 值是在零假设及统计模型假设成立时,得到当前观察结果或更不利于零假设结果的概率。它不是:
固定 时,第一类错误是零假设成立却被拒绝;第二类错误是有研究所定义的效应却未拒绝零假设。检验效能是给定真实效应和设计条件下正确拒绝零假设的概率。
置信区间把效应大小和不确定性放在同一尺度。95% 置信区间的频率学解释是:若按相同设计和程序反复抽样,长期来看约 95% 的这样构造的区间会覆盖真实参数。它不是“这个已得到的区间有 95% 概率包含真实值”。
一段可审核的医学统计结果至少应说明:
p < 0.001,不要写
p = 0);在【人群与设计】中,【组 A】与【组 B】的【结局】分别为【带分母的描述统计】。预先指定方向的【效应尺度】为【估计值与单位】,95% CI【下限,上限】;使用【完整检验名称、单双侧及校正方法】得到 【数值】。分析基于【实际样本量】份完整/可用记录,并受【具体假设、缺失、偏倚或推广限制】影响。
report_difference <- mean(new_values) - mean(standard_values)
report_ci <- -rev(welch_result$conf.int)
report_p <- format_p(welch_result$p.value)
cat(
paste0(
"新治疗组与标准治疗组各纳入 ", n1, " 与 ", n0,
" 人,平均收缩压下降分别为 ",
sprintf("%.1f", mean(new_values)), "(SD ",
sprintf("%.1f", sd(new_values)), ")和 ",
sprintf("%.1f", mean(standard_values)), "(SD ",
sprintf("%.1f", sd(standard_values)), ")mmHg。",
"新治疗减标准治疗的均值差为 ",
sprintf("%.1f", report_difference), " mmHg(95% CI ",
sprintf("%.1f", report_ci[1]), " 至 ",
sprintf("%.1f", report_ci[2]), ";双侧 Welch t 检验 p ",
ifelse(welch_result$p.value < 0.001, "< 0.001", paste0("= ", report_p)),
")。该估计来自模拟完整资料,临床解释还需结合预先规定的最小临床重要差异。"
)
)## 新治疗组与标准治疗组各纳入 160 与 160 人,平均收缩压下降分别为 11.6(SD 8.9)和 7.1(SD 8.2)mmHg。新治疗减标准治疗的均值差为 4.5 mmHg(95% CI 2.7 至 6.4;双侧 Welch t 检验 p < 0.001)。该估计来自模拟完整资料,临床解释还需结合预先规定的最小临床重要差异。
| 错误做法 | 为什么有问题 | 更合适的处理 |
|---|---|---|
p > 0.05 就写“两组相同” |
未拒绝零假设不证明相同 | 报告效应与 CI;等效问题用预设界值 |
| 只写“差异有统计学意义” | 没有方向、大小、单位和精度 | 报告原始尺度效应、CI 与临床意义 |
| 先做 Shapiro 检验再机械选方法 | 两阶段规则忽略目标估计量与稳健性 | 先定问题和设计,结合图形及敏感性分析 |
| 把 Mann–Whitney 称为普遍的中位数检验 | 一般检验的是秩分布 | 说明额外形状假设或使用准确表述 |
| 把配对数据当独立样本 | 忽略患者内相关性 | 使用配对检验或重复测量模型 |
| 对三个时点执行三次未校正检验 | 增加检验族假阳性率 | 先总体模型,再做预设且校正的对比 |
| 因异常值影响显著性便删除 | 形成结果驱动的数据处理 | 查明来源,按方案处理并做敏感性分析 |
| 把 OR 当 RR | 结局常见时二者可差很多 | 按设计报告 RD、RR 或 OR,并写清尺度 |
| RCT 基线变量逐项做显著性检验 | 随机化后差异本来就是随机产生 | 描述基线;按方案基于预后价值调整 |
| 一个亚组显著、另一个不显著就称有交互 | 两个 p 值的差不等于差异的 p 值 | 直接估计交互项及其 CI |
| 看结果后改成单侧检验 | 破坏预设错误率 | 在方案中事先决定方向和理由 |
| 隐藏不同分析的样本量 | 完整案例可能改变样本与目标人群 | 每项分析报告实际分母和缺失 |
| 不断更换检验、阈值或结局直到显著 | 产生选择性报告与不稳定结论 | 预注册主要分析并完整披露探索过程 |
| 把相关当成一致性或因果 | 三者回答不同问题 | 使用一致性方法,并基于设计讨论因果 |
一项随机试验比较两组 8 周 LDL-C 变化,每位参与者只属于一组。两组方差看起来不完全相同,研究问题是平均变化差。最直接的基础检验是什么?主要报告什么?
研究者比较同一患者左眼和右眼的眼压,却使用独立样本 t 检验。核心错误是什么?
三组 ANOVA 的 p 值为 0.003。能否直接写“三组彼此都不同”?
两组各 25 人,一组 0 例严重不良事件,另一组 3 例。为什么不能只报告 Fisher 检验 p 值?
同一批患者在干预前后接受同一二分类检测。哪个检验利用了正确的配对结构?
新仪器与标准仪器的 Pearson 。这是否足以证明两台仪器可以互换?
| 分析目的 | base R 或推荐包代码模式 | 优先报告 |
|---|---|---|
| 单样本均值 | t.test(x, mu = value) |
均值与参考值之差、95% CI |
| 两独立均值 | t.test(y ~ group) |
均值差、95% CI |
| 配对均值 | t.test(after, before, paired = TRUE) |
平均个体差、95% CI |
| 两独立秩分布 | wilcox.test(y ~ group, conf.int = TRUE) |
分布、位置差与 CI |
| 配对秩分布 | wilcox.test(after, before, paired = TRUE) |
差值分布、位置差 |
| 多组均值 | oneway.test(y ~ group, var.equal = FALSE) |
预设组间均值差 |
| 传统 ANOVA | aov(y ~ group) |
总体检验、具体对比与效应量 |
| 多组秩分布 | kruskal.test(y ~ group) |
总体秩差与校正后比较 |
| 完整区组重复测量 | friedman.test(y ~ time \| id) |
时点差异与校正后比较 |
| 单比例 | binom.test(x, n) |
比例与精确 CI |
| 两独立比例 | prop.test(x, n);chisq.test(tab) |
RD、RR 或 OR 与 CI |
| 稀疏 2×2 表 | fisher.test(tab) |
分母、绝对风险、OR 与 CI |
| 配对二分类 | mcnemar.test(tab) |
边际风险差、不一致对 |
| 有序比例趋势 | prop.trend.test(x, n) |
各级比例与趋势检验 |
| 分层 2×2 表 | mantelhaen.test(array) |
分层结果、共同 OR 与 CI |
| 两个发病率 | poisson.test(events, person_time) |
率、率比与 CI |
| Pearson 相关 | cor.test(x, y, method = "pearson") |
与 CI |
| 秩相关 | cor.test(x, y, method = "spearman") |
及适当区间 |
| 生存曲线比较 | survival::survdiff(Surv(time, event) ~ group) |
KM 概率、风险集、效应与 CI |
| 多重 p 值调整 | p.adjust(p, method = "holm") |
调整方法、调整 p 与同时 CI |
| 效应尺度 | 零效应值 | 典型解释 |
|---|---|---|
| 均值差、风险差、率差、相关系数 | 0 | 无相应尺度上的差异或关联 |
| 风险比、率比、优势比、风险率比 | 1 | 两组相对尺度相同 |
| 灵敏度、特异度、预测值 | 无统一“零效应”值 | 与用途、阈值和基准比较 |
发布结果前,请确认:
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Tahoe 26.5.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] C.UTF-8/C.UTF-8/C.UTF-8/C/C.UTF-8/C.UTF-8
##
## time zone: America/Edmonton
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## loaded via a namespace (and not attached):
## [1] digest_0.6.39 R6_2.6.1 fastmap_1.2.0 Matrix_1.7-5
## [5] xfun_0.60 lattice_0.22-9 splines_4.6.1 cachem_1.1.0
## [9] knitr_1.51 htmltools_0.5.9 rmarkdown_2.31 lifecycle_1.0.5
## [13] cli_3.6.6 grid_4.6.1 sass_0.4.10 jquerylib_0.1.4
## [17] compiler_4.6.1 tools_4.6.1 evaluate_1.0.5 bslib_0.12.0
## [21] survival_3.8-6 yaml_2.3.12 rlang_1.3.0 jsonlite_2.0.0