Apache Commons Statistics 1.3 统计计算示例与最佳实践详解
详解 Apache Commons Statistics 1.3 inference 与 interval 模块的假设检验与置信区间实战,覆盖 T 检验、卡方检验、二项检验、G 检验、五种二项置信区间与正态区间,附参数顺序等实测坑与最佳实践。
Apache Commons Statistics 1.3 统计计算示例与最佳实践详解
目标:掌握 Apache Commons Statistics 1.3 中 inference(假设检验)与 interval(置信区间)两个模块的完整用法——T 检验、卡方检验、二项检验、G 检验与五种二项置信区间,并用实测数据总结参数顺序、检验选型与浮点输出等最佳实践。
假设检验回答「差异是否显著」,置信区间回答「参数真实值的可能范围」,两者构成统计推断的两条腿。Commons Statistics 1.3 的 inference 模块提供 TTest、ChiSquareTest、BinomialTest、GTest 等检验类,1.2 起新增的 interval 模块提供五种二项比例置信区间与正态均值/方差区间。本文用一个可运行的演示工程逐个实测,全部 p 值与区间端点均为真实运行输出。描述统计与排名的内容见《Apache Commons Statistics 1.3 常用 API 与统计工具详解》。
一、环境信息
- 操作系统:Ubuntu 24.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1,Eclipse Temurin(
Temurin-17.0.20.1+1) - 构建工具:Apache Maven 3.9.16
- 组件版本:commons-statistics-inference 1.3、commons-statistics-interval 1.3(官方当前最新稳定版)
- Maven 坐标:
org.apache.commons:commons-statistics-inference:1.3、org.apache.commons:commons-statistics-interval:1.3 - 工作目录:
/work/stat-demo
环境确认(实时回显):
java -version 2>&1 && mvn -version | head -4
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment Temurin-17.0.20.1+1 (build 17.0.20.1+1)
OpenJDK 64-Bit Server VM Temurin-17.0.20.1+1 (build 17.0.20.1+1, mixed mode, sharing)
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Eclipse Adoptium, runtime: /opt/java/openjdk
Default locale: en_US, platform encoding: UTF-8
💡 技术拓展:inference 模块的依赖关系(依赖树实测)——inference 传递依赖 distribution 1.3(概率分布,用于计算检验统计量的零分布),distribution 又依赖 rng-sampling 1.6 与 numbers-rootfinder 1.3;因此引入 inference 后这些 jar 自动就位,无需手工管理。 本文所用:inference 的 TTest/ChiSquareTest/BinomialTest/GTest 四个检验类 + interval 的两个置信区间枚举。
二、API 总览
本节给出两个模块的类速查表,后续章节逐类实测。
inference 模块(包 org.apache.commons.statistics.inference)核心类:
| 类 | 职责 | 关键方法 | 详解章节 |
|---|---|---|---|
| TTest | 单样本/双样本/配对 t 检验 | withMu、with、test、pairedTest | 4.1 至 4.3 |
| ChiSquareTest | 拟合优度/列联表卡方检验 | test | 4.4 |
| BinomialTest | 二项检验 | test(参数顺序 n, k, p) | 4.5 |
| GTest | 似然比检验(G 检验) | test | 4.6 |
| AlternativeHypothesis(枚举) | 备择假设 | TWO_SIDED/GREATER_THAN/LESS_THAN | 4.2 |
| DataDispersion(枚举) | 双样本方差假设 | HOMOSCEDASTIC/HETEROSCEDASTIC | 4.2 |
interval 模块(包 org.apache.commons.statistics.interval)核心类:
| 类 | 职责 | 关键方法 | 详解章节 |
|---|---|---|---|
| BinomialConfidenceInterval(枚举) | 二项比例区间,5 种方法 | fromErrorRate(n, k, alpha) | 5.1 |
| NormalConfidenceInterval(枚举) | 正态均值/方差区间 | fromErrorRate(mean, variance, size, alpha) | 5.2 |
| Interval | 区间结果对象 | getLowerBound、getUpperBound | 5.1 |
所有检验的返回类型实现 SignificanceResult 接口,统一提供 getStatistic()(统计量)、getPValue()(p 值)与 reject(alpha)(在显著性水平 alpha 下是否拒绝零假设);TTest 的结果还提供 getDegreesOfFreedom()(自由度)。
三、前置条件:演示工程与主类
本节给出贯穿全文的演示工程。pom.xml 关键依赖(完整工程 /work/stat-demo):
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-statistics-inference</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-statistics-interval</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
编译与运行方式:
cd /work/stat-demo && mvn -q -B package
java -Dfile.encoding=UTF-8 -cp target/statistics-demo.jar InferenceDemo
java -Dfile.encoding=UTF-8 -cp target/statistics-demo.jar IntervalDemo
InferenceDemo.java 主类全文(文件路径 /work/stat-demo/src/main/java/InferenceDemo.java):
import org.apache.commons.statistics.inference.AlternativeHypothesis;
import org.apache.commons.statistics.inference.BinomialTest;
import org.apache.commons.statistics.inference.ChiSquareTest;
import org.apache.commons.statistics.inference.DataDispersion;
import org.apache.commons.statistics.inference.GTest;
import org.apache.commons.statistics.inference.SignificanceResult;
import org.apache.commons.statistics.inference.TTest;
public class InferenceDemo {
public static void main(String[] args) {
oneSampleT();
twoSampleT();
pairedT();
chiSquareGoodness();
chiSquareContingency();
binomial();
gtest();
}
static void oneSampleT() {
System.out.println("== [1] TTest 单样本 t 检验(mu=5,双侧) ==");
double[] data = {9, 2, 5, 4, 12, 7, 8, 11, 9, 3};
TTest.Result r = TTest.withDefaults().withMu(5).test(data);
System.out.println("data = [9, 2, 5, 4, 12, 7, 8, 11, 9, 3], mu = 5");
System.out.println("t 统计量 = " + r.getStatistic());
System.out.println("自由度 = " + r.getDegreesOfFreedom());
System.out.println("p 值(双侧) = " + r.getPValue());
System.out.println("reject(0.05) = " + r.reject(0.05));
}
static void twoSampleT() {
System.out.println("== [2] TTest 独立双样本(默认异方差 Welch / 同方差 pooled) ==");
double[] a = {2, 4, 6, 8, 10};
double[] b = {1, 3, 5, 7, 9};
TTest.Result welch = TTest.withDefaults().test(a, b);
System.out.println("HETEROSCEDASTIC(默认): t=" + welch.getStatistic()
+ " df=" + welch.getDegreesOfFreedom() + " p=" + welch.getPValue());
TTest.Result pooled = TTest.withDefaults()
.with(DataDispersion.HOMOSCEDASTIC).test(a, b);
System.out.println("HOMOSCEDASTIC : t=" + pooled.getStatistic()
+ " df=" + pooled.getDegreesOfFreedom() + " p=" + pooled.getPValue());
TTest.Result greater = TTest.withDefaults()
.with(AlternativeHypothesis.GREATER_THAN).test(a, b);
System.out.println("GREATER_THAN 单侧 : p=" + greater.getPValue()
+ " reject(0.05)=" + greater.reject(0.05));
}
static void pairedT() {
System.out.println("== [3] TTest 配对 t 检验 ==");
double[] before = {88.5, 90.2, 87.1, 91.4, 85.6, 89.8, 90.7, 86.9};
double[] after = {90.4, 91.9, 88.3, 93.0, 87.8, 91.5, 92.6, 88.1};
TTest.Result r = TTest.withDefaults().pairedTest(before, after);
System.out.println("paired t 统计量 = " + r.getStatistic());
System.out.println("自由度 = " + r.getDegreesOfFreedom());
System.out.println("p 值(双侧) = " + r.getPValue());
System.out.println("reject(0.05) = " + r.reject(0.05));
}
static void chiSquareGoodness() {
System.out.println("== [4] ChiSquareTest 拟合优度检验 ==");
double[] expected = {50, 30, 20};
long[] observed = {48, 35, 17};
SignificanceResult r = ChiSquareTest.withDefaults().test(expected, observed);
System.out.println("expected = [50, 30, 20], observed = [48, 35, 17]");
System.out.println("卡方统计量 = " + r.getStatistic());
System.out.println("p 值 = " + r.getPValue());
System.out.println("reject(0.05) = " + r.reject(0.05));
}
static void chiSquareContingency() {
System.out.println("== [5] ChiSquareTest 列联表独立性检验(2x2) ==");
long[][] table = {{30, 20}, {22, 28}};
SignificanceResult r = ChiSquareTest.withDefaults().test(table);
System.out.println("table = [[30, 20], [22, 28]]");
System.out.println("卡方统计量 = " + r.getStatistic());
System.out.println("p 值 = " + r.getPValue());
System.out.println("reject(0.05) = " + r.reject(0.05));
}
static void binomial() {
System.out.println("== [6] BinomialTest 二项检验(掷硬币 100 次成功 55 次,参数顺序 n, k, p) ==");
SignificanceResult two = BinomialTest.withDefaults().test(100, 55, 0.5);
System.out.println("双侧: 统计量=" + two.getStatistic() + " p=" + two.getPValue()
+ " reject(0.05)=" + two.reject(0.05));
SignificanceResult greater = BinomialTest.withDefaults()
.with(AlternativeHypothesis.GREATER_THAN).test(100, 55, 0.5);
System.out.println("GREATER_THAN 单侧: p=" + greater.getPValue()
+ " reject(0.05)=" + greater.reject(0.05));
}
static void gtest() {
System.out.println("== [7] GTest 似然比检验(G 检验) ==");
double[] expected = {50, 30, 20};
long[] observed = {48, 35, 17};
SignificanceResult r = GTest.withDefaults().test(expected, observed);
System.out.println("expected = [50, 30, 20], observed = [48, 35, 17]");
System.out.println("G 统计量 = " + r.getStatistic());
System.out.println("p 值 = " + r.getPValue());
System.out.println("reject(0.05) = " + r.reject(0.05));
}
}
IntervalDemo.java 主类全文(文件路径 /work/stat-demo/src/main/java/IntervalDemo.java):
import org.apache.commons.statistics.interval.BinomialConfidenceInterval;
import org.apache.commons.statistics.interval.Interval;
import org.apache.commons.statistics.interval.NormalConfidenceInterval;
public class IntervalDemo {
public static void main(String[] args) {
System.out.println("== [1] 二项比例置信区间(n=100, k=55, alpha=0.05) ==");
for (BinomialConfidenceInterval method : BinomialConfidenceInterval.values()) {
Interval iv = method.fromErrorRate(100, 55, 0.05);
System.out.printf("%-22s = [%.6f, %.6f]%n", method, iv.getLowerBound(), iv.getUpperBound());
}
System.out.println("== [2] 正态总体置信区间(mean=7, variance=11.555555555555555, n=10, alpha=0.05,第 2 参数为方差) ==");
Interval mean = NormalConfidenceInterval.MEAN.fromErrorRate(7.0, 11.555555555555555, 10, 0.05);
System.out.printf("MEAN = [%.6f, %.6f]%n", mean.getLowerBound(), mean.getUpperBound());
Interval var = NormalConfidenceInterval.VARIANCE.fromErrorRate(7.0, 11.555555555555555, 10, 0.05);
System.out.printf("VARIANCE = [%.6f, %.6f]%n", var.getLowerBound(), var.getUpperBound());
}
}
完整运行输出(实测,分节拆解见第四、五章):
== [1] TTest 单样本 t 检验(mu=5,双侧) ==
data = [9, 2, 5, 4, 12, 7, 8, 11, 9, 3], mu = 5
t 统计量 = 1.860521018838127
自由度 = 9.0
p 值(双侧) = 0.09573390947125937
reject(0.05) = false
== [2] TTest 独立双样本(默认异方差 Welch / 同方差 pooled) ==
HETEROSCEDASTIC(默认): t=0.5 df=8.0 p=0.6305360755569764
HOMOSCEDASTIC : t=0.5 df=8.0 p=0.6305360755569764
GREATER_THAN 单侧 : p=0.3152680377784882 reject(0.05)=false
== [3] TTest 配对 t 检验 ==
paired t 统计量 = -13.717203674463095
自由度 = 7.0
p 值(双侧) = 2.579556955451804E-6
reject(0.05) = true
== [4] ChiSquareTest 拟合优度检验 ==
expected = [50, 30, 20], observed = [48, 35, 17]
卡方统计量 = 1.3633333333333333
p 值 = 0.5057733339556132
reject(0.05) = false
== [5] ChiSquareTest 列联表独立性检验(2x2) ==
table = [[30, 20], [22, 28]]
卡方统计量 = 2.564102564102564
p 值 = 0.10931457620866637
reject(0.05) = false
== [6] BinomialTest 二项检验(掷硬币 100 次成功 55 次,参数顺序 n, k, p) ==
双侧: 统计量=0.55 p=0.368201617326696 reject(0.05)=false
GREATER_THAN 单侧: p=0.184100808663348 reject(0.05)=false
== [7] GTest 似然比检验(G 检验) ==
expected = [50, 30, 20], observed = [48, 35, 17]
G 统计量 = 1.345992511039241
p 值 = 0.510177662787492
reject(0.05) = false
== [1] 二项比例置信区间(n=100, k=55, alpha=0.05) ==
NORMAL_APPROXIMATION = [0.452493, 0.647507]
WILSON_SCORE = [0.452446, 0.643855]
JEFFREYS = [0.452229, 0.644923]
CLOPPER_PEARSON = [0.447280, 0.649680]
AGRESTI_COULL = [0.452429, 0.643872]
== [2] 正态总体置信区间(mean=7, variance=11.555555555555555, n=10, alpha=0.05,第 2 参数为方差) ==
MEAN = [4.568254, 9.431746]
VARIANCE = [5.467133, 38.512963]
四、假设检验示例详解
4.1 TTest 单样本 t 检验
单样本 t 检验回答「样本均值是否等于某给定值 mu」。实测(对应主类 [1] 节):数据 [9, 2, 5, 4, 12, 7, 8, 11, 9, 3](均值 7.0、样本标准差 3.39934634239519)对 mu=5 检验,输出 t 统计量 1.860521018838127、自由度 9.0、双侧 p 值 0.09573390947125937、reject(0.05) 为 false。手算核对:t = (7 − 5) / (3.39934634239519/√10) = 2 / 1.0749 ≈ 1.8606,与库输出一致。结论:在 0.05 水平下不能拒绝「均值为 5」的零假设(p=0.096 未过阈值)。
API 契约:TTest.withDefaults() 创建默认配置(双侧、mu=0),withMu(5) 设置零假设均值,test(double[]) 执行。p 值小于 alpha 才拒绝。
4.2 TTest 独立双样本:Welch 与 pooled
独立双样本比较两组均值。实测(对应主类 [2] 节):{2, 4, 6, 8, 10} 对 {1, 3, 5, 7, 9},默认 HETEROSCEDASTIC(Welch 异方差)与显式 HOMOSCEDASTIC(同方差 pooled)输出完全相同——t=0.5、df=8.0、p=0.6305360755569764。这不是巧合:本例两组方差相等(均为 10),两种方法退化为同一结果。方差不等时两者会显著分化,Welch 自由度会变成非整数(Satterthwaite 近似)。单侧检验 with(AlternativeHypothesis.GREATER_THAN) 实测 p=0.3152680377784882,恰为双侧 0.6305360755569764 的一半,reject(0.05) 为 false。
💡 技术拓展:三种 t 检验形态与两种方差假设——
形态 API 零假设 单样本 test(double[]) + withMu(mu) 总体均值 = mu 独立双样本 test(a, b) 两总体均值相等 配对 pairedTest(before, after) 配对差值均值为 0 方差假设:HETEROSCEDASTIC(默认,Welch,不要求方差相等)对 HOMOSCEDASTIC(pooled,要求方差齐性)。本文所用:默认 HETEROSCEDASTIC,仅在有明确方差齐性证据时改 pooled。
4.3 TTest 配对 t 检验
配对设计(同一对象前后测量)要用 pairedTest 而非独立双样本——它对差值序列做单样本检验,消除个体间差异。实测(对应主类 [3] 节):8 名受试者的 before/after 数据,t 统计量 -13.717203674463095、自由度 7.0、双侧 p 值 2.579556955451804E-6(约 2.6 × 10⁻⁶),reject(0.05) 为 true——处理后分数显著升高(after 系统性高于 before,t 为负表示 before−after 的差值均值为负)。
要点提示:同样的两列数据若误用独立双样本检验,会丢失配对信息导致检验力大幅下降;配对场景 pairedTest 是唯一正确入口。
4.4 ChiSquareTest:拟合优度与列联表
卡方检验比较观测频数与期望频数的偏离。实测拟合优度(对应主类 [4] 节):期望 [50, 30, 20]、观测 [48, 35, 17],卡方统计量 1.3633333333333333、p 值 0.5057733339556132、不拒绝——手算核对:(48−50)²/50 + (35−30)²/30 + (17−20)²/20 = 0.08 + 0.8333 + 0.45 = 1.3633,完全一致。实测 2×2 列联表独立性检验(对应主类 [5] 节):[[30, 20], [22, 28]],卡方统计量 2.564102564102564、p 值 0.10931457620866637、不拒绝——手算核对:期望矩阵 [[26, 24], [26, 24]],四格贡献 0.6154 + 0.6667 + 0.6154 + 0.6667 = 2.5641,一致。
要点提示:test(double[] expected, long[] observed) 的期望传比例或频数均可(库内部按总量归一),观测必须是 long 频数;列联表入口 test(long[][]) 自动作 Pearson 卡方并按 (行−1)×(列−1) 计算自由度。
4.5 BinomialTest:参数顺序是实测坑
二项检验判断「k 次成功 / n 次试验」是否符合成功概率 p 的二项分布。实测正确的调用方式(对应主类 [6] 节):掷硬币 100 次成功 55 次,test(100, 55, 0.5) 双侧 p=0.368201617326696、统计量 0.55(即 k/n)、reject(0.05) 为 false;GREATER_THAN 单侧 p=0.184100808663348。
参数顺序必须先 n(试验数)后 k(成功数)——统计量输出 0.55 = 55/100 印证了这一契约。传反时实测直接抛异常(真实回显):
Exception in thread "main" org.apache.commons.statistics.inference.InferenceException: must have n >= k for binomial coefficient (n, k), got n = 100, k = 55
调用的是 test(55, 100, 0.5)(把成功数放在了第一位),库按「先 n 后 k」解析后检测出 n < k 而拒绝执行。注意报错信息中打印的 n/k 数值与实参书写顺序正好相反,调试时以「先 n 后 k」的参数契约为准,不要按报错字面去改。
💡 技术拓展:与 TTest 不同,BinomialTest 是精确检验(基于二项分布精确计算,非正态近似),小样本下比正态近似更准;
AlternativeHypothesis同样适用于单侧场景(如「新版转化率是否高于旧版」)。 本文所用:双侧默认 + GREATER_THAN 单侧对照。
4.6 GTest:似然比检验
G 检验(对数似然比检验)与卡方拟合优度互为替代,统计量 G = 2ΣO·ln(O/E)。实测(对应主类 [7] 节):与 4.4 节完全相同的数据,G 统计量 1.345992511039241、p 值 0.510177662787492、不拒绝——与卡方的 p=0.5057733339556132 高度接近(两者渐近等价,大样本下趋同)。生物统计等领域偏好 G 检验的乘性可分解性。
要点提示:G 检验要求所有期望频数为正(对数为零无定义);同一数据两个检验 p 值接近属正常现象,结论冲突时优先检查期望频数是否过小(惯例要求期望 ≥ 5)。
五、置信区间示例详解
5.1 二项比例的五种区间方法
interval 模块的 BinomialConfidenceInterval 枚举提供五种区间方法,统一入口 fromErrorRate(int n, int k, double alpha)(注意参数顺序也是先 n 后 k,alpha 是错误率,95% 置信即 alpha=0.05)。实测 n=100、k=55、alpha=0.05(对应主类 [1] 节输出):
| 方法 | 区间(实测) | 特点 |
|---|---|---|
| NORMAL_APPROXIMATION | [0.452493, 0.647507] | 正态近似,最简单,小样本/n·p 过小时偏窄失真 |
| WILSON_SCORE | [0.452446, 0.643855] | Wilson 得分区间,覆盖率稳定,推荐默认 |
| JEFFREYS | [0.452229, 0.644923] | 贝叶斯 Jeffreys 先验(Beta(0.5, 0.5)) |
| CLOPPER_PEARSON | [0.447280, 0.649680] | 精确区间,最保守(最宽),保证覆盖率 |
| AGRESTI_COULL | [0.452429, 0.643872] | Agresti-Coull 加 2 成功 2 失败的修正近似 |
实测五种方法的区间都包含真实比例 0.55,宽度排序为 CLOPPER_PEARSON 最宽(0.2024)、其余四种接近(约 0.1914 至 0.1950)——精确法以宽度换覆盖率。
💡 技术拓展:二项区间的选型逻辑——
场景 建议 通用报告/AB 实验面板 WILSON_SCORE(窄且覆盖率稳定,小比例事件尤其明显) 监管/论文要求「精确方法」 CLOPPER_PEARSON 与教材/既有报表对齐 NORMAL_APPROXIMATION(注意 n·k 与 n·(n−k) 均 ≥ 5 才可靠) 贝叶斯工作流衔接 JEFFREYS 本文所用:五种方法全量实测对齐,生产默认 WILSON_SCORE。
5.2 正态均值/方差区间与「第 2 参数是方差」的实测验证
NormalConfidenceInterval 枚举提供 MEAN 与 VARIANCE 两个区间,入口 fromErrorRate(double mean, double variance, long size, double alpha)。实测(对应主类 [2] 节):均值 7.0、方差 11.555555555555555(即 2.2 节数据的样本方差)、n=10、alpha=0.05,MEAN 区间 [4.568254, 9.431746]、VARIANCE 区间 [5.467133, 38.512963]。
两个验证点:其一,MEAN 区间半宽 = t(0.975, df=9) × √(方差/n) = 2.2622 × 1.07497 = 2.4318,7.0 ± 2.4318 = [4.5682, 9.4318],与实测 [4.568254, 9.431746] 一致,证明小样本下库使用 t 分布而非 z 分布;其二,第二个参数是方差而非标准差——实测曾误传标准差 3.39934634239519,得到的 VARIANCE 区间 [1.608289, 11.329520] 恰是以 3.39934634239519 为方差的卡方向(9 × 3.3993/χ²₀.₉₇₅,₉ 到 9 × 3.3993/χ²₀.₀₂₅,₉),据此反推确认参数语义为方差。使用时务必传入 Variance.getAsDouble() 的结果(无偏样本方差)。
要点提示:区间与检验是一体两面——4.1 节 mu=5 的 t 检验不拒绝(p=0.0957),对应本节 MEAN 区间 [4.568, 9.432] 恰好包含 5,两个结论自洽。
六、最佳实践
本节汇总本次实测验证过的工程实践,按落地优先级排列。
6.1 storeless 流式统计 vs 全量数组统计
| 维度 | 流式(create + accept) | 全量(of / evaluate) |
|---|---|---|
| 内存 | O(1),只存矩状态 | O(n),需持有完整数组 |
| 时效 | 数据到达即可计算,随时取中间结果 | 数据齐后才能算 |
| 精度 | 增量算法( compensated 补偿求和),长序列更稳 | 一次性求和,超大数组易丢精度 |
| 分片合并 | combine 原生支持 | 不支持 |
| 适用 | 消息流、监控埋点、ETL | 离线批、一次性分析 |
实测两种路径对同一 20 数据的均值/方差输出逐位一致(姊妹篇 4.5 节),精度无差异,选择依据是数据到达方式与内存约束。流式场景优先选流式——它在内存与实时性上的优势是结构性的。
6.2 分片并行统计用 combine 汇总
多线程/多分片场景下,每个分片各自维护累积器,最后 combine 汇总(姊妹篇 4.5 节实测:拆成前后各 10 个再合并,均值与方差与全量计算逐位一致)。要点:各分片必须用同一口径(如全部无偏方差);DoubleStatistics 可用 EnumSet.allOf(Statistic.class) 一次开启全部统计量后合并,一次拿到全套指标。
6.3 检验选型与 p 值解读
- 数据形态决定检验入口:配对设计用
pairedTest,独立组用test(a, b),比例/频数用 BinomialTest 或 ChiSquareTest(4.3 节实测了误用代价:检验力下降)。 - 单侧检验要显式
with(AlternativeHypothesis.GREATER_THAN),默认双侧(4.2 节实测单侧 p 恰为双侧一半)。 reject(0.05)返回 boolean 与「p < 0.05」等价,建议直接用它表达决策,避免手写比较引入反向错误。- p 值不是效应大小:4.4 节 p=0.506 只说明「与期望无显著偏离」,不代表观测与期望完全一致。
6.4 API 契约自查:先用 javap 再编码
本次实测抓到的两个参数顺序坑(BinomialTest 的 n/k、NormalConfidenceInterval 的方差)都源于「想当然的参数顺序」。推荐工作流:引入新类后先 javap -cp <jar> <全限定类名> 查看真实方法签名,再用小数据(能手算核对)跑通后集成进业务。所有检验类遵循同一结果契约(SignificanceResult:getStatistic/getPValue/reject),掌握一个即掌握全部。
6.5 浮点输出与容差比较
所有统计量与 p 值都是 double 全精度输出(如 0.368201617326696、2.579556955451804E-6),落库/上报前不要在计算层截断;跨系统对数(如与 R、SciPy 比对)用相对容差(如 1e-9)而非字符串全等。检验统计量的自由度是 double(Welch 检验可为非整数),格式化时按需保留。
七、常见问题(FAQ)
Q1:reject(0.05) 与「p 值小于 0.05」一定等价吗?
等价。reject 的实现就是 p < alpha 的严格比较(4.2 节实测单侧 p=0.3153 与 reject=false 的对应关系一致)。它存在的意义是表达决策语义,避免使用者在比较方向上犯错。
Q2:BinomialTest 报 must have n >= k 但我的 n 明明大于 k?
参数顺序传反了。正确契约是 test(int n, int k, double p)——先试验数后成功数(4.5 节实测 statistic=0.55=55/100 印证)。注意报错信息打印的 n/k 数值与实参书写顺序相反,按契约修正实参顺序即可。
Q3:正态置信区间的第二个参数为什么算出来不对?
那是方差不是标准差(5.2 节实测反推验证)。传入 Variance 统计量的无偏结果;若手头只有标准差,先平方再传入。
Q4:卡方检验要求期望频数至少多大? 惯例是每格期望 ≥ 5(4.4 节期望 50/30/20 均满足)。不满足时合并相邻类别,或改用 Fisher 精确检验类思路(inference 1.3 提供 FisherExactTest 用于列联表精确检验)。
Q5:双样本 t 检验的 df 为什么不是 n1+n2−2?
默认 HETEROSCEDASTIC(Welch)用 Satterthwaite 近似,df 一般是非整数;本例两组方差相等才恰好等于 8.0(4.2 节实测)。方差齐性明确时用 with(DataDispersion.HOMOSCEDASTIC) 回到经典 pooled 的 df=n1+n2−2。
Q6:检验类是线程安全的吗?可以复用同一个 TTest 实例吗?
TTest.withDefaults() 返回的配置对象建议按「一次配置 + 一次 test」使用,跨线程共享配置对象前自行保证可见性;统计累积器(descriptive 模块)本身可变,多线程分片累积后用 combine 合并(6.2 节),不要多线程并发 accept 同一实例。
八、参考链接
- Commons Statistics inference 模块用户指南:https://commons.apache.org/proper/commons-statistics/
- Commons Statistics interval 模块(1.2 新增):https://commons.apache.org/proper/commons-statistics/
- Maven 中央仓库 inference 1.3:https://repo1.maven.org/maven2/org/apache/commons/commons-statistics-inference/
- Maven 中央仓库 interval 1.3:https://repo1.maven.org/maven2/org/apache/commons/commons-statistics-interval/
- 描述统计与排名姊妹篇:《Apache Commons Statistics 1.3 常用 API 与统计工具详解》