3. 海洋时间序列可视化与探索性分析

拿到一份海表温度(SST)时间序列后,最忌讳的做法是直接套用模型或计算相关系数。探索性分析(EDA)的目的,是在建模之前先把数据的“脾气”摸清楚:它有没有长期趋势、季节波动幅度多大、是否存在异常跳变、缺失值集中在哪些时段。本节以一份假想的日平均 SST 序列为例,走完从绘图、统计到结论的完整流程,所用数据与数值均为教学示例,不代表真实观测结果。

先看清数据的基本形态

假设数据文件 sst_daily.csv 含两列:date(日期)与 sst(摄氏度),时间范围 2015-01-01 至 2024-12-31,共 3653 条记录,其中约 2% 的日期因云覆盖缺测。读入后第一件事是建立规范的时间索引并检查完整性:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("sst_daily.csv", parse_dates=["date"]).set_index("date")
df = df.asfreq("D")                 # 补齐日历,缺测日变为 NaN
print(df["sst"].isna().sum())       # 缺测天数
print(df["sst"].describe())         # 均值、标准差、分位数

示例输出中均值约 18.6 ℃、标准差约 4.2 ℃、最小值 9.1 ℃、最大值 30.4 ℃。标准差偏大往往提示存在强季节信号,因为年周期会把全年温度拉开十几度。此时若直接对原始序列求趋势,得到的斜率会被季节项严重干扰,所以下一步必须把“趋势—季节—残差”拆开看。

时间序列图:第一眼要看什么

用 matplotlib 绘制原始序列,重点观察四件事:整体抬升或下降的斜率、每年重复的波峰波谷位置、波峰高度的年际差异、以及孤立的尖峰或断崖。绘图时把 x 轴设为日期、y 轴固定为温度,并叠加一条 365 天滑动均值线,趋势会更清楚:

fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(df.index, df["sst"], lw=0.6, color="#7fb3d5", label="日SST")
ax.plot(df.index, df["sst"].rolling(365, center=True).mean(),
        lw=2, color="#c0392b", label="365天滑动均值")
ax.set_ylabel("SST (℃)"); ax.legend(); plt.tight_layout()

若滑动均值线在十年间缓慢上行约 0.3~0.5 ℃,说明存在弱增温趋势;若某年夏季峰值明显低于邻年,则要回头核查该时段是否缺测偏多,避免把采样不足误判为降温。

季节分解:把叠加的信号拆开

statsmodels 的 seasonal_decompose 能把序列拆成趋势项、季节项和残差项。对日数据应指定 period=365,并优先选加法模型(温度的季节振幅随均值变化不大时适用):

from statsmodels.tsa.seasonal import seasonal_decompose
res = seasonal_decompose(df["sst"].interpolate(), model="additive", period=365)
res.plot(); plt.show()

读图时按三层判断:趋势项是否单调、季节项的峰谷是否稳定在每年 7—8 月与 1—2 月、残差项是否近似零均值白噪声。若残差在某段时期连续偏正或偏负,说明该时段有分解模型未捕获的事件(如一次持续暖异常),这正是后续异常检测的线索。需要注意,seasonal_decompose 对两端趋势估计不稳,首尾各一年的趋势值仅供参考。

热力图:把年际与季节放在一张表里

把日期重排成“年 × 月”的矩阵,用 seaborn 热力图呈现月均 SST,能一眼看出哪年哪月偏暖偏冷:

pivot = df["sst"].resample("MS").mean().to_frame("sst")
pivot["year"] = pivot.index.year
pivot["month"] = pivot.index.month
grid = pivot.pivot(index="year", columns="month", values="sst")
sns.heatmap(grid, cmap="RdYlBu_r", annot=True, fmt=".1f",
            cbar_kws={"label": "月均SST (℃)"})

行是年份、列是月份,颜色由蓝到红对应低温到高温。横向看同一行,能确认季节节律是否每年一致;纵向看同一列,能比较各年同月的冷暖差异。若某列出现明显偏红的单元格,就是该月发生了暖异常,可回到原始序列定位具体日期。

SST 探索性分析:从原始序列到四类判读 ① 原始日序列 看趋势斜率 / 季节峰谷 / 尖峰 ② 季节分解 趋势项 季节项 残差项 ③ 年×月热力图 行=年份 列=月份 色=月均温 找偏红单元格=暖异常 ④ 基本统计量 均值 ≈ 18.6 ℃ 标准差 ≈ 4.2 ℃ 最小 / 最大 9.1 / 30.4 缺测 ≈ 2% 标准差大→季节信号强 先分解再谈趋势 判读逻辑:图形证据 → 结论 → 后续动作 滑动均值缓慢上行 ≈0.3~0.5 ℃/10年 季节项峰谷稳定 7—8月高、1—2月低 残差连续偏正 → 存在持续暖异常 热力图偏红单元格 → 定位到具体年月 结论示例:十年间弱增温 + 稳定年周期 + 个别月份暖异常;建模前需插补缺测、去季节或显式建模季节项 注:图中数值为教学示例,非真实观测结果

统计量与趋势、周期的定量确认

图形给出直觉,统计量给出可复现的数字。除均值、标准差、分位数外,建议补充:按月分组的箱线图看季节离散度;用 df["sst"].resample("YS").mean() 得到年均序列后做一元线性回归,斜率即年际趋势估计;用自相关函数(ACF)确认周期长度——若 ACF 在滞后 365 天附近出现明显峰值,年周期成立。示例中若年均序列回归斜率约 0.04 ℃/年,且 ACF 在 lag=365 处峰值约 0.6,则可初步认定“弱增温 + 强年周期”。

缺测处理要谨慎:短缺口(1~3 天)可用线性插值;长缺口(数周)若直接插值会人为压低季节振幅,建议在分析中标注或改用能处理缺失的模型。任何插补都应在报告中写明方法与影响范围。

探索性分析报告应包含什么

把上述过程整理成一页报告,核心是让读者能复核你的判断。建议包含:数据来源与时间范围、缺测比例及处理方式;四张关键图(原始序列+滑动均值、季节分解、年×月热力图、月箱线图);统计量汇总表;趋势与周期的结论及依据(斜率、ACF 峰值);发现的异常时段及其可能成因;对后续建模的建议(如是否需要去季节、是否需对残差做异常检测)。结论要区分“数据支持的”与“推测的”,例如“2019 年 8 月月均温高于十年同月均值约 1.2 ℃”是数据事实,而“可能与某次暖事件有关”则属于待验证推测。

完成这一轮探索后,你对这份 SST 序列的趋势方向、季节强度、异常位置和缺失分布都有了明确认识,后续无论是做预测、相关分析还是异常检测,都有了可靠的起点和可对照的基线。