MATLAB 只剩批次摘要怎么合并方差:用 n、mean、M2 保留批间差异

MATLAB 只剩批次摘要怎么合并方差:用 n、mean、M2 保留批间差异

环境、实测结果与一键运行

实测环境:MATLAB R2025b(25.2.0.2998904),Windows PCWIN64,2026-10-20。仅需基础 MATLAB。保存文末完整代码为 pooled_batch_demo.m,在文件所在目录执行 matlab -batch "r=pooled_batch_demo;"。

本次实测:11 个样本的加权均值为 9.27272727273,总样本方差为 25.8181818182;批内 M2=2,批间 M2=256.181818182。遗漏批间项会把同一分母下的结果误算为 0.2。

本次由总入口实际调用同一份 demo,主运行和独立复核均退出成功。这里给出单篇复现命令,不另行声称启动过单篇独立进程。

设备分包上传数据,服务端只收到每包样本数、均值和方差。此时把各包均值取平均,看起来最自然,却会让两点的小包与八百点的大包拥有一样的权重。方差还有第二个陷阱:即使正确加权了批内方差,也可能漏掉不同批次中心的移动。

此前 CSV 数据处理已经讨论过按样本数加权均值,本篇新增的问题是:原始行不在手里时,怎样用 (n,mean,M2) 摘要恢复总平方和,并按正确自由度输出方差。

本文用基础 MATLAB 合并互不重叠批次的描述统计,不做显著性检验,也不假设各批来自相同分布。问题只有一个:这些批次拼接成一条记录时,整条记录的均值和方差是多少?

1. 先问清楚你要平均"样本"还是"批次"

第 j 批有 n_j 个样本、均值 mu_j,总样本数 N 为各 n_j 之和。按样本等权的总体记录均值是:

mu = sum(n_j*mu_j)/N

举一个可以手算的例子:第一批 [0,2],均值为 1;第二批八个 10,均值为 10。两批均值直接平均得到 5.5,而十个样本拼接后的均值为 (2*1+8*10)/10=8.2。

5.5 不是永远没有意义。如果研究目标就是"随机挑选一批时,批次平均水平是多少",批次等权可能恰当。但它不是这十个观测值的样本等权均值。报表必须写出目标,不能把两种加权悄悄混用。

样本数权重也不是任何场景都适用。例如不同采样率的数据若要估计时间平均,需要按代表的持续时间加权;重复上传或重叠窗口若被当成独立样本合并,会重复计数。

2. 不必保存每一批方差,保存 M2 更直接

定义第 j 批的中心平方和:

M2_j = sum((x_j-mu_j).^2)

若原记录提供样本方差 s_j²,且 n_j≥2,则 M2_j=(n_j-1)*s_j²。如果提供的是以 n_j 为分母的描述方差,则应乘 n_j。接口必须说明分母,不可只留一个含糊的 variance 字段。

基础 MATLAB var(x,0) 默认使用 N−1,var(x,1) 使用 N。单个观测时 MATLAB 返回零,但这不意味着我们掌握了单样本总体波动的信息。var 官方文档

本文内部直接保存 M2:空批 n=0、M2=0、均值 NaN 并跳过;单点批的 M2=0,但仍贡献一个样本及其批间差异。最终只有 N≥2 才报告样本方差。代码中的 N−1 只是标准描述统计分母,在相关时间序列上不能不加条件地称为总体方差的无偏估计。

3. 总平方和等于批内项加批间项

把一个样本相对总均值的偏差拆开:

x-mu = (x-mu_j)+(mu_j-mu)

平方后在批内求和,交叉项为零,因为 sum(x-mu_j)=0。因此:

M2_total = sum(M2_j) + sum(n_j*(mu_j-mu)^2)

前一项是批内波动,后一项是批间均值差异。最终样本方差是 M2_total/(N-1),记录自身的总体形式方差是 M2_total/N。

继续用前两批手算:批内平方和只有第一批贡献 2;批间项为 2*(1-8.2)^2+8*(10-8.2)^2=129.6,总平方和为 131.6。只合并批内部分会严重低估整条记录的离散程度。

统计学中"pooled variance"有时专指假设组间共享噪声方差时的批内合并估计。本文标题中的合并统计明确指拼接记录的总方差,包含批间项;不把两种用途混称。

4. 流式接收时,只保留三个量也能继续合并

两个摘要分别为 (nA,muA,M2A) 和 (nB,muB,M2B),令 delta=muB-muA,则:

n = nA+nB

