简介:一套面向气象、海洋等地球科学数据分析的MATLAB工具包,聚焦EOF、REOF、SVD与CCA四种常用统计方法,适合需要做多变量降维、时空模式识别或区域异质性研究的学生和科研人员使用。压缩包内含4个.m文件,分别对应经验正交函数、区域经验正交函数、奇异值分解和典型相关分析的实现,整体仅4KB,代码精炼,便于阅读和二次修改。已有323人学习下载。通过这套代码可以快速掌握EOF/REOF的完整处理流程:从数据预处理、SVD分解、EOF与负荷计算,到按空间划分子区域、分别分析并组合结果,同时理解CCA在两变量集相关性分析中的应用。脚本既可独立运行,也可串联使用,方便根据实际数据和研究目标灵活调整保留模态数或区域划分方式,是地学统计入门与日常分析的实用工具。
1. 这串文件名背后的 EOF/REOF 分析流程
看到eof,reof等.rar这类资源,很多人的第一反应是解压、改名、直接跑REOF.m。真正决定分析质量的不是 RAR 里那几个.m文件,而是你对 EOF、REOF 和 SVD 三者关系的理解。经验正交函数(Empirical Orthogonal Function)是一套把“空间点 × 时间序列”矩阵分解为空间型与时间系数的降维工具,实际计算大多落在奇异值分解(SVD)上;REOF 则是基于 EOF 前 k 个模态再做旋转,让空间模态更集中、更便于物理解释。这套流程常见于气象海洋领域的网格数据、遥感影像序列、工业传感阵列与任何“批量位置 + 连续时间”的测量数据。适合的人群是:拿到 MATLAB 脚本但不知道参数怎么设的数据工程师,以及被要求把 EOF/REOF 结果写进报告却说不清 SVD 与前几个奇异值关系的分析师。下面直接从数值实现开始,把每一步用最小可运行代码写出来。
2. EOF 的数值骨架:为什么用 SVD 而不是直接算协方差特征分解
针对网格化数据做 EOF 时,常见做法是先把观测场排成二维矩阵 X,然后用svd(X,'econ')一次拿到左奇异向量、奇异值和右奇异向量。之所以不走协方差矩阵eig,不是因为两者结果不同,而是因为数值可靠性。协方差矩阵 C = Xc*Xc'/(n-1) 是 m×m 实对称矩阵,理论上特征值非负,但网格数据量大、量纲差异明显时,C 的条件数会放大舍入误差,eig偶尔给出微小的负特征值,后续求方差贡献时开根号会直接报错或产生 NaN。SVD 直接作用在 Xc 上,奇异值永远不会为负,“经济型分解”同时控制了内存占用,是写 EOF 代码时的默认选项。
2.1 数据矩阵的排列约定与 SVD 三因子的物理解读
在气候分析里,EOF 的数据矩阵一般写成X(m,n),m 是空间格点数,n 是时间观测数,时间维固定在第二维。这个约定与很多从 Fortran 转过来的脚本不同:旧脚本常把三层do循环的 (经度,纬度,时间) 直接写进文件,进 MATLAB 后按列优先展开,空间维被拆散,十个粗心的人里有八个会算完才发现第一模态对应的是“半个地球拼在一起”。保持时间维在后的好处从 SVD 三因子里直接看得出来:| SVD 返回项 | 维度 | EOF 语义 | | --- | --- | --- | | U | m×min(m,n) | 空间载荷向量,EOF 的空间型 | | S | min(m,n)×min(m,n) | 奇异值,平方后对应特征值 | | V | n×min(m,n) | 归一化时间系数,主成分方向 |
左奇异向量 U 用于画空间型,右奇异向量 V 用于画时间系数,S 对角元用于计算各模态方差贡献。理解了这三者的角色,就不会再把REOF.m里的旋转对象搞错。需要强调一点:svd(Xc,'econ')返回的列数取决于 m 与 n 的较小值;当空间点数远大于时间样本数时,返回的列数是 n,此时第 n 个奇异值之后的空间变化根本进不了分解,时间样本长度直接决定可解的模态数量。
2.2 用 MATLAB 的 svd() 跑通一个最小 EOF 代码
手头没有真实观测数据时,我通常先合成一组含两个空间模态的信号,这样能验证代码对奇异值分解的每个中间量都认识。下面的脚本不需要任何附加工具箱,生成 60 个空间点、200 个时间样本的场,噪声方差约为信号能量的四分之一。
% 合成数据:两个空间模态,各自有独立时间系数 rng(2024); m = 60; % 空间格点数(经向或站点数) n = 200; % 时间样本数 lon = (1:m)'; % 模式1:高斯型空间分布,时间系数低频振荡 pattern1 = exp(-((lon-20).^2)/50); pc1 = sin(2*pi*(1:n)/40) + 0.2*randn(1,n); % 模式2:带正负位相的两个中心,时间系数高频 pattern2 = exp(-((lon-45).^2)/30) - exp(-((lon-12).^2)/20); pc2 = cos(2*pi*(1:n)/15) + 0.3*randn(1,n); X = pattern1 * pc1 + pattern2 * pc2; X = X + 0.5 * randn(m, n); % 附加噪声 % EOF 核心分解 Xc = X - mean(X, 2); % 去掉各格点的时间均值 [U, S, V] = svd(Xc, 'econ'); % 方差贡献 lambda = diag(S).^2 / (n - 1); frac = lambda / sum(lambda); % 取前两个模态做重构检验 k = 2; X_rec = U(:, 1:k) * S(1:k, 1:k) * V(:, 1:k)';这段代码的参数和逻辑说明如下:
mean(X, 2)沿第二维求均值,得到每个格点的时间平均值,Xc保存的是距平场。如果对整个矩阵只减去一个全局标量,格点间的气候均值差异仍然保留,第一模态会被平均水平空间分布占据。svd(Xc,'econ')中的'econ'是经济型分解。m=60、n=200 时,完整 SVD 会返回 60×60 的 U、60×200 的 S、200×200 的 V;econ把 S 和 V 的零空间部分截掉,返回 60×60 的 S 和 200×60 的 V。网格数据跑到几万格点时,这个参数省下的内存非常可观。diag(S).^2/(n-1)把奇异值还原成协方差矩阵的特征值,因为 Xc = USV',而 XcXc'/(n-1) = US*S'*U'/(n-1)。漏掉/(n-1)的REOF.m脚本很常见,表现是方差贡献偏大一个量级。- 重构校验
X_rec是判断 EOF 分解是否正确的通用手段。用前两个模态重建的场均与Xc的空间趋势明显不符时,先检查数据生成逻辑,而不是怀疑 SVD 算法。
2.3 EOF 的预处理参数:去均值、去趋势与空间权重
把代码跑通后,真正需要跟业务交互的是进入svd之前的三个参数选择。第一是去均值。前面用的是格点时间均值,更严格的气象分析会强调“气候态”,即每个格点在相同日期上的多年平均值。用总平均代替逐日或逐月气候态,会留下明显的年循环,使第一模态总是一个单调变化的信号。第二是标准化。单独分析一种变量,比如只做海温,通常不必对行做标准化;但要同时分析温度、风速、气压这类量纲不同的变量时,必须对每一行做 z-score,否则数值大的变量会主导整个 EOF 分解,而这不代表它的物理重要性。第三是空间权重。对等经纬度网格,高纬度格点代表的实际面积小,一般先乘以sqrt(cos(lat))权重再进 SVD。很多人用纬度平均面积去修正结果,但修正动作若不放在分解前而放在分解后,会直接改变模态的形状。这三个参数没有绝对对错,但要在报告里写清楚,因为 EOF 对预处理相当敏感,同一套数据可能因为这三种选择的不同而得到差异明显的第一模态。
3. REOF 步骤的实质:Varimax 旋转、载荷符号与模态可解释性
REOF 是 EOF 的旋转延伸,不是新算法。REOF.m拿 EOF 前 k 个载荷向量做旋转,得到空间上更集中的新载荷。最常见的准则是 Varimax,它最大化载荷平方的方差。这么做的原因是 EOF 的正交性约束很强:第二个模态必须与第一个模态在空间上正交,这会导致模态变成“两个区域反相”的全球型图案,区域化特征反而被抹掉。旋转放松正交约束后,载荷的平方分布更趋于集中在少数格点上,更接近人们对特定气候区的直观判断。注意,旋转不改变原始场的信息量,只改变前 k 个模态的解释方式。
3.1 为什么旋转的是载荷而不是时间系数
搞清旋转对象,是所有REOF.m能否正确工作的分水岭。EOF 计算得到空间载荷 U 与时间系数 V 的乘积,旋转通常只作用于载荷 A = U(:,1:k)。旋转矩阵 R 是 k×k 矩阵,新载荷 A_rot = A*R。时间系数不能用旧 V 直接代替,需要用 A_rot 对原始距平场做最小二乘投影,即 PC_rot = A_rot \ Xc。如果某个脚本旋转后只画了新载荷,时间曲线还是老 V 的列,它的时空配对就错位了。判断脚本是否可靠,可以看它有没有求A_rot \ Xc这一步。
3.2 MATLAB 里用 rotatefactors 做 Varimax 旋转与参数设置
MATLAB 自带rotatefactors函数,可以直接对前 k 个载荷做正交旋转。一个最小用法如下:
% 用前 k 个 EOF 载荷做 Varimax 旋转 k = 4; % 旋转模态数,需要根据谱确定 A_raw = U(:, 1:k); % 原始空间载荷矩阵 (m*k) % Normalize 开启 Kaiser 归一化,对大尺度场更稳健 A_rot = rotatefactors(A_raw, 'Method', 'varimax', 'Normalize', true); % 求旋转后的时间系数:最小二乘投影 Xc = X - mean(X, 2); PC_rot = A_rot \ Xc; % 左除等价于求最小二乘解 % 旋转后各模态实际方差贡献 var_rot = sum(PC_rot.^2, 2) / (size(X, 2) - 1); frac_rot = var_rot / sum(var(Xc, 0, 2));rotatefactors默认把每列当作因子、每行当作观测,所以A_raw的行是空间格点、列是模态。'Normalize', true对应 Kaiser 归一化,旋转前先消除各格点载荷向量模长差异带来的尺度干扰,旋转完再恢复,对前几个模态方差贡献相差较大的场更合适。A_rot \ Xc返回 k×n 矩阵,每一行是一个旋转模态的时间系数,这一步保证了新时间曲线与新空间载荷匹配。frac_rot用旋转后时间系数的方差除以原始场总方差,是 RE 模态的真实贡献,不要直接沿用frac(1:k)。如果不想用工具箱,也可以手工实现 Varimax:对 A 迭代执行“计算载荷平方方差梯度、按 Givens 旋转更新、直到目标函数变化小于阈值”,但手工实现需要处理迭代步长和正交约束,业务中基本没有理由重复造轮子。
3.3 REOF 的模态数、方差贡献与符号约定
旋转模态数是 REOF 里争议最多的参数。取少了,区域特征混合不充分;取多了,会把噪声也旋转出“看起来像模态”的图案。经验上先用 North 准则圈定显著 EOF 模态的个数:特征值 λ 的采样误差约为 λ·sqrt(2/N),N 是时间样本数,当相邻两个特征值的差值小于这个误差区间时,两个模态不可区分,旋转时要慎重。之后在前后两个整数上各做一次旋转,比较空间载荷的相关性,载荷对 k 的变化不敏感的区间就是可靠区间。不要为了追求“前两个模态解释 80% 方差”而强行把 k 设成 2,因为旋转模态上的方差会重新分配,这个数字并不代表区域模态的独立性。
| 对比项 | EOF | REOF |
|---|---|---|
| 模态正交性 | 严格正交 | 旋转后无正交约束 |
| 空间分布 | 可能全球型、载荷分散 | 更集中、区域化明显 |
| 方差贡献 | 随奇异值递减 | 需根据旋转后时间系数重新计算 |
| 时间系数 | V 与 S 的乘积 | 对旋转载荷做最小二乘投影 |
| 主要用途 | 查看总体变率 | 识别可物理解释的区域模态 |
符号约定也常被忽视:EOF/REOF 的载荷符号是任意的,同一分析用不同版本 MATLAB 计算,得到的第二模态可能整体反号。写报告前要做一个符号固定:指定某个区域中心点或某个载荷绝对值最大的格点的符号为正,然后对整个模态和时间系数同时乘以 -1。缺少这一步,两台机器上跑同一份数据画出来的图颜色会相反,分析结论却完全一致,容易引起不必要的返工。
4. 把 RAR 里的 EOF/REOF 脚本落地成可复现流程
拿到eof,reof等.rar这类资源后,先别急着运行。RAR 包里的文件命名往往带着不同历史阶段的分析痕迹:REOF.m、eof.m、plot_EOF.m、data.mat可能来自不同时期,函数接口未必能直接串起来。我一般先把 RAR 解压到一个独立工作目录,在 MATLAB 中用addpath指向该目录,然后逐文件查看函数签名,确认输入输出变量名后再组装流程。可以关注reof.m是否包含rotatefactors调用,以及旋转后的时间系数是否重新投影过;这两点最容易暴露“伪 REOF”。
4.1 从三维网格到二维矩阵:RAR 数据文件的导入与重构
先看包里文件类型:如果是.mat文件,直接whos -file data.mat查看变量维度;如果是.txt或.dat,用readmatrix读取,但要注意文件编码。很多早期脚本用 GBK 编码保存说明文字,直接readmatrix不影响数值列,但用textscan或fopen读取时若报出unexpected end of file或premature EOF,优先检查文本文件是否完整解压,其次看文件行数是否与脚本里的n一致。这类错误不是算法问题,是数据读取层的问题。
三维网格数据一般长这样:
% 假设数据为 (lon, lat, time),维度 [nlon, nlat, nt] [nlon, nlat, nt] = size(X3d); % 将空间维拉平,时间维保持最后一维 X2d = reshape(X3d, nlon*nlat, nt);如果读进来发现维度是 (time, lon, lat),要先permute(X3d, [2 3 1])再reshape。这个 permute 步骤看着不起眼,却决定后续空间图的绘制能否对上坐标。拉平后,建议保留lon2d = repmat(lon', nlat, 1)和lat2d = repmat(lat, 1, nlon)之类的网格坐标数组,防止画图时重新造坐标出现转置错误。
4.2 一个通用的 EOF+REOF 流水线脚本骨架
在真实数据分析中,我一般会写一个函数把 EOF、REOF 和方差贡献封装起来,参数只留一个旋转模态数。这样无论换数据还是调参,都不必改动分解核心代码。
function [eof_load, eof_pc, eof_frac, ... reof_load, reof_pc, reof_frac] = eof_reof_pipeline(X, kRot) % X: 空间格点×时间样本的距平场,建议先在外面去掉气候态 % kRot: 参与旋转的模态数 Xc = X - mean(X, 2); [U, S, V] = svd(Xc, 'econ'); lam = diag(S).^2 / (size(X, 2) - 1); eof_frac = lam / sum(lam); eof_load = U(:, 1:kRot); eof_pc = S(1:kRot, 1:kRot) * V(:, 1:kRot)'; % 保留振幅的时间系数 reof_load = rotatefactors(eof_load, 'Method', 'varimax', 'Normalize', true); reof_pc = reof_load \ Xc; reof_frac = sum(reof_pc.^2, 2) / (size(X, 2) - 1) / sum(eof_frac); end函数里有两个细节。第一,eof_pc没有直接用V(:,1:kRot)',而是乘了S(1:kRot,1:kRot),因为V的列是归一化的,乘上奇异值后时间序列才保留真实振幅;不乘奇异值的后果是画时间系数图时数值普遍小于 0.1,看不出发散趋势差异。第二,reof_frac的分母用sum(eof_frac)与var(Xc(:))等价,但前者更不容易因为单位换算出错。旋转模态的方差贡献不是lam(1:kRot)原样搬运,这一点务必在输出阶段重新计算。
4.3 三个必调参数与两个排错点
必调参数至少有三个,整理成表:
| 参数 | 建议做法 | 作用 |
|---|---|---|
| kRot | 依据 North 准则或特征值谱转折点选取 | 控制旋转模态个数 |
| Normalize | true(Kaiser 归一化) | 提高旋转结果的可靠性 |
| 左除投影 | reof_pc = reof_load \ Xc | 保证旋转后的时空配对正确 |
排错点一:输入矩阵包含 NaN。SVD 对 NaN 零容忍,任何一格缺测都会让对应奇异值变 NaN。不要用 0 填充缺测,这会人为压低估方差,使前几个模态虚高;更常见做法是用该格点序列的多年平均插补,或直接删除缺测行。排错点二:奇异值非零但特征值出现负值。若有人坚持使用eig(Xc*Xc')并开根号,负特征值会直接报错;改用svd后此问题消失。若svd依然报出收敛警告,检查数据是否含 Inf,以及是否在分解前对行列做了不合理的标准化。
5. 三个必须掌握的验证手法:重构残差、符号固定与分半鲁棒性检验
最后一章落在“怎么判断算对”上。EOF 和 REOF 没有真实标签可以对照,输出图再漂亮也可能在符号、模态数或空间权重上出偏差,所以我通常固定做三个验证。
5.1 重构残差检验
用前 k 个模态重建距平场,残差里若还残留明显的空间主模态,说明 k 取小了。判断残差是否随机,可以对残差再做一次 SVD,看第一模态方差占比是否显著高于后续模态;如果还是“悬崖式”下降,继续加模态。
% 残差 EOF:检查残余空间结构 Xc = X - mean(X, 2); X_rec = U(:, 1:k) * S(1:k, 1:k) * V(:, 1:k)'; resid = Xc - X_rec; [Ur, Sr] = svd(resid, 'econ'); frac_resid = diag(Sr).^2 / (size(X, 2) - 1) / sum(diag(Sr).^2); bar(frac_resid(1:min(10, numel(frac_resid))));5.2 符号固定
EOF 的载荷符号是任意的,换机器、换 SVD 库都可能整体反号。实务中我一般先指定一个物理基准点,比如某个已知异常中心的格点索引,检查该点在每个模态的载荷符号,如果为负,就把该模态的载荷和时间系数同时乘以 -1:
for j = 1:k if eof_load(anchor_idx, j) < 0 eof_load(:, j) = -eof_load(:, j); eof_pc(j, :) = -eof_pc(j, :); end end注意旋转前后的模态必须使用同一套符号规则,否则 REOF 的空间型和时间系数会错配。
5.3 分半鲁棒性检验
把时间序列切成前后两半,各跑一次 EOF/REOF,比较对应模态的空间相关,是判断旋转模态是否可信的最快方法。对应模态相关系数高于 0.6 才认为可靠;如果某个模态在前后半段里变形明显,它很可能由几个特征值极为接近的 EOF 混合而成,不宜在结论中重点讨论。
n = size(X, 2); half = floor(n/2); [eof1_l, ~, ~, reof1_l] = eof_reof_pipeline(X(:, 1:half), kRot); [eof2_l, ~, ~, reof2_l] = eof_reof_pipeline(X(:, half+1:end), kRot); corr_mat = corrcoef([reof1_l, reof2_l]); % 检查 corr_mat(1:kRot, kRot+1:2*kRot) 中对应模态的匹配程度这三个验证都不需要额外工具箱,却能在输出报告前拦截绝大多数错误。实际项目里,三分之二的 EOF/REOF 问题出在 SVD 之前的预处理而不是算法本身;把重构残差、符号和分半一致性做成脚本里的固定步骤,比换更复杂的旋转算法更值得先做。
本文还有配套的精品资源,点击获取