4. 现金流季节性分解

任务说明

本案例针对企业月度现金流序列,使用STL(Seasonal-Trend decomposition using LOESS)方法将其分解为趋势、季节和残差三个成分。分解结果可用于识别现金流的季节性规律、判断长期趋势方向,以及通过残差成分发现异常月份。案例使用Python的statsmodels库实现,输入为一份包含12个月以上连续月度现金流数据的CSV文件。

环境与依赖

需要安装pandas、statsmodels、matplotlib。若使用X13-ARIMA需额外安装statsmodels的X13后端并配置X13AS二进制路径,本案例以STL为主实现。

数据准备

创建输入文件cash_flow.csv,包含两列:month(格式YYYY-MM)和amount(万元)。以下为示例数据(24个月),可直接复制使用:

month,amount
2022-01,320
2022-02,285
2022-03,310
2022-04,298
2022-05,335
2022-06,352
2022-07,340
2022-08,365
2022-09,348
2022-10,372
2022-11,358
2022-12,390
2023-01,355
2023-02,318
2023-03,342
2023-04,330
2023-05,368
2023-06,385
2023-07,372
2023-08,398
2023-09,380
2023-10,405
2023-11,392
2023-12,428

完整实现代码

将以下代码保存为stl_decompose.py:

import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import STL

# 读取数据
df = pd.read_csv('cash_flow.csv')
df['month'] = pd.to_datetime(df['month'])
df.set_index('month', inplace=True)

# 确保按月频率且无缺失
ts = df['amount'].asfreq('MS')

# STL分解(周期为12个月)
stl = STL(ts, period=12, robust=True)
result = stl.fit()

# 输出分解结果
components = pd.DataFrame({
    'observed': ts,
    'trend': result.trend,
    'seasonal': result.seasonal,
    'resid': result.resid
})
print("分解结果(最近6个月):")
print(components.tail(6).round(2))

# 残差统计
print("\n残差标准差:", round(result.resid.std(), 2))
print("残差均值:", round(result.resid.mean(), 4))

# 可视化
fig = result.plot()
fig.set_size_inches(10, 8)
plt.tight_layout()
plt.savefig('stl_result.png', dpi=150)
print("\n图表已保存为 stl_result.png")

运行方式

pip install pandas statsmodels matplotlib
python stl_decompose.py

验证方法与判定标准

运行后观察以下输出以确认分解成功:

  1. 控制台打印最近6个月的分解结果,包含observed、trend、seasonal、resid四列数值,且resid数值明显小于原始序列量级(示例数据中残差值通常小于15);
  2. 残差均值接近0(示例中应小于0.5),残差标准差在5~15之间;
  3. 当前目录生成stl_result.png,图中包含4个子图:原始序列、趋势成分(平滑上升曲线)、季节成分(呈周期性波动,每年同月数值一致)、残差(围绕0轴随机波动)。

若使用自己的数据,需保证至少24个月连续月度数据且无缺失值,否则STL无法正确估计12个月季节周期。判定通过标准为:趋势成分呈现合理单调或平滑变化,季节成分呈现12个月固定周期形态,残差无显著趋势且均值接近0。

常见问题

  • 数据不足24个月:STL要求至少2个完整季节周期,否则会报错或季节成分失真。解决方法是增加数据长度或改用周期为6的period=6参数。
  • 缺失值报错:STL不接受NaN,需先通过ts.interpolate()或ts.fillna(method='ffill')处理。
  • 季节成分每年同月不完全相等:STL允许季节成分随时间缓慢变化,这是正常现象,与X13-ARIMA的固定季节成分不同。
STL分解流程与成分关系 原始现金流 趋势成分 季节成分 残差成分 + 原始 = 趋势+季节+残差 季节成分反映每年固定周期波动(如年末回款增加) 残差用于检测异常月份(|残差| > 2σ 视为异常) 季节成分示意(12个月周期) 1月 6月 12月 6月 12月

上图展示了STL分解的核心逻辑:原始序列被拆分为三个可解释成分,其中季节成分呈现以12个月为周期的重复形态,残差则围绕零值波动。实际应用中,可进一步对残差设定阈值(如±2倍标准差)来自动标记现金流异常月份。