简介:这份文档是一篇以ARIMA模型为核心的实证小论文,面向正在学习时间序列分析与计量经济学的高校学生、科研入门者,重点解决“如何利用Eviews软件对经济指标进行建模与预测”的问题。论文以我国1980—2012年全社会固定资产投资额为研究对象,完整呈现了数据平稳性检验、差分处理、模型识别与参数估计、残差白噪声诊断以及2013年预测等环节,最终给出ARIMA(4,1,3)模型的较优预测效果。全文共1个doc文件,压缩包体积1.05MB,内容紧凑,适合用于课程论文参考、期末报告模仿或时间序列方法实战演练。已有253人学习下载,对理解ARIMA建模流程、相关领域应用以及Eviews操作步骤均有直观帮助。文档还包含原始数据表、时序图、单位根检验结果等关键输出,便于读者对照复现整个分析过程。
1. 时间序列分析小论文的常见死法与选题定位
“时间序列分析小论文.doc”这个标题,大概率不是指某个固定工具,而是很多人在课程作业、结课报告或季度数据分析里绕不过去的一个坎:拿到一列带日期的数据,要写出一篇能交差、能答辩、能说明白“数据里到底发生了什么”的文档。我见过太多初稿是同一副面孔:开头一句“数据整体呈上升趋势”、中间一张折线图、结尾贴一个ARIMA预测,R方或者RMSE一放就当结论。如果只是交差,这确实够用;但评阅的人只要多问一句“为什么差分一次而不是两次”“残差里还有没有可提取的信息”,这套初稿基本就塌了。
这篇内容按“从原始数据到可写进Word的完整推导链条”来组织,覆盖平稳性、定阶、拟合、诊断、滚动验证这几个环节,把每步该贴什么图、该写什么结论、该避开什么坑直接讲清楚。适合两类人:一类是手头有一份带时间列的业务数据、需要快速产出分析报告的工程师;另一类是正在写时序分析课程论文、想让自己那份报告从“套模板”升级成“可复现、可挑错”的学生。
2. 时间序列分析的预处理与统计特征提取
2.1 时间戳解析与索引排序
拿到任何时序数据的第一步,永远是把时间列变成真正的DatetimeIndex,而不是字符串。很多人在Excel里看着日期很正常,读进Pandas之后df['date']却是object类型,直接用df.plot()画出来横轴就是乱序字符串。常见的处理方式是用parse_dates在读取时解析,进内存后立刻排序并统一频率:
from pandas import read_csv, to_datetime df = read_csv('sales.csv', parse_dates=['date'], index_col='date') df = df.sort_index() # 检查索引是否有重复或缺失 print(df.index.duplicated().sum()) # 重复时间戳个数 print(df.index.is_monotonic_increasing) # 是否按时间递增 # 统一到日频,无数据的日期填 NaN,方便后续处理 df = df.asfreq('D')parse_dates让Pandas把字符串解析成datetime64;sort_index()必须放在检查之前,因为后面所有关于“滞后”“差分”的操作都依赖时间顺序;asfreq('D')会把数据重采样到日频,原本缺失的日期显式变成NaN,这一步不是为了好看,而是为了后面做缺失值统计时有一个完整的时间轴。业务上如果原始数据本来就是工作日频率,强行asfreq('D')会造出一大批周末空值,这种情况应该用resample('B')或者干脆不重采样,直接按原始时间戳建模。
2.1.1 时间戳解析的边界情况
时间列本身不干净的情况很常见:有的是“2024/1/5 0:00”这种混着斜杠的,有的是“20240105”八位数字,还有的带时区后缀。统一用pd.to_datetime()手动兜底一次更稳妥:
df['date'] = to_datetime(df['date'], format='mixed', errors='coerce') df = df.dropna(subset=['date'])format='mixed'允许同一列里出现多种日期格式;errors='coerce'把解析失败的记录置为NaN,随后用dropna清掉。这一步看起来多此一举,但实际数据里一条“2024-02-30”就能让后面的diff()结果出现莫名其妙的NaN断层,排查成本远高于提前清理。
2.2 缺失值处理与重采样
小论文里常见的处理方式是df.fillna(df.mean()),这在横截面数据里可以,在时序数据里基本是错误示范。时间序列的缺失值不能用全局均值填充,因为序列本身有趋势和季节性,全局均值会把一个局部高值点拉向整体平均水平,直接污染自相关结构。
| 缺失比例 | 推荐做法 | 理由 |
|---|---|---|
| < 5% 且呈孤立缺失 | 线性插值interpolate(method='time') | 按时间间隔插值,尊重局部趋势 |
| 连续缺失较长 | 先按业务周期聚合,再考虑模型补齐 | 长段缺失时插值会“制造”数据 |
| 高频数据中的零星空值 | 前向填充ffill() | 多数业务指标可视为上一时刻延续 |
# 按时间间隔线性插值 df['value'] = df['value'].interpolate(method='time') # 如果插值后仍有尾部缺失,前向填充兜底 df['value'] = df['value'].ffill().bfill()interpolate(method='time')会利用DatetimeIndex的实际时间间隔来计算插值权重,两个观测点间距越大,插出来的值越接近较远的那一端,这比method='linear'(按行号插值)更贴合业务直觉。ffill()和bfill()只做边界补齐,不能作为主方案,因为连续前向填充会在图像上制造一小段平台期,ACF图里会出现“假拖尾”。
2.3 趋势、季节性与残差的三方分解
描述时间序列前,先做一次STL分解,这是性价比最高的一步。STL处理趋势和季节性时比单纯移动平均更稳健,加了robust=True后对异常值不那么敏感:
from statsmodels.tsa.seasonal import STL import matplotlib.pyplot as plt stl = STL(df['value'], period=7, robust=True).fit() fig = stl.plot() plt.tight_layout() plt.savefig('stl_decompose.png', dpi=150).plot()会输出四张子图:原始序列、趋势项、季节项、残差项。period=7对应周周期,如果是月度数据观察年度周期,这里应改成12;如果拿不准周期,可以对原始数据做周期图看看峰值出现在哪个频率。robust=True会在迭代拟合里降低异常点权重,适合销售、流量这类偶尔有促销或宕机尖刺的数据。
2.3.1 分解结果怎么读
观察趋势项,判断是否存在单调变化,这决定后面要不要给模型加trend项;观察季节项是否长期稳定,稳定则说明季节性可以用固定周期描述;关键在残差项——如果残差里还有明显波动规律,说明周期参数挑错了。不少小论文把分解图放上但不写结论,评阅人看不到分析痕迹。写上三句判断,比如“残差项的波动范围约在±0.3内,未见明显周期结构,可进入平稳性检验”,整段分析的完整度立刻不一样。
3. 时间序列平稳性检验与ARIMA定阶思路
3.1 ADF单位根检验与差分次数
大部分时序建模流程的第一步是判断序列是否平稳,而判断平稳最常用的就是ADF检验。这里有一个容易翻车的地方:ADF检验有regression参数,默认是'c'(只含常数项),但序列如果有明显的线性趋势,应该用'ct'(常数项+趋势项),否则检验功效会下降,原本平稳的趋势序列会被误判成有单位根。
from statsmodels.tsa.stattools import adfuller adf_result = adfuller(df['value'], autolag='AIC', regression='ct') print(f'ADF统计量: {adf_result[0]:.4f}') print(f'p值: {adf_result[1]:.4f}')autolag='AIC'让函数自动选择最优滞后阶数,避免手动定滞后影响检验结果。p值小于0.05可以认为拒绝单位根原假设,序列平稳;大于0.05则需要差分。对原始序列做一阶差分后再次检验,直到通过为止:
diff1 = df['value'].diff().dropna() adf_diff1 = adfuller(diff1, autolag='AIC', regression='c') print(f'一阶差分后p值: {adf_diff1[1]:.4f}')提示:小论文里切不要只写“差分后p值小于0.05”,要写清楚差分次数、回归形式、滞后阶数这三个前提。同一个序列,用
regression='c'和regression='ct'得到的结果可能相反。
差分次数不能无限增加,过差分会让序列丢失长期信息,方差被放大。通常差到d=1或d=2就停,d>2在小论文里基本站不住脚,评阅人会怀疑数据预处理环节出了问题。
3.2 ACF和PACF拖尾/截尾怎么看
差分成平稳序列后,画ACF和PACF图,这是ARIMA定阶最直观的一步。注意plot_acf和plot_pacf的lags参数,数据量少于100时lags=40会画出太多无关置信区间,反而干扰判断:
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf fig, axes = plt.subplots(2, 1, figsize=(10, 6)) plot_acf(diff1, ax=axes[0], lags=min(30, len(diff1) // 3)) plot_pacf(diff1, ax=axes[1], lags=min(30, len(diff1) // 3)) plt.tight_layout() plt.savefig('acf_pacf.png', dpi=150)3.2.1 看图定阶的三种情况
第一种:ACF拖尾、PACF在滞后k阶处截尾,对应AR(k)模型;第二种:ACF在滞后k阶处截尾、PACF拖尾,对应MA(k)模型;第三种:ACF和PACF都拖尾,说明纯ARIMA结构不够,需要考虑SARIMA或对数据做变换。实际业务数据里第三种最常见,所以不要在这里纠结太久,靠信息准则辅助定阶是更省力的路线。
3.3 信息准则辅助定阶
小论文里不要只写一行auto_arima的结果,评委想看的是“你比较过哪些候选模型”。用一个小网格搜索遍历参数组合,把AIC、BIC记录下来,形成一个可展示的候选表:
import itertools import warnings from statsmodels.tsa.statespace.sarimax import SARIMAX warnings.filterwarnings('ignore') results = [] for p, d, q in itertools.product(range(3), [1], range(3)): try: model = SARIMAX(diff1, order=(p, d, q), trend='n', enforce_stationarity=False) fit = model.fit(disp=False) results.append((p, d, q, fit.aic, fit.bic)) except Exception: continue candidate_df = pd.DataFrame(results, columns=['p', 'd', 'q', 'AIC', 'BIC']) print(candidate_df.sort_values('AIC').head(5))enforce_stationarity=False表示在拟合阶段不强求模型满足平稳条件,交给数据自己判断,这能减少迭代报错的概率;trend='n'因为在差分上建模,原序列的趋势已经由差分处理。按AIC升序取前5个候选,再结合ACF/PACF图的判断做最终选择。
| 候选模型 | AIC | BIC | 选择理由 |
|---|---|---|---|
| ARIMA(1,1,1) | 1023.4 | 1035.2 | AIC最小,且从AR到MA各留一阶,残差大概率噪声 |
| ARIMA(2,1,0) | 1025.1 | 1034.0 | 模型更简洁,BIC更优,样本量小可考虑 |
| ARIMA(0,1,2) | 1028.9 | 1039.7 | 仅做参考 |
写进小论文时,把这张表放进去,结论写明“按AIC最小原则选择ARIMA(1,1,1)”,后文所有预测都基于这个模型,全篇就有一条完整的决策链。
4. 时间序列模型拟合、诊断与滚动验证
4.1 划分训练集与测试集
时序预测不能用随机拆分,只能用前段训练、后段验证。拆分的比例取决于预测量级:如果能观测到的历史数据足够长,可以留出最后10%做验证;如果样本量特别小,留出20%会导致训练集信息不足。一个相对稳妥的做法是先看数据总量,少于200个观测时留10%即可。
n_test = int(len(df['value']) * 0.1) train = df['value'].iloc[:-n_test] test = df['value'].iloc[-n_test:]注意iloc切分前要确认索引已经按时间排序,否则切出来的一段可能时间不连续。切分后看一眼train.index[-1]和test.index[0],确认没有断开或错位。
4.2 SARIMAX拟合与关键参数说明
定好阶数后,正式拟合模型。数据里有明显周季节性时,用SARIMA而不是纯ARIMA,季节周期m要和前面STL分解时用的period保持一致:
from statsmodels.tsa.statespace.sarimax import SARIMAX model = SARIMAX(train, order=(1, 1, 1), seasonal_order=(0, 1, 1, 7), trend='c', enforce_stationarity=False, enforce_invertibility=False, time_varying_regression=False) fit = model.fit(disp=False) print(fit.summary())order=(1,1,1)中的三个数字分别对应非季节部分的AR阶数、差分次数、MA阶数,与3.3节里选的阶数一致。seasonal_order=(0,1,1,7)中前三个数字是季节部分对应的阶数,最后的7是季节周期长度,这是SARIMA和ARIMA最关键的区别。trend='c'表示在模型里加入常数漂移项,适合有明显长期增长但已经差分过一遍的序列。
注意:
enforce_stationarity和enforce_invertibility两个参数在拟合阶段应保持默认关闭,遇到优化器收敛告警时,优先检查阶数是否过大,而不是直接改这两个参数。
4.3 残差诊断
模型拟合完,不能只看预测准不准,得先确认残差是白噪声。这里用Ljung-Box检验:
from statsmodels.stats.diagnostic import acorr_ljungbox resid = fit.resid lb_test = acorr_ljungbox(resid, lags=[7, 14, 21], return_df=True) print(lb_test)lags=[7, 14, 21]三个滞后阶分别对齐一周、两周、三周周期,能够捕获周期内残留的自相关。return_df=True让输出变成DataFrame,直接看lb_pvalue列:全部大于0.05则残差无显著自相关,模型结构抓干净了。如果有某个阶数的p值小于0.05,回到3.3节调整阶数,或者考虑加大季节部分阶数。
残差方差是否稳定也很重要。画出标准化残差图,观察是否在某段区间出现“喇叭口”形状:
plt.figure(figsize=(10, 3)) plt.plot(resid / resid.std()) plt.axhline(y=0, color='black', linestyle='--', linewidth=0.8) plt.savefig('resid_std.png', dpi=150)如果残差分段的波动幅度差异明显,说明方差不稳定,常见做法是对原始数据做log1p变换后重新走一遍流程。
4.4 滚动验证评估稳定性
小论文里放一个总体的RMSE会显得单薄,因为单次划分训练/测试集的结果受切分点位置影响很大。更常用的做法是做滚动一步预测,逐点评估模型在每一步的误差:
import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error history = list(train.values) predictions = [] for t in range(len(test)): model = SARIMAX(history, order=(1, 1, 1), seasonal_order=(0, 1, 1, 7), trend='c', enforce_stationarity=False) fit = model.fit(disp=False) yhat = fit.forecast(steps=1).iloc[0] predictions.append(yhat) history.append(test.values[t]) # 把真实值加进历史,模拟在线更新 mae = mean_absolute_error(test.values, predictions) rmse = np.sqrt(mean_squared_error(test.values, predictions)) print(f'MAE: {mae:.4f}, RMSE: {rmse:.4f}')这一步循环每迭代一次就重新拟合模型,计算开销比一次性拟合高出不少,但换来的是稳定的误差曲线。把每步的yhat和真实值存下来,画成“预测值对比测试值”的折线图,比只报一个RMSE更能说明问题。
5. 时间序列预测结果的可视化与可复现输出
5.1 让matplotlib出图直接能放进Word
默认的matplotlib中文字体会在Word里变成一串方块。在画图脚本顶部统一配置:
import matplotlib matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS'] matplotlib.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150axes.unicode_minus=False用来修正负号显示成方框的问题。figure.dpi=150保证导出图的清晰度,Word页面宽度有限,figsize=(8, 4)左右的图在文档里最协调,长宽比太大会被缩放导致字看不清。
5.2 把DataFrame导出成Word可粘贴的三线表
表格直接从DataFrame截屏放进Word很丑,而且以后数据更新了还要重新截。最省事的方式:
candidate_df.round(2).to_clipboard()执行后直接到Word里粘贴成表格,再手动加上下粗线、表头细线,做成三线表。这个操作在Excel或WPS里同样适用,比导出为图片再插入要干净得多。如果表格列数多,可以先用.T转置再复制;超过八列的宽表在Word里横排会被截断。
5.3 把所有关键参数集中到一个配置区
做完整轮分析后,把会变的参数统一提到脚本顶部,这样小论文里所有图表都可以在一分钟以内重新生成:
CONFIG = { 'data_path': 'sales.csv', 'date_col': 'date', 'value_col': 'value', 'freq': 'D', 'period': 7, 'adf_regression': 'ct', 'order': (1, 1, 1), 'seasonal_order': (0, 1, 1, 7), 'trend': 'c', 'train_ratio': 0.9, 'lags': [7, 14, 21], }接下来所有脚本都引用CONFIG['order']、CONFIG['period']而不是写成固定数字。评审问“为什么用7不用8”的时候,直接改CONFIG['period'] = 8重跑一遍,对比两张图,远比现场翻代码找参数再重新执行一整套分析要从容。整套流程跑完,从数据解析、平稳性检验到模型诊断、滚动预测,每一步都能对应上脚本中的一段代码和一份输出,这篇小论文就不再是“抄一个模板”,而是一套随时可以重放的分析管道。
本文还有配套的精品资源,点击获取