Apache Commons Math 3.6.1 API 与统计代数详解
系统讲解 Apache Commons Math 3.6.1 的统计 API(DescriptiveStatistics、SummaryStatistics、TTest)与线性代数 API(Array2DRowRealMatrix、LUDecomposition、EigenDecomposition、RealVector),逐项代码配真实输出,并给出与 math4 的命名差异锚点,全部已在 Ubuntu 22.04 实测。
Apache Commons Math 3.6.1 API 与统计代数详解
目标:系统掌握 Apache Commons Math 3.6.1 的两大核心 API 面——统计(DescriptiveStatistics、SummaryStatistics、TTest)与线性代数(Array2DRowRealMatrix、LUDecomposition、EigenDecomposition、RealVector),每个方法都配完整示例代码与实测输出,并标注与 math4 的命名差异。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)、OpenJDK 17.0.20.1 实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1(
java -version实测) - 构建工具:Apache Maven 3.6.3
- 目标库:Apache Commons Math 3.6.1(
org.apache.commons:commons-math3:3.6.1,2016-03-21 发布后的最终版本) - 传递依赖:无(零传递依赖,
mvn dependency:tree实测仅自身一个节点) - Maven 示例工程目录:
/opt/math-demo(本文演示类demo.MathDemo在该工程内编译运行)
确认 JDK 与 Maven 版本基线:
java -version 2>&1
mvn -v | head -3
输出:
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
💡 说明:本文所有运行命令统一为
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo <组名>形式($M3在第三章赋值为本地仓库 jar 全路径);-Dfile.encoding=UTF-8用于 POSIX/C locale 下中文输出不乱码,数字结果不受影响。
二、API 总览
Commons Math 3.6.1 的 API 按「包 → 门面类 → 方法」组织,统计与线性代数分别位于 org.apache.commons.math3.stat 与 org.apache.commons.math3.linear。本文覆盖的核心 API 如下(第一列即调用入口,均经本文实测):
| API | 包 | 角色 | 关键方法 | 详解章节 |
|---|---|---|---|---|
| DescriptiveStatistics | stat.descriptive | 有存储的描述统计(支持滑动窗口) | addValue、getMean、getVariance、getStandardDeviation、getMax、getMin、getPercentile、getSum、getN、removeMostRecentValue | 4.1、4.2 |
| SummaryStatistics | stat.descriptive | 无存储的汇总统计(常数内存) | addValue、getMean、getVariance、getStandardDeviation、getSum、getN | 4.3 |
| TTest | stat.inference | t 检验 | pairedT、pairedTTest、homoscedasticT、homoscedasticTTest | 4.4 |
| Array2DRowRealMatrix | linear | 行存储实矩阵 | multiply、transpose、operate、toString | 5.1 |
| LUDecomposition | linear | LU 分解 | getDeterminant、getSolver | 5.2 |
| DecompositionSolver | linear | 分解求解器 | solve(只收 RealVector/RealMatrix) | 5.2 |
| EigenDecomposition | linear | 特征分解 | getRealEigenvalues、getImagEigenvalues | 5.3 |
| ArrayRealVector / RealVector | linear | 实向量 | add、dotProduct、getNorm、mapMultiply | 5.4 |
⚠️ 注意:3.6.1 是 2016 年的最终版本,与 math4/部分新资料存在 API 名差异(
removeMostRecentValue、solve签名、Fraction方法等),本文各节遇到时都会给出实测锚点,第六章集中汇总。
💡 技术拓展:math3 与 math4 的包结构对照(迁移评估必看,包名决定共存可行性)——
功能 math3 3.6.1 包名 math4 4.0-beta1 包名(所在模块) 描述统计/汇总统计 org.apache.commons.math3.stat.descriptive org.apache.commons.math4.legacy.stat.descriptive(commons-math4-legacy) t 检验等统计推断 org.apache.commons.math3.stat.inference org.apache.commons.math4.legacy.stat.inference(commons-math4-legacy) 线性代数 org.apache.commons.math3.linear org.apache.commons.math4.legacy.linear(commons-math4-legacy) 复数 org.apache.commons.math3.complex org.apache.commons.math4.legacy.complex(commons-math4-legacy) FastMath org.apache.commons.math3.util.FastMath org.apache.commons.math4.legacy.core.jdkmath.AccurateMath(legacy-core,类更名) 极简高精度数学核心 无 org.apache.commons.math4.core.jdkmath(commons-math4-core,实测仅 18 个类) 两代包名不重叠,可同 classpath 共存过渡;迁移时旧代码多数情况只改顶层包名即可编译,
FastMath需改名AccurateMath(jar tf 实测 math4-legacy 内org/apache/commons/math4/legacy/stat/descriptive/DescriptiveStatistics.class存在)。
三、前置条件:工程与演示主类
创建工程目录并写入 /opt/math-demo/pom.xml(全文):
mkdir -p /opt/math-demo/src/main/java/demo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>math-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</build>
</project>
逐项说明:maven.compiler.release=17 按 JDK 17 语义编译(math3 自身是 major=49 的 Java 5 字节码,两者互不影响);maven-compiler-plugin 必须显式固定 3.11.0,否则 Maven 3.6.3 默认绑定的 3.1 版插件不认识 release 属性,会报 Source option 5 is no longer supported。
/opt/math-demo/src/main/java/demo/MathDemo.java 全文(本文所有示例集中在这一个主类,按组演示,通过命令行参数选择组):
package demo;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.commons.math3.stat.inference.TTest;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.EigenDecomposition;
import org.apache.commons.math3.linear.DecompositionSolver;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.primes.Primes;
import org.apache.commons.math3.fraction.Fraction;
public class MathDemo {
public static void main(String[] args) {
String group = args.length > 0 ? args[0] : "all";
if (group.equals("descriptive") || group.equals("all")) descriptive();
if (group.equals("summary") || group.equals("all")) summary();
if (group.equals("linalg") || group.equals("all")) linalg();
if (group.equals("vector") || group.equals("all")) vector();
if (group.equals("complex") || group.equals("all")) complex();
if (group.equals("primes") || group.equals("all")) primes();
if (group.equals("fraction") || group.equals("all")) fraction();
if (group.equals("ttest") || group.equals("all")) ttest();
}
static void descriptive() {
System.out.println("== DescriptiveStatistics(无界 + 滑动窗口) ==");
DescriptiveStatistics ds = new DescriptiveStatistics();
double[] data = {9, 2, 5, 4, 12, 7, 8, 11, 9, 3, 7, 4, 12, 5, 4, 10, 9, 6, 9, 4};
for (double v : data) {
ds.addValue(v);
}
System.out.println("data.length = " + data.length);
System.out.println("getN() = " + ds.getN());
System.out.println("getMean() = " + ds.getMean());
System.out.println("getVariance() = " + ds.getVariance());
System.out.println("getStandardDeviation() = " + ds.getStandardDeviation());
System.out.println("getMax() = " + ds.getMax());
System.out.println("getMin() = " + ds.getMin());
System.out.println("getPercentile(50) = " + ds.getPercentile(50));
System.out.println("getSum() = " + ds.getSum());
System.out.println("-- 窗口大小 5 的滑动统计 --");
DescriptiveStatistics window = new DescriptiveStatistics(5);
for (double v : new double[] {1, 2, 3, 4, 5, 6, 7, 8}) {
window.addValue(v);
System.out.printf("add %.0f -> N=%d mean=%.3f min=%.0f max=%.0f%n",
v, window.getN(), window.getMean(), window.getMin(), window.getMax());
}
window.removeMostRecentValue();
System.out.println("after removeMostRecentValue: N=" + window.getN()
+ " mean=" + window.getMean());
}
static void summary() {
System.out.println("== SummaryStatistics(两个数据集 + 汇总对比) ==");
SummaryStatistics s1 = new SummaryStatistics();
for (double v : new double[] {2, 4, 6, 8, 10}) {
s1.addValue(v);
}
SummaryStatistics s2 = new SummaryStatistics();
for (double v : new double[] {1, 3, 5, 7, 9}) {
s2.addValue(v);
}
SummaryStatistics all = new SummaryStatistics();
for (double v : new double[] {2, 4, 6, 8, 10, 1, 3, 5, 7, 9}) {
all.addValue(v);
}
System.out.printf("set1: N=%d mean=%.3f variance=%.3f sd=%.3f sum=%.1f%n",
s1.getN(), s1.getMean(), s1.getVariance(), s1.getStandardDeviation(), s1.getSum());
System.out.printf("set2: N=%d mean=%.3f variance=%.3f sd=%.3f sum=%.1f%n",
s2.getN(), s2.getMean(), s2.getVariance(), s2.getStandardDeviation(), s2.getSum());
System.out.printf("all : N=%d mean=%.3f variance=%.3f sd=%.3f sum=%.1f%n",
all.getN(), all.getMean(), all.getVariance(), all.getStandardDeviation(), all.getSum());
double weightedMean = (s1.getMean() * s1.getN() + s2.getMean() * s2.getN())
/ (s1.getN() + s2.getN());
System.out.printf("加权平均核对: (mean1*N1 + mean2*N2)/(N1+N2) = %.3f(应等于 all.mean)%n",
weightedMean);
}
static void linalg() {
System.out.println("== 线性代数(Array2DRowRealMatrix / LU / Eigen) ==");
Array2DRowRealMatrix a = new Array2DRowRealMatrix(new double[][] {{2, 3}, {1, 4}});
Array2DRowRealMatrix b = new Array2DRowRealMatrix(new double[][] {{5, 6}, {7, 8}});
System.out.println("A = [[2,3],[1,4]], B = [[5,6],[7,8]]");
System.out.println("A.multiply(B) = " + a.multiply(b));
LUDecomposition lu = new LUDecomposition(a);
System.out.println("LUDecomposition(A).getDeterminant() = " + lu.getDeterminant());
DecompositionSolver solver = lu.getSolver();
double[] x = solver.solve(new ArrayRealVector(new double[] {5, 9})).toArray();
System.out.println("solve A*x=[5,9] -> x = [" + x[0] + ", " + x[1] + "]");
EigenDecomposition eig = new EigenDecomposition(a);
double[] real = eig.getRealEigenvalues();
double[] imag = eig.getImagEigenvalues();
System.out.println("EigenDecomposition(A).getRealEigenvalues() = [" + real[0] + ", " + real[1] + "]");
System.out.println("getImagEigenvalues() = [" + imag[0] + ", " + imag[1] + "]");
}
static void vector() {
System.out.println("== RealVector(ArrayRealVector) ==");
ArrayRealVector v1 = new ArrayRealVector(new double[] {1, 2, 3});
ArrayRealVector v2 = new ArrayRealVector(new double[] {4, 5, 6});
System.out.println("v1 = [1, 2, 3], v2 = [4, 5, 6]");
System.out.println("v1.add(v2) = " + v1.add(v2));
System.out.println("v1.dotProduct(v2) = " + v1.dotProduct(v2));
System.out.println("v1.getNorm() = " + v1.getNorm());
System.out.println("v1.mapMultiply(10) = " + v1.mapMultiply(10));
}
static void complex() {
System.out.println("== Complex ==");
Complex a = new Complex(3, 4);
Complex b = new Complex(1, -2);
System.out.println("a = 3 + 4i, b = 1 - 2i");
System.out.println("a.add(b) = " + a.add(b));
System.out.println("a.subtract(b) = " + a.subtract(b));
System.out.println("a.multiply(b) = " + a.multiply(b));
System.out.println("a.divide(b) = " + a.divide(b));
System.out.println("a.abs() = " + a.abs());
System.out.println("a.pow(2) = " + a.pow(2));
System.out.println("a.conjugate() = " + a.conjugate());
}
static void primes() {
System.out.println("== Primes ==");
System.out.println("isPrime(2) = " + Primes.isPrime(2));
System.out.println("isPrime(97) = " + Primes.isPrime(97));
System.out.println("isPrime(100) = " + Primes.isPrime(100));
System.out.println("isPrime(1) = " + Primes.isPrime(1));
System.out.println("nextPrime(97) = " + Primes.nextPrime(97));
System.out.println("nextPrime(98) = " + Primes.nextPrime(98));
System.out.println("nextPrime(100) = " + Primes.nextPrime(100));
System.out.println("primeFactors(360) = " + Primes.primeFactors(360));
System.out.println("primeFactors(97) = " + Primes.primeFactors(97));
System.out.println("primeFactors(1024)= " + Primes.primeFactors(1024));
}
static void fraction() {
System.out.println("== Fraction ==");
Fraction f1 = new Fraction(1, 3);
Fraction f2 = new Fraction(1, 6);
System.out.println("f1 = " + f1 + ", f2 = " + f2);
System.out.println("f1.add(f2) = " + f1.add(f2));
System.out.println("f1.multiply(f2) = " + f1.multiply(f2));
System.out.println("new Fraction(2,4)(构造自动约分) = " + new Fraction(2, 4)
+ " -> " + new Fraction(2, 4).getNumerator() + "/" + new Fraction(2, 4).getDenominator());
System.out.println("f1.doubleValue() = " + f1.doubleValue());
System.out.println("new Fraction(3,8).doubleValue()*100 = " + new Fraction(3, 8).doubleValue() * 100);
System.out.printf("百分数输出(3/8): %.1f%%%n", new Fraction(3, 8).doubleValue() * 100);
}
static void ttest() {
System.out.println("== TTest(配对 t 检验 / 同方差 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 ttest = new TTest();
System.out.println("pairedT(before, after) = " + ttest.pairedT(before, after));
System.out.println("pairedTTest p 值(双侧) = " + ttest.pairedTTest(before, after));
System.out.println("homoscedasticT(before, after) = " + ttest.homoscedasticT(before, after));
System.out.println("homoscedasticTTest p 值(双侧) = " + ttest.homoscedasticTTest(before, after));
}
}
说明:主类按组拆分方法,第四、五章逐组讲解;complex/primes/fraction 三组的独立拆解见第 4 篇《Apache Commons Math 3.6.1 数值计算示例详解》(同样的源码与输出,按类独立成篇)。
编译并运行(本文所有组共用以下方式):
cd /opt/math-demo && mvn -q compile
echo "EXIT=$?"
M3=~/.m2/repository/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo descriptive
输出:
EXIT=0
== DescriptiveStatistics(无界 + 滑动窗口) ==
data.length = 20
getN() = 20
getMean() = 7.0
getVariance() = 9.368421052631579
getStandardDeviation() = 3.0607876523260447
getMax() = 12.0
getMin() = 2.0
getPercentile(50) = 7.0
getSum() = 140.0
-- 窗口大小 5 的滑动统计 --
add 1 -> N=1 mean=1.000 min=1 max=1
add 2 -> N=2 mean=1.500 min=1 max=2
add 3 -> N=3 mean=2.000 min=1 max=3
add 4 -> N=4 mean=2.500 min=1 max=4
add 5 -> N=5 mean=3.000 min=1 max=5
add 6 -> N=5 mean=4.000 min=2 max=6
add 7 -> N=5 mean=5.000 min=3 max=7
add 8 -> N=5 mean=6.000 min=4 max=8
after removeMostRecentValue: N=4 mean=5.5
验证点:EXIT=0 编译成功;descriptive 组完整跑通,后文 4.1/4.2 节基于此输出逐项解读。
四、统计 API 详解
统计能力集中在 org.apache.commons.math3.stat 包:descriptive 子包提供描述统计(两种门面:有存储的 DescriptiveStatistics 与无存储的 SummaryStatistics),inference 子包提供统计推断(t 检验、卡方检验等,本文实测 t 检验)。
4.1 DescriptiveStatistics:全量数据的描述统计
DescriptiveStatistics 内部保存全部历史值(无界模式),任何统计量都基于全量数据现算。4.1 节开头 descriptive() 的前半段就是它的标准用法:new DescriptiveStatistics() 无参构造后逐个 addValue,再调用各 getter。全部实测方法汇总:
| 方法 | 实测返回 | 含义 |
|---|---|---|
| getN() | 20 | 观测个数 |
| getMean() | 7.0 | 算术均值(总和 140 / 20) |
| getVariance() | 9.368421052631579 | 样本方差(分母 N-1=19;140/19=7.368…? 实测 9.368…,见下方核对) |
| getStandardDeviation() | 3.0607876523260447 | 样本标准差(方差开平方) |
| getMax() | 12.0 | 最大值 |
| getMin() | 2.0 | 最小值 |
| getPercentile(50) | 7.0 | 中位数 |
| getSum() | 140.0 | 总和 |
核对:20 个数据求和 140(getSum 一致);偏差平方和 Σ(x-7)²=178,样本方差 178/19=9.368421052631579,与实测一致——注意 getVariance() 返回的是样本方差(除以 N-1),若需总体方差(除以 N,本例为 178/20=8.9)用 getPopulationVariance();标准差 3.0607876523260447 = 9.368421052631579 的平方根,一致。getPercentile(50) 用分位数插值算法求中位数,本例得 7.0。
4.2 DescriptiveStatistics:滑动窗口统计
new DescriptiveStatistics(5) 构造的是窗口上限为 5 的滑动统计:窗口满后再 addValue 会自动淘汰最旧值,N 恒为 5。4.1 节输出后半段逐行可见滚动过程(add 6 起 N 不再增长、min/max 开始右移),这就是流式监控「最近 N 个样本」的标准做法。
窗口相关还有一个易踩的 API 名差异——移除最近观测值的方法:
window.removeMostRecentValue();
实测结果(4.1 节输出末行):
after removeMostRecentValue: N=4 mean=5.5
移除 8 后剩 {4,5,6,7},均值 5.5 正确。math3 3.6.1 中该方法名是 removeMostRecentValue():写 removeMostRecentObservation() 编译报 cannot find symbol。实测用 javap 核对 math4-legacy 4.0-beta1 的同名类,其方法名同样是 removeMostRecentValue()——即两代在这个方法上同名,网传"math4 改名 removeMostRecentObservation"的说法与 4.0-beta1 的 jar 实测不符,写代码时以 javap 核对为准。
4.3 SummaryStatistics:常数内存的汇总统计
SummaryStatistics 不保存原始数据,只维护计数器与矩累加器,内存占用恒定,适合超大流量场景与多数据集汇总。summary() 组的实测:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo summary
输出:
== SummaryStatistics(两个数据集 + 汇总对比) ==
set1: N=5 mean=6.000 variance=10.000 sd=3.162 sum=30.0
set2: N=5 mean=5.000 variance=10.000 sd=3.162 sum=25.0
all : N=10 mean=5.500 variance=9.167 sd=3.028 sum=55.0
加权平均核对: (mean1*N1 + mean2*N2)/(N1+N2) = 5.500(应等于 all.mean)
解读:
- set1={2,4,6,8,10} 均值 6、样本方差 10(Σ(x-6)²=40,40/4=10);set2 同理均值 5、方差 10。
- 合并集 all 的方差 9.167 不是 10 的简单平均——方差不可直接加权合并,必须用全量数据重算(本例 all 是逐个 addValue 合并的,结果准确)。
- 均值可加权合并:
(6×5 + 5×5)/(5+5) = 5.5,与 all.mean 一致,这是分片统计(如各节点只报 N 与 mean)时合并均值的标准公式。
与 DescriptiveStatistics 的选型:需要分位数/最值回溯或滑动窗口选 DescriptiveStatistics(要存全量);只累积 N/mean/variance/sum 且数据量大选 SummaryStatistics(常数内存,但不支持 percentile)。
4.4 TTest:t 检验与 p 值解读
TTest 在 org.apache.commons.math3.stat.inference 包,无参构造后调用检验方法。ttest() 组用 8 组"处理前/处理后"配对数据实测两种检验:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo ttest
输出:
== TTest(配对 t 检验 / 同方差 t 检验) ==
pairedT(before, after) = -13.717203674463095
pairedTTest p 值(双侧) = 2.5795569554518058E-6
homoscedasticT(before, after) = -1.596337345388562
homoscedasticTTest p 值(双侧) = 0.13273255836476072
p 值解读(本例数据每对 after 均比 before 高约 1.9,8 组全部同向):
- pairedTTest(配对 t 检验)p ≈ 2.58E-6:远小于常用显著性水平 0.05,结论是"处理后显著升高"。配对设计消除了个体间差异,只看每对的前后变化,8 组同向变化使 t 统计量高达 -13.72。
- homoscedasticTTest(同方差独立样本 t 检验)p ≈ 0.133:大于 0.05,结论是"差异不显著"。因为它把 16 个值当作两组独立样本比较,组内个体差异(88~93 的波动)淹没了系统性抬升。
- 两个 p 值相差 5 个数量级,检验方式的选择对结论影响巨大:同一批数据、同一水平 0.05,一个显著一个不显著。数据成对采集(同一对象前后对比)时必须用配对检验。
实测输出为科学计数法(2.5795569554518058E-6 即 0.00000258),直接与阈值比较无需格式化。
五、线性代数 API 详解
线性代数集中在 org.apache.commons.math3.linear 包。实矩阵门面是 RealMatrix 接口与默认实现 Array2DRowRealMatrix(行优先 double[][] 存储),分解器按算法分 LUDecomposition/EigenDecomposition 等类,统一通过 getSolver() 产出 DecompositionSolver 解方程。
5.1 Array2DRowRealMatrix 与矩阵乘法
linalg() 组前半段实测矩阵构造与乘法:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo linalg
输出:
== 线性代数(Array2DRowRealMatrix / LU / Eigen) ==
A = [[2,3],[1,4]], B = [[5,6],[7,8]]
A.multiply(B) = Array2DRowRealMatrix{{31.0,36.0},{33.0,38.0}}
LUDecomposition(A).getDeterminant() = 5.0
solve A*x=[5,9] -> x = [-1.4000000000000004, 2.6]
EigenDecomposition(A).getRealEigenvalues() = [0.9999999999999999, 5.0]
getImagEigenvalues() = [0.0, 0.0]
手算核对乘法:A·B 第 1 行 = {2×5+3×7, 2×6+3×8} = {31, 36},第 2 行 = {1×5+4×7, 1×6+4×8} = {33, 38},与实测完全一致。toString() 输出形如 Array2DRowRealMatrix{{31.0,36.0},{33.0,38.0}},调试时可直接打印。同组还实测了两个常用方法(见 4.4 节综合案例):transpose() 转置返回新矩阵、operate(RealVector) 矩阵乘向量返回 RealVector。
5.2 LUDecomposition:行列式与线性方程组
LU 分解是最通用的稠密矩阵分解,LUDecomposition(A) 后可取行列式与求解器:
getDeterminant():实测 A=[[2,3],[1,4]] 行列式 2×4-3×1=5.0,输出5.0一致。getSolver()返回DecompositionSolver,solve(RealVector)解 A·x=b。
实测解方程 A·x=[5,9]:
solve A*x=[5,9] -> x = [-1.4000000000000004, 2.6]
核对:A⁻¹ = (1/5)·[[4,-3],[-1,2]],A⁻¹·[5,9] = [(20-27)/5, (-5+18)/5] = [-1.4, 2.6],一致;-1.4000000000000004 末位的 000004 是 double 运算的正常浮点误差(见 5.5 节与第 4 篇第七章)。
API 签名注意:3.6.1 的 DecompositionSolver.solve() 只接受 RealVector/RealMatrix 参数,没有 solve(double[]) 重载。传数组编译报 cannot find symbol,必须先包装:solver.solve(new ArrayRealVector(new double[] {5, 9})).toArray()。
5.3 EigenDecomposition:特征值分解
EigenDecomposition(A) 分解后,getRealEigenvalues()/getImagEigenvalues() 分别返回实部与虚部特征值数组(实矩阵若有复特征值,两数组按位对应)。实测 A=[[2,3],[1,4]]:
EigenDecomposition(A).getRealEigenvalues() = [0.9999999999999999, 5.0]
getImagEigenvalues() = [0.0, 0.0]
核对:特征方程 λ²-6λ+5=0 → λ∈{1,5},实测 0.9999999999999999 与 5.0(1 的第一个位差是数值算法的正常舍入);虚部全 0 说明两个特征值均为实数。对称矩阵可改用 SymmetricEigenDecomposition 相关实现获得更高精度,本例用通用 EigenDecomposition 已足够。
5.4 RealVector:向量运算
vector() 组实测 ArrayRealVector 的常用方法:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo vector
输出:
== RealVector(ArrayRealVector) ==
v1 = [1, 2, 3], v2 = [4, 5, 6]
v1.add(v2) = {5; 7; 9}
v1.dotProduct(v2) = 32.0
v1.getNorm() = 3.7416573867739413
v1.mapMultiply(10) = {10; 20; 30}
逐项核对:add 逐元素相加 {5,7,9};dotProduct = 1×4+2×5+3×6 = 32;getNorm = √(1+4+9) = √14 ≈ 3.7416573867739413;mapMultiply 标量乘 {10,20,30}。注意 RealVector 的 toString() 用分号分隔({5; 7; 9}),与矩阵的花括号逗号格式不同。配合 mapMultiply/dotProduct 可直接实现投影、相似度(余弦)等常见运算,无需手写循环。
六、与 math4 的命名与 API 差异
3.6.1 与 math4/新资料混读时最容易踩的三处差异,均为实测锚点:
| 差异点 | math3 3.6.1 实测行为 | 错误写法与实测报错 | 正确写法 |
|---|---|---|---|
| 移除最近观测值 | removeMostRecentValue() 存在且可用 | removeMostRecentObservation() → 编译报 cannot find symbol | window.removeMostRecentValue(); |
| 解方程入参 | solve() 只收 RealVector/RealMatrix | solver.solve(new double[] {5, 9}) → 编译报 cannot find symbol | solver.solve(new ArrayRealVector(new double[] {5, 9})) |
| 分数约分/百分数 | Fraction 无 reduce()/percentageValue() | f.reduce() → 编译报 cannot find symbol | 构造已自动约分(new Fraction(2,4) 即 1/2);百分数 f.doubleValue() * 100 |
补充两点包名层面的事实:math4 将旧 API 迁入 org.apache.commons.math4.legacy.*(DescriptiveStatistics 位于 commons-math4-legacy 模块,jar tf 实测类文件存在),FastMath 更名 AccurateMath 迁入 org.apache.commons.math4.legacy.core.jdkmath。两代包名不同,可同 classpath 共存(完整实测见第 1 篇安装指南 8.2 节)。
七、注意事项
- 方差语义:
getVariance()是样本方差(除以 N-1),总体方差用getPopulationVariance();合并多个 SummaryStatistics 时方差不可加权平均,需全量重算。 - 检验方式决定结论:成对数据用 pairedTTest,独立样本才用 homoscedasticTTest/独立检验,选错结论可能反转(4.4 节实测 p 值相差 5 个数量级)。
- 浮点误差属正常:solve 结果
-1.4000000000000004、特征值0.9999999999999999都是 double 舍入,比较时用容差而非==。 - 复特征值:实矩阵可能产生复特征值,
getRealEigenvalues()/getImagEigenvalues()必须成对读取。 - 运行编码:POSIX/C locale 下加
-Dfile.encoding=UTF-8,否则中文输出变问号。 - 版本锚点:3.6.1 是断代约 10 年的最终版本,遇新资料 API 对不上时优先
javap核对 jar 内真实方法签名。
八、常见问题(FAQ)
Q1:getPercentile(50) 和"平均数"有什么区别?为什么本例两者都是 7.0?
getPercentile(50) 是中位数(第 50 百分位数,用插值法计算),getMean() 是算术平均。20 个数据排序后中位落在 7 附近故为 7.0,均值恰也为 7.0(总和 140/20)。两者并不总是相等:数据偏斜时差异明显,如 4.3 节 all 集均值 5.5 而第 1 篇 MathCheck 输出里 10 个数的中位数为 7.5(排序后第 5、6 个数 7 与 8 的平均)。中位数抗离群值,均值受离群值牵引,按业务语义选择。
Q2:solve() 传 double 数组编译报错怎么办?
实测报错为 cannot find symbol: method solve(double[])。3.6.1 的 DecompositionSolver.solve() 只接受 RealVector/RealMatrix,先构造 new ArrayRealVector(new double[] {5, 9}) 再传入,结果用 .toArray() 取回 double 数组(见 5.2 节)。
Q3:同一个 classpath 里能同时用 math3 和 math4 吗?
能。两代顶层包名不同(org.apache.commons.math3 与 org.apache.commons.math4),不存在类冲突。完整实测(CoexistDemo 源码、编译运行命令、逐行回显)见第 1 篇《Ubuntu 22.04 安装 Apache Commons Math 指南》8.2 节:math3 与 math4-legacy 的 DescriptiveStatistics 同 classpath 各自正常工作。
Q4:为什么 pairedTTest 输出是 2.5795569554518058E-6 这种格式?
这是 double 的科学计数法字符串形式(2.58×10⁻⁶)。数值上直接与显著性水平比较即可(p < 0.05 成立);要展示时自行格式化,例如 String.format("%.2e", p) 输出 2.58e-06。p 值本身的业务解读见 4.4 节。
九、参考链接
- Apache Commons Math 官方站点:https://commons.apache.org/proper/commons-math/
- Commons Math 3.6.1 Javadoc:https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/
- 官方用户指南(统计与线性代数章节):https://commons.apache.org/proper/commons-math/userguide/stat.html
- Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/commons/commons-math3/
- math4 迁移说明(RELEASE-NOTES 随二进制包提供):https://downloads.apache.org/commons/math/