ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python实战EOF分析:从气候数据中提取时空模态

Python实战EOF分析:从气候数据中提取时空模态 1. 项目概述从数据海洋中提取气候“指纹”如果你处理过气象或海洋数据比如全球几百个站点几十年的温度序列或者一个区域高分辨率的再分析格点数据你一定会被那庞大的数据量所震撼。面对一个三维甚至四维的数据立方体时间×空间×变量我们如何一眼看出其最核心的变化规律是简单地画几百张折线图还是对着海量格点数据发呆这时候你需要一个强大的数学工具来帮你“降维打击”从纷繁复杂的时空变化中提取出那几个最显著、最稳定的“模态”这就是经验正交函数分析也就是我们常说的EOF分析。简单来说EOF分析就像给气候数据做“主成分分析”。它能把一个包含大量空间点和时间点的数据集分解成一系列空间模态EOF和对应的时间系数PC。每个空间模态可以看作是一种典型的气候变化“图案”比如全国一致的增温型、东西相反的跷跷板型而对应的时间系数则告诉我们这种“图案”在历史上是如何随时间增强或减弱的。通过研究前几个方差贡献最大的模态我们就能抓住数据中最主要的变化信号滤掉那些琐碎的“噪音”。这对于研究气候变率如厄尔尼诺、北大西洋涛动、检测气候变化趋势、甚至进行气候预测都是不可或缺的基石性方法。十年前做EOF分析可能还得依赖昂贵的专业软件如GrADS、NCL或者复杂的MATLAB脚本。但现在有了Python及其强大的科学计算栈我们完全可以在一个开源、免费且灵活的环境中完成从数据读取、预处理、EOF计算到可视化出图的完整流程。这对于科研人员、气象业务工作者乃至对气候数据感兴趣的数据分析师来说无疑大大降低了门槛。接下来我就结合自己多次“踩坑”的经验带你一步步用Python实现EOF分析并解读其结果。2. 核心原理与数据准备理解EOF的数学内核在动手写代码之前我们必须对EOF分析的数学本质有一个清晰的认识这能帮助我们在后续结果解读时避免低级错误。EOF分析的核心是对方差-协方差矩阵或相关矩阵进行特征分解。假设我们有一个数据矩阵X其维度是m×n其中m是空间点例如格点或站点数n是时间点。通常空间点的数量远大于时间点。2.1 数学过程拆解第一步是数据预处理最常见的是去除每个空间点的时间平均值即去趋势或求距平。这样我们分析的对象就是数据围绕其平均状态的波动部分。处理后的矩阵记为X。第二步构建协方差矩阵C。C (1/(n-1))X**X**ᵀ这是一个m×m的方阵。这个矩阵的元素代表了不同空间点之间波动情况的协方差关系。第三步对协方差矩阵C进行特征分解。即求解方程CEEΛ。这里Λ是一个对角矩阵其对角线上的元素 λ₁, λ₂, ..., λ_m 就是特征值。特征值的大小直接反映了对应模态所解释的原始数据总方差的比例。E是一个矩阵它的每一列eᵢ就是一个EOF空间模态。这些模态是正交的即任意两个不同的EOF空间向量点积为零。第四步计算时间系数Principal Components, PC。将原始距平数据投影到EOF空间上即可得到PEᵀX。这里P的每一行pᵢ就是对应第 i 个模态的时间系数。时间系数之间也是正交的。注意在实际计算中特别是当空间点数m非常大如全球高分辨率格点数据时直接计算m×m的协方差矩阵C会极其消耗内存且计算缓慢。此时通常采用“时空转换”技巧改为计算较小的n×n的时间协方差矩阵然后再推导出EOF和PC。幸运的是像xeofs这样的专业库会自动处理这些优化我们无需手动实现。2.2 Python环境与数据准备工欲善其事必先利其器。一个稳定、兼容的Python环境是第一步。我强烈建议使用conda来管理环境它能很好地处理科学计算包复杂的依赖关系。# 创建一个名为eof_analysis的新环境并指定python版本 conda create -n eof_analysis python3.9 conda activate eof_analysis # 安装核心计算与数据处理的库 conda install -c conda-forge numpy scipy pandas jupyter # 安装处理气象NetCDF数据的利器 conda install -c conda-forge xarray netcdf4 dask # 安装可视化库 conda install -c conda-forge matplotlib cartopy # 安装专门用于EOF分析的库。这里有两个主流选择 # 1. eofs: 经典稳定功能直接 conda install -c conda-forge eofs # 2. xeofs: 基于xarray更现代与NetCDF数据结合无缝支持多变量推荐 conda install -c conda-forge xeofs对于数据我们以一个公开数据集为例ERA5再分析资料的月平均海表面温度数据。你可以从ECMWF或Climate Data Store获取。假设我们已经下载了一个NetCDF文件sst_global_1979_2020.nc。我们的目标是分析全球SST的主要变化模态。import xarray as xr import numpy as np import matplotlib.pyplot as plt import cartopy.crs as ccrs # 1. 加载数据 file_path sst_global_1979_2020.nc ds xr.open_dataset(file_path) # 假设温度变量名为 sst维度为 (time, lat, lon) sst ds[sst] # 2. 数据初览 print(sst.dims) # 查看维度应为 (time, lat, lon) print(sst.shape) # 例如 (504, 181, 360) 表示42年*12月504个时间点纬度181格点经度360格点 # 3. 简单可视化某个时刻的数据 fig plt.figure(figsize(10, 6)) ax plt.axes(projectionccrs.Robinson(central_longitude180)) sst.isel(time0).plot(axax, transformccrs.PlateCarree(), cbar_kwargs{shrink: 0.6}, cmapRdBu_r) ax.coastlines() ax.set_title(SST at First Time Step) plt.show()3. 数据预处理为EOF分析奠定基石原始数据往往不能直接用于EOF分析不恰当的预处理会导致提取的模态物理意义不清甚至完全是误导。这一步是决定分析成败的关键也是最容易出错的地方。3.1 去除时间平均与季节循环EOF分析关注的是“变异”因此通常需要移除每个空间格点上的气候平均态。对于月数据这个“平均态”通常是指“气候态月平均”即计算每个月份1月、2月...12月在所有年份上的平均值然后从原始数据中减去对应月份的气候值。这能有效去除强大的季节循环信号否则EOF第一模态很可能就是“夏季暖、冬季冷”的季节变化这不是我们研究年际或年代际变率所希望的。# 计算气候态月平均climatology climatology sst.groupby(time.month).mean(dimtime) # 计算距平anomaly sst_anom sst.groupby(time.month) - climatology # 验证查看某个格点去除季节循环前后的时间序列 point_lat, point_lon 0, 180 # 赤道日期变更线附近 sst_ts_original sst.sel(latpoint_lat, lonpoint_lon, methodnearest) sst_ts_anom sst_anom.sel(latpoint_lat, lonpoint_lon, methodnearest) fig, axes plt.subplots(2, 1, figsize(12, 6)) axes[0].plot(sst_ts_original.time, sst_ts_original) axes[0].set_title(Original SST Time Series) axes[0].set_ylabel(SST (°C)) axes[1].plot(sst_ts_anom.time, sst_ts_anom) axes[1].set_title(SST Anomaly Time Series (Seasonal Cycle Removed)) axes[1].set_ylabel(SST Anom (°C)) axes[1].set_xlabel(Time) plt.tight_layout() plt.show()3.2 空间加权与缺失值处理地球是球体格点面积随纬度变化。在赤道附近经度方向1度代表的距离远大于在高纬度地区。如果不进行面积加权高纬度格点虽然面积小但数量可能不少会在总方差中占据过大的权重导致EOF模态向高纬度倾斜。通常的加权方法是乘以纬度的余弦平方根sqrt(cos(lat))。# 计算纬度权重 lat sst_anom.lat weights np.sqrt(np.cos(np.deg2rad(lat))) # 将权重扩展到和数据相同的维度 weighted_sst_anom sst_anom * weights # 处理缺失值如陆地格点。EOF算法通常不能处理NaN。 # 对于气象数据一种常见方法是用空间或时间插值填充或者直接屏蔽。 # 这里我们假设海洋数据在陆地为NaN选择用附近海洋格点的平均值进行简单填充需谨慎根据实际情况选择。 weighted_sst_anom_filled weighted_sst_anom.interpolate_na(dimlon, methodlinear)实操心得预处理顺序一定要先去除季节循环再进行空间加权。如果先加权季节循环的振幅也会被扭曲再去季节循环就不准确了。此外对于缺失值xeofs库内置了处理能力比手动填充更稳健推荐使用。4. 使用xeofs库进行EOF分解经过预处理我们得到了一个干净的数据集weighted_sst_anom_filled。现在进入核心环节——EOF分解。我们选择xeofs库因为它与xarray深度集成输入输出都是DataArray后续分析和可视化非常方便。from xeofs.xarray import EOF # 初始化EOF分析器 # dim参数指定时间维度这里我们的时间维度名就是 time # 通过n_modes参数可以指定要计算多少个模态不指定则计算全部可能很慢 eof_model EOF(weighted_sst_anom_filled, dimtime, n_modes10) # 执行分解 eof_model.solve() # 获取结果 # 1. 空间模态 (EOFs) eofs eof_model.eofs() # 返回一个DataArray维度为 (mode, lat, lon) # 2. 时间系数 (PCs) pcs eof_model.pcs() # 返回一个DataArray维度为 (mode, time) # 3. 解释方差 explained_variance eof_model.explained_variance() explained_variance_ratio eof_model.explained_variance_ratio()让我们查看一下前几个模态的解释方差这能告诉我们每个模态的重要性。# 绘制碎石图Scree Plot mode_index np.arange(1, len(explained_variance_ratio) 1) cumulative_variance np.cumsum(explained_variance_ratio.values) fig, ax1 plt.subplots(figsize(10, 6)) color tab:blue ax1.bar(mode_index[:10], explained_variance_ratio.values[:10] * 100, colorcolor, alpha0.6, labelIndividual) ax1.set_xlabel(Mode Number) ax1.set_ylabel(Explained Variance [%], colorcolor) ax1.tick_params(axisy, labelcolorcolor) ax1.set_xticks(mode_index[:10]) ax2 ax1.twinx() color tab:red ax2.plot(mode_index[:10], cumulative_variance[:10] * 100, o-, colorcolor, linewidth2, labelCumulative) ax2.set_ylabel(Cumulative Explained Variance [%], colorcolor) ax2.tick_params(axisy, labelcolorcolor) ax2.axhline(y90, colorgray, linestyle--, alpha0.5) # 标记90%线 plt.title(Scree Plot: Explained Variance by EOF Modes) fig.tight_layout() plt.show() print(fMode 1 explains {explained_variance_ratio.values[0]*100:.2f}% of variance.) print(fMode 2 explains {explained_variance_ratio.values[1]*100:.2f}% of variance.) print(fFirst 5 modes together explain {cumulative_variance[4]*100:.2f}% of variance.)5. 结果可视化与物理意义解读计算出EOF和PC只是第一步更重要的是如何将它们可视化并结合气候学知识进行物理解读。这是将数学结果转化为科学认知的关键。5.1 绘制空间模态EOF图空间模态图展示了该种变化型的空间分布。通常我们用填色图表示EOF的数值正值和负值区域代表该模态下变化方向相反的区域。# 绘制前三个模态的空间分布 modes_to_plot [0, 1, 2] # 对应第123模态索引从0开始 nmodes len(modes_to_plot) # 由于我们之前乘了权重现在绘图前最好去除权重以恢复真实的物理量值。 # xeofs返回的eofs是加权后的我们需要除以权重。 # 注意权重在纬度方向需要扩展维度以匹配eofs weights_expanded np.sqrt(np.cos(np.deg2rad(eofs.lat))) eofs_unweighted eofs / weights_expanded fig, axes plt.subplots(nmodes, 1, figsize(14, 4*nmodes), subplot_kw{projection: ccrs.Robinson(central_longitude180)}) if nmodes 1: axes [axes] # 确保axes是可迭代的 for i, (ax, mode_idx) in enumerate(zip(axes, modes_to_plot)): mode_num mode_idx 1 # 选择第mode_idx个模态的数据 eof_data eofs_unweighted.isel(modemode_idx) # 计算一个合适的色标范围通常以0为中心对称 vmax np.max(np.abs(eof_data.values)) levels np.linspace(-vmax, vmax, 21) contour ax.contourf(eof_data.lon, eof_data.lat, eof_data, levelslevels, transformccrs.PlateCarree(), cmapRdBu_r, extendboth) ax.coastlines() ax.set_title(fEOF Mode {mode_num} ({explained_variance_ratio.values[mode_idx]*100:.1f}%)) # 只在最后一个子图添加色标 if i nmodes - 1: plt.colorbar(contour, axax, orientationhorizontal, pad0.05, shrink0.8, labelEOF Loading) plt.tight_layout() plt.show()5.2 绘制时间系数PC图及其谱分析时间系数图展示了该空间模态随时间演变的强度。我们可以将其与已知的气候指数如ENSO指数进行对比验证其物理意义。此外对PC进行功率谱分析可以了解该模态的主要周期如年际、年代际。# 绘制前三个模态的时间系数 fig, axes plt.subplots(nmodes, 1, figsize(14, 3*nmodes), sharexTrue) for i, (ax, mode_idx) in enumerate(zip(axes, modes_to_plot)): mode_num mode_idx 1 pc_data pcs.isel(modemode_idx) ax.plot(pc_data.time, pc_data.values, linewidth1.5) ax.axhline(y0, colork, linestyle-, linewidth0.5, alpha0.5) ax.set_ylabel(fPC{mode_num}) ax.set_title(fPrincipal Component {mode_num}) ax.grid(True, alpha0.3) # 可以标注一些重大气候事件例如强厄尔尼诺年 # el_nino_years [1982, 1987, 1991, 1997, 2009, 2015, 2019] # for year in el_nino_years: # ax.axvline(xnp.datetime64(f{year}-06-15), colorred, linestyle--, alpha0.3) axes[-1].set_xlabel(Time) plt.tight_layout() plt.show() # 对PC1进行简单的功率谱分析以年为单位 from scipy import signal pc1 pcs.isel(mode0).values # 假设数据是月平均采样频率为 12个月/年 fs 12.0 # 每年采样12次 # 计算周期图 frequencies, power_spectrum signal.periodogram(pc1, fsfs, detrendlinear) # 将频率转换为周期年 periods 1 / frequencies[1:] # 忽略频率为0无穷周期的点 power_spectrum power_spectrum[1:] fig, ax plt.subplots(figsize(10, 5)) ax.semilogx(periods, power_spectrum, -o, markersize4) ax.set_xlabel(Period (Years)) ax.set_ylabel(Power Spectral Density) ax.set_title(Power Spectrum of PC1) ax.grid(True, whichboth, alpha0.3) # 标记一些常见气候周期 for p in [1, 2, 3, 4, 5, 7, 10]: ax.axvline(xp, colorgray, linestyle:, alpha0.5) ax.text(p, ax.get_ylim()[1]*0.9, f{p}y, hacenter, fontsize9, alpha0.7) plt.tight_layout() plt.show()5.3 物理意义解读示例以全球SST的EOF分析为例通常我们会看到EOF1很可能对应厄尔尼诺-南方涛动模态。空间型表现为赤道东太平洋与西太平洋的反相变化一个暖一个冷解释方差通常最高。其PCPC1与ENSO指数如Nino3.4指数高度相关并显示出2-7年的年际振荡周期。EOF2可能对应全球变暖趋势或太平洋年代际振荡的一部分。如果数据时间跨度足够长如40年以上一个近乎全球一致增暖的模态可能会因强大的趋势信号而占据前几位。其PC会表现出长期的上升趋势。EOF3可能对应其他大洋模态如印度洋偶极子或大西洋多年代际振荡的某些特征。解读的关键在于将数学模态与已知的、有物理基础的气候现象联系起来。这需要查阅文献并将PC与公认的气候指数做相关性分析。# 示例计算PC1与Nino3.4指数的相关性假设你有nino34_index数据与sst时间对齐 # import pandas as pd # nino34 pd.read_csv(nino34_index.csv, parse_dates[time]).set_index(time) # 将PC1转换为与nino34相同时间索引的Series # pc1_series pcs.isel(mode0).to_pandas() # correlation pc1_series.corr(nino34[index]) # print(fCorrelation between PC1 and Nino3.4 index: {correlation:.3f})6. 常见问题、陷阱与高级技巧在实际操作中你会遇到各种各样的问题。下面是我总结的一些常见坑点和进阶处理方法。6.1 模态的符号不确定性EOF分析的一个经典问题是符号不确定性。对于一个EOF模态eᵢ和其PCpᵢ同时将其乘以 -1即(-eᵢ)和(-pᵢ)它们相乘-1 * -1 1后对原始数据方差的贡献完全不变。这意味着EOF和PC的符号是任意的。在可视化时为了便于解释我们通常遵循一个约定调整EOF空间型的符号使得其空间加权平均值或某个关键区域的平均值为正同时相应反转PC的符号以保持物理乘积不变。xeofs和eofs库通常有内置方法或建议来处理。# 一种常见的符号调整方法确保EOF空间型在某个关键区域如Nino3.4区域的平均值为正 def adjust_eof_pc_sign(eof_map, pc_series, region_maskNone): 调整EOF和PC的符号。 region_mask: 一个与eof_map形状相同的布尔数组True表示关键区域。 如果为None则使用全局平均。 if region_mask is None: region_mask np.ones_like(eof_map, dtypebool) # 计算关键区域内EOF的平均值 region_mean np.mean(eof_map[region_mask]) if region_mean 0: # 如果均值为负则将EOF和PC同时乘以-1 eof_map_adjusted -1 * eof_map pc_series_adjusted -1 * pc_series print(Sign of mode flipped.) else: eof_map_adjusted eof_map.copy() pc_series_adjusted pc_series.copy() return eof_map_adjusted, pc_series_adjusted # 示例调整EOF1的符号使用Nino3.4区域 (5N-5S, 170W-120W) # 需要先定义该区域的经纬度掩膜此处为伪代码 # nino34_mask (lat -5) (lat 5) (lon 190) (lon 240) # 经度0-360格式 # eof1_adj, pc1_adj adjust_eof_pc_sign(eofs_unweighted.isel(mode0).values, pcs.isel(mode0).values, nino34_mask)6.2 北检验与模态显著性我们如何知道提取的EOF模态不是随机噪声产生的这就需要显著性检验。最常用的是北检验。其核心思想是如果数据是纯粹的白噪声无空间相关那么特征值解释方差的误差范围可以用一个公式估算。如果相邻模态的特征值误差范围有重叠则它们可能无法被显著区分。# 计算北检验的误差范围 # 根据North et al. (1982)第k个特征值的误差约为 λ_k * sqrt(2/N) # 其中 N 是有效自由度通常近似为时间序列长度 n n len(pcs.time) # 时间序列长度 eigenvalues explained_variance.values # 特征值 error_bars eigenvalues * np.sqrt(2 / n) # 绘制特征值及其误差棒图 fig, ax plt.subplots(figsize(10, 6)) modes np.arange(1, len(eigenvalues) 1) ax.errorbar(modes[:10], eigenvalues[:10], yerrerror_bars[:10], fmto-, capsize5, capthick2, linewidth2) ax.set_xlabel(Mode Number) ax.set_ylabel(Eigenvalue (Explained Variance)) ax.set_title(North Test of Significance) ax.grid(True, alpha0.3) ax.set_xticks(modes[:10]) # 判断显著性如果误差棒不重叠则认为模态显著可分。 for i in range(len(modes[:10])-1): if eigenvalues[i] - error_bars[i] eigenvalues[i1] error_bars[i1]: print(fMode {modes[i]} and Mode {modes[i1]} are well separated (significant).) else: print(fMode {modes[i]} and Mode {modes[i1]} may not be well separated.)6.3 旋转EOF分析标准EOF追求方差最大有时会导致模态在空间上过于“全局化”物理意义模糊。旋转EOF通常指Varimax旋转通过旋转EOF空间基牺牲一部分解释的方差来换取模态在空间上更局地化、更易解释的结构。这在分析区域气候或寻找更具体的空间型时非常有用。xeofs库也支持旋转EOF。# 使用xeofs进行旋转EOF分析以Varimax为例 from xeofs.xarray import Rotator # 假设我们已经有了标准EOF分析的结果 eof_model # 对前10个模态进行旋转 rotator Rotator(eof_model, n_modes10, rotationvarimax) rotator.rotate() # 获取旋转后的结果 rotated_eofs rotator.eofs() rotated_pcs rotator.pcs() rotated_variance rotator.explained_variance_ratio() print(Original variance of first 5 modes:, explained_variance_ratio.values[:5]) print(Rotated variance of first 5 modes:, rotated_variance.values[:5]) # 注意旋转后各模态解释的方差之和不变但单个模态的方差会重新分配。6.4 处理大数据的技巧分块与Dask对于全球高分辨率、多变量的长时间序列数据其数据量可能达到GB甚至TB级别无法一次性读入内存。这时需要结合dask进行惰性计算和分块处理。xarray和xeofs都支持dask数组作为后端。# 使用dask打开大型数据集 ds_large xr.open_dataset(very_large_data.nc, chunks{time: 120}) # 每次处理120个时间步 sst_large ds_large[sst] # 此时sst_large是一个dask数组 # 后续的预处理操作如去季节循环、加权都会是惰性的 sst_anom_large sst_large.groupby(time.month) - sst_large.groupby(time.month).mean(dimtime) # 使用xeofs时确保输入是dask数组库会自动利用并行计算 eof_model_large EOF(sst_anom_large, dimtime, n_modes5) eof_model_large.solve() # 这一步会触发实际计算可能需要较长时间 # 计算结果仍然是dask数组需要调用.compute()将其具体化 eofs_result eof_model_large.eofs().compute() pcs_result eof_model_large.pcs().compute()注意事项计算资源管理使用Dask时务必注意设置合适的块大小chunks。块太小会导致任务调度开销大块太大会导致内存不足。一个经验法则是每个块的大小应在100MB到1GB之间。对于EOF分析在时间维度上分块通常是高效的。7. 从分析到应用重构场与预测得到EOF和PC后我们可以做很多有意义的事情。7.1 数据重构我们可以用前k个模态来近似重构原始距平场。这本质上是一种数据压缩和降噪。如果k选取得当重构场能保留主要的气候信号而滤除高频噪声。# 使用前5个模态重构SST距平场 k 5 # 获取前k个EOF和PC eofs_subset eofs_unweighted.isel(modeslice(0, k)) pcs_subset pcs.isel(modeslice(0, k)) # 重构公式: X_reconstructed EOFs * PCs^T # 使用矩阵乘法注意维度对齐 # 为了简化我们可以利用xeofs模型的方法 reconstructed eof_model.reconstructed_field(n_modesk) # 或者手动计算 (注意维度顺序) # reconstructed xr.dot(eofs_subset, pcs_subset, dimsmode) # 需要调整维度 # 比较原始场和重构场在某个时刻的差异 time_idx 100 # 选择一个时间点 original_field weighted_sst_anom_filled.isel(timetime_idx) reconstructed_field reconstructed.isel(timetime_idx) fig, axes plt.subplots(1, 3, figsize(18, 5), subplot_kw{projection: ccrs.PlateCarree(central_longitude180)}) titles [Original Anomaly, fReconstructed (First {k} Modes), Difference] datasets [original_field, reconstructed_field, original_field - reconstructed_field] for ax, title, data in zip(axes, titles, datasets): im ax.contourf(data.lon, data.lat, data, levels21, cmapRdBu_r, transformccrs.PlateCarree()) ax.coastlines() ax.set_title(title) plt.colorbar(im, axax, orientationhorizontal, pad0.05, shrink0.8) plt.tight_layout() plt.show() # 计算重构误差的均方根RMSD rmsd np.sqrt(((original_field - reconstructed_field)**2).mean()) print(fRMSD of reconstruction using first {k} modes: {rmsd.values:.3f})7.2 基于PC的简单预测模型PC时间序列通常比原始格点数据平滑且维度极低适合用来建立统计预测模型。例如我们可以用PC1的历史序列来预测未来几个月的PC1值如使用自回归模型AR然后再结合EOF空间型得到对未来SST场的预测。from statsmodels.tsa.ar_model import AutoReg import warnings warnings.filterwarnings(ignore) # 以PC1为例建立AR模型 pc1_series pcs.isel(mode0).to_pandas() # 转换为pandas Series # 假设数据是月平均我们尝试用滞后12个月1年来预测 lags 12 train_size int(len(pc1_series) * 0.8) # 用80%的数据训练 train, test pc1_series.iloc[:train_size], pc1_series.iloc[train_size:] # 拟合AR模型 model AutoReg(train, lagslags, old_namesFalse) model_fitted model.fit() # 进行预测 predictions model_fitted.predict(startlen(train), endlen(pc1_series)-1, dynamicFalse) # 可视化 plt.figure(figsize(12, 5)) plt.plot(train.index, train, labelTraining Data) plt.plot(test.index, test, labelTrue Test Data, colorgreen) plt.plot(predictions.index, predictions, labelAR Prediction, colorred, linestyle--) plt.axvline(xtrain.index[-1], colorgray, linestyle:, labelTrain/Test Split) plt.xlabel(Time) plt.ylabel(PC1) plt.title(AR Model Forecast for PC1) plt.legend() plt.grid(True, alpha0.3) plt.show() # 评估预测效果 from sklearn.metrics import mean_squared_error, r2_score mse mean_squared_error(test, predictions) r2 r2_score(test, predictions) print(fAR Model MSE: {mse:.4f}) print(fAR Model R² Score: {r2:.4f})这种基于EOF的统计降尺度预测方法虽然物理机制上不如动力模型复杂但在某些场景下如季节预测可以作为有用的补充工具计算成本也低得多。整个流程走下来从数据准备、核心分解、结果可视化和显著性检验到最后的模态旋转和应用EOF分析提供了一个强大的框架来理解复杂气候数据的时空结构。关键在于不要把它当作一个黑箱每一步预处理的选择、每一个模态的解读都需要结合具体的研究问题和气候学背景。我个人的体会是多尝试不同的预处理方法如是否去趋势、是否加权多与已知的气候指数做对比并始终用北检验等工具保持对结果统计显著性的警惕这样才能从数据中挖掘出真正可靠且有物理意义的信息。
返回列表