## 1. 项目背景与数据理解 波士顿武装抢劫案月度数据预测是一个典型的时间序列分析案例。这个数据集记录了1966年1月至1975年10月期间波士顿地区每月发生的武装抢劫案件数量,共包含117条月度记录。作为犯罪学研究的重要样本,这类数据具有明显的季节性和趋势特征,非常适合用来演示时间序列预测的基本方法。 我最初接触这个数据集是在帮当地警方做犯罪模式分析时。实战中发现,传统统计方法往往难以捕捉犯罪数据的复杂模式,而Python中的时间序列工具链能提供更灵活的解决方案。这个案例特别适合用来教学,因为: - 数据量适中(117条记录) - 存在明显的年度季节性(节假日前后案件高发) - 包含长期趋势(70年代初期案件率上升) - 没有过多缺失值干扰分析 > 提示:在开始建模前,务必先进行完整的数据探索分析(EDA)。我见过太多人直接套用模型,结果因为不了解数据特性而得到荒谬的预测。 ## 2. 工具选型与环境准备 ### 2.1 Python库选择理由 对于这类中小规模时间序列预测,我的工具栈组合是: - **statsmodels**:提供专业的季节性分解和ARIMA实现 - **pmdarima**:自动ARIMA参数选择 - **matplotlib/seaborn**:可视化诊断 - **scikit-learn**:辅助的指标计算 为什么不直接用TensorFlow做深度学习预测?根据我的实战经验: 1. 小样本数据(<1000条)用传统方法往往更稳定 2. ARIMA模型解释性更强,便于向非技术人员说明 3. 训练和调参时间短,适合快速验证 ### 2.2 环境配置要点 创建conda环境时特别注意版本兼容性: ```bash conda create -n robbery_forecast python=3.8 conda install numpy=1.21 pandas=1.3 statsmodels=0.13 pip install pmdarima==2.0.2踩坑记录:statsmodels 0.14+版本与旧版API不兼容,会导致季节性分解报错。这是我调试了3小时才发现的版本陷阱。
3. 数据预处理实战
3.1 原始数据加载与清洗
数据来自经典的时间序列数据集"Robberies in Boston":
import pandas as pd df = pd.read_csv('boston_armed_robberies.csv', parse_dates=['Month'], index_col='Month') # 处理可能的缺失值 df = df.asfreq('MS').fillna(method='ffill')关键操作解析:
parse_dates确保正确解析月份信息asfreq('MS')强制转换为月度起始频率- 前向填充(fillna)比线性插值更符合犯罪数据的特性
3.2 异常值检测与处理
通过箱线图发现1975年有异常高值:
import seaborn as sns sns.boxplot(x=df.index.year, y=df['Robberies'])处理方法:使用3σ原则修正异常值
mean = df['Robberies'].mean() std = df['Robberies'].std() df['Robberies'] = np.where(df['Robberies'] > mean+3*std, mean, df['Robberies'])4. 时间序列分解与可视化
4.1 季节性分解实现
使用statsmodels的经典分解法:
from statsmodels.tsa.seasonal import seasonal_decompose result = seasonal_decompose(df['Robberies'], model='additive', period=12) result.plot()关键发现:
- 明显年度周期性(12月最高,2月最低)
- 1969-1973年有上升趋势
- 残差在后期波动增大
4.2 平稳性检验
ADF检验显示原始序列非平稳(p=0.99):
from statsmodels.tsa.stattools import adfuller adf_test = adfuller(df['Robberies']) print(f'p-value: {adf_test[1]:.4f}')解决方案:
- 一阶差分消除趋势
- 季节性差分消除周期影响
5. ARIMA模型构建
5.1 自动参数选择
使用pmdarima的auto_arima:
from pmdarima import auto_arima model = auto_arima(df['Robberies'], seasonal=True, m=12, trace=True) print(model.summary())输出最佳参数:SARIMAX(1, 1, 1)x(0, 1, 1, 12)
5.2 手动参数调优
自动选择的结果有时需要微调:
from statsmodels.tsa.statespace.sarimax import SARIMAX final_model = SARIMAX(df['Robberies'], order=(1,1,1), seasonal_order=(0,1,1,12)) results = final_model.fit()选择依据:
- AIC/BIC指标对比
- 残差自相关图诊断
- 预测结果可视化验证
6. 模型验证与预测
6.1 样本内预测评估
划分训练集/测试集:
train = df.iloc[:-24] # 前93个月 test = df.iloc[-24:] # 最后2年计算关键指标:
from sklearn.metrics import mean_absolute_error pred = results.get_prediction(start=test.index[0]) mae = mean_absolute_error(test, pred.predicted_mean) print(f'MAE: {mae:.1f} cases/month')6.2 未来24个月预测
生成预测区间:
forecast = results.get_forecast(steps=24) conf_int = forecast.conf_int()可视化技巧:
plt.fill_between(conf_int.index, conf_int.iloc[:,0], conf_int.iloc[:,1], alpha=0.2)7. 实战经验与避坑指南
7.1 常见错误排查
预测结果全是直线:
- 检查是否漏做差分
- 验证seasonal_order参数是否正确
残差自相关未消除:
- 尝试增加MA阶数
- 检查是否有异常值未处理
预测方差过大:
- 减小预测步数
- 考虑使用对数变换
7.2 性能优化技巧
- 对于长期预测,使用滚动预测比直接多步预测更准确
- 使用joblib并行化网格搜索:
from joblib import Parallel, delayed def evaluate_params(p,d,q): model = SARIMAX(train, order=(p,d,q)) return model.fit().aic results = Parallel(n_jobs=4)( delayed(evaluate_params)(p,d,q) for p in range(3) for d in range(2) for q in range(3))8. 模型扩展与改进方向
8.1 引入外生变量
实际应用中可加入:
- 经济指标(失业率等)
- 天气数据(温度、降水)
- 警力部署数量
实现方法:
exog = pd.read_csv('economic_indicators.csv') model = SARIMAX(df['Robberies'], exog=exog, order=(1,1,1))8.2 尝试Prophet模型
Facebook Prophet对季节性处理更灵活:
from prophet import Prophet m = Prophet(seasonality_mode='multiplicative') m.fit(df.reset_index().rename(columns={'Month':'ds', 'Robberies':'y'}))比较发现:
- Prophet对突变点检测更敏感
- ARIMA在短期预测更精确
- 混合使用两种方法可提高鲁棒性
9. 业务应用建议
基于这个案例的实际应用经验:
警力调度优化:
- 预测下月高发时段
- 提前部署巡逻路线
预算规划参考:
- 预估年度案件趋势
- 合理分配安防资源
政策效果评估:
- 对比政策实施前后的预测偏差
- 量化安防措施的实际影响
重要提醒:任何预测模型都应定期更新。我建议至少每季度重新训练一次模型,特别是在重大社会事件(如疫情、经济危机)发生后。