mu = muA+delta*nB/n

M2 = M2A+M2B+delta^2*nA*nB/n

空批直接跳过。脚本同时用全部批次的"批内加批间"公式,以及这个逐批更新式计算,核对二者一致,再与原始样本拼接后的 mean 和 var 对照。

脚本还把全部值平移 1e5,并逆序合并平移后的批次摘要,使用 1e-8 绝对容差检查方差保持不变。本次实际 MATLAB 检查已通过,但不是对任意大偏置的稳定性证明。

这避免了用 sum(x.^2)-sum(x)^2/N 在"大直流加小波动"场景下做两个大数相减,但不是绝对无误差的保证。批次顺序、浮点舍入、极大样本计数与溢出仍要评估;本例只覆盖有限规模、幅度不超过 1e6 的 double 数据,没有实现任意精度累计器。

5. 空批、单点与单位,都是接口的一部分

演示最终再加入一个空批和一个单点 20,检验跳过规则与单点贡献。主结果因此对应 11 个观测值,不是前面十点手算例子的 8.2;十点例子单独作为断言,避免把不同实验的数字混写。

NaN 与 Inf 在本例被拒绝。若业务要跳过坏点,摘要中的 n 必须同步改成实际参加统计的有效计数,并保存丢弃数量;不能均值忽略坏点、权重却仍使用原始行数。

各批单位和校准必须一致。两个传感器一个输出摄氏度、一个输出 ADC 码,即使公式没有报错,合并也没有物理意义。若批次之间换了增益或工作点,批间项可能正是应该调查的线索,而不是需要人为扣掉的"噪声"。

6. 复现与工程交付

保存完整代码为 pooled_batch_demo.m,运行:

matlab 复制代码
r = pooled_batch_demo;

批处理命令:matlab -batch "r=pooled_batch_demo;"。本次已在 results 中生成批次摘要 CSV、结果 MAT 和批内/批间对照 PNG。

验收包含不等长批次、空批、单点、直接拼接校验、逐批合并校验,以及两个明确错误的对照:均值的无权平均与遗漏批间项。所有代码仅使用基础 MATLAB。

均值和方差还应与后续功率分析区分。方差围绕均值计算,均方值包含直流贡献;可结合本专栏PSD 积分功率校验检查单位与归一化。合并统计只需要保留计数、均值和 M2,但这些摘要不能恢复频谱、峰值位置或异常发生的时间,原始记录的留存仍取决于工程追溯需求。

实测图:批间项为什么不能丢

上图只画三个非空批次,空批次 3 被省略;实线为按样本加权的 9.27272727273,虚线为批次等权的 10.3333333333。下图批内项仅为 2,批间项却为 256.181818182,说明这段记录的大部分离散来自批次中心不同。

全体观测的总和为 102、平方和为 1204。实际 MATLAB 独立复核用这组有理数参考得到样本方差 284/11,并用逐观测 Welford 更新与批次摘要算法交叉比较;测试集中最大相对差约 6.88e-17。以 N 为分母的记录方差为 2840/121=23.4710743802,它与 N−1 分母的 25.8181818182 回答的是不同归一化定义,不能混写。

完整代码

matlab 复制代码
function r = pooled_batch_demo
% Base MATLAB; descriptive aggregation, not inferential ANOVA.
batches={ [0;2], [10;10;10;10;10;10;10;10], [], 20 };
B=numel(batches); n=zeros(B,1); mu=nan(B,1); M2=zeros(B,1);
for j=1:B
    x=batches{j};
    if ~isa(x,'double')||~isreal(x)||any(~isfinite(x(:)))||any(abs(x(:))>1e6)
        error('POOL:Input','Finite real double batches within +/-1e6 required.');
    end
    x=x(:); n(j)=numel(x);
    if n(j)>0, mu(j)=mean(x); M2(j)=sum((x-mu(j)).^2); end
end
active=n>0; N=sum(n); assert(N>1,'POOL:Count','Need at least two observations.');
meanAll=sum(n(active).*mu(active))/N;
within=sum(M2); between=sum(n(active).*(mu(active)-meanAll).^2);
varSample=(within+between)/(N-1); varPopulation=(within+between)/N;
allX=vertcat(batches{:});
assert(abs(meanAll-mean(allX))<1e-12,'POOL:Mean','Mean mismatch.');
assert(abs(varSample-var(allX,0))<1e-12,'POOL:Var','Sample variance mismatch.');
assert(abs(varPopulation-var(allX,1))<1e-12,'POOL:Pop','Population variance mismatch.');
% Separate pairwise merge, using only (count,mean,M2).
count=0; avg=0; scatter=0;
for j=1:B
    if n(j)==0, continue; end
    if count==0, count=n(j); avg=mu(j); scatter=M2(j); continue; end
    delta=mu(j)-avg; newCount=count+n(j);
    scatter=scatter+M2(j)+delta^2*(count*n(j)/newCount);
    avg=avg+delta*(n(j)/newCount); count=newCount;
end
assert(abs(avg-meanAll)<1e-12 && abs(scatter-(within+between))<1e-10, ...
    'POOL:Merge','Sequential merge mismatch.');
% Translation and reverse-order checks, with tolerance for double rounding.
shift=1e5; shifted=allX+shift;
assert(abs(var(shifted,0)-varSample)<1e-8,'POOL:Shift','Translation check failed.');
countRev=0; avgRev=0; scatterRev=0;
for j=B:-1:1
    if n(j)==0, continue; end
    if countRev==0
        countRev=n(j); avgRev=mu(j)+shift; scatterRev=M2(j); continue;
    end
    delta=mu(j)+shift-avgRev; nc=countRev+n(j);
    scatterRev=scatterRev+M2(j)+delta^2*(countRev*n(j)/nc);
    avgRev=avgRev+delta*(n(j)/nc); countRev=nc;
end
assert(abs(avgRev-(meanAll+shift))<1e-8 && ...
    abs(scatterRev/(N-1)-varSample)<1e-8,'POOL:Reverse','Reverse shifted merge failed.');
% Equal batch weights estimate a different target: average batch mean.
wrongMean=mean(mu(active)); wrongWithinOnly=within/(N-1);
r=struct('N',N,'mean',meanAll,'sampleVariance',varSample, ...
    'populationVariance',varPopulation,'withinM2',within,'betweenM2',between, ...
    'meanOfMeans',wrongMean,'withinOnlyVariance',wrongWithinOnly);
% Hand-computable two-batch reference: mean 8.2, total M2 131.6.
x=[0;2;repmat(10,8,1)];
assert(abs(mean(x)-8.2)<1e-12 && abs(sum((x-mean(x)).^2)-131.6)<1e-10, ...
    'POOL:Hand','Two-batch reference failed.');
disp(r);
out=fullfile(fileparts(mfilename('fullpath')),'results');
if ~exist(out,'dir'), mkdir(out); end
writetable(table((1:B)',n,mu,M2,'VariableNames',{'batch','count','mean','M2'}), ...
    fullfile(out,'pooled_batch.csv'));
save(fullfile(out,'pooled_batch.mat'),'r','n','mu','M2');
f=figure('Visible','off','Color','w','Position',[100 100 1100 760]);
subplot(2,1,1); bar(find(active),mu(active)); hold on; yline(meanAll); yline(wrongMean,'--');
xlabel('batch (empty batch 3 omitted)'); ylabel('mean');
legend('batch mean','sample-weighted','equal-batch','Location','southoutside','Orientation','horizontal');
ylim([0 1.15*max(mu(active))]); grid on;
subplot(2,1,2); bar([within between]); set(gca,'XTickLabel',{'within','between'});
ylabel('sum of squared deviations'); grid on;
print(f,fullfile(out,'pooled_batch.png'),'-dpng','-r150'); close(f);
fprintf('MATLAB run finished: %s, %s\n',version,computer);
end
相关推荐
yuniko-n1 小时前
【JUC】JMM(Java 内存模型)
java·开发语言
Elastic 中国社区官方博客2 小时前
Observability:当 YAML 遇上 IConfiguration:OpenTelemetry .NET SDK 的声明式配置
大数据·开发语言·elasticsearch·搜索引擎·全文检索·.net
郑州光合科技余经理2 小时前
同城商城源码:商品库和订单库怎么随包交付
android·java·开发语言·后端·uni-app·php·ai编程
welfare9622 小时前
新号别搞:C++_list
开发语言·c++
数据狐(Datafox)2 小时前
一文读懂京东商品详情 API:返回字段解读与 JSON 样例
开发语言·数据库·爬虫·json
坤坤子吖2 小时前
Linux 线程同步与互斥——条件变量与线程同步
linux·开发语言·c++·笔记·学习
damoluomu2 小时前
国产 Java CMS 的协议和坑,讲清楚
java·开发语言
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 黑名单:分页查询黑名单
java·开发语言·交友