时序预测入门全攻略!(万字长文代码演示)
提升你的时间序列预测能力:准确的数据拆分、时间序列交叉验证、特征工程等!
前言
本文属于搬运内容,原作者:Sara Nóbrega(全体起立!)原文链接已在文章末尾。
时序预测不同于普通的回归预测,其主要差距来源于序列概念。让我们开始入门吧!
为什么要谨慎对待时间序列分析
时间序列数据的分析通常并非易事。
这种类型的数据具有独特的特性和挑战,这些特点在其他数据集中并不常见。
例如,时间序列数据的观察值需要按照时间顺序处理。如果数据科学家忽视了这一点,不仅会导致模型表现不佳,更可能会导致完全错误的预测结果。
在这篇文章中,我们将使用一个真实的数据集来讨论这些挑战,并提供代码示例,确保结果的可复现性。
如果没有正确处理时间序列数据,你可能会构建出一个在训练中表现良好的模型,但在实际应用中却完全无法使用。
这是因为时间序列数据有一个根本特点——它是随着时间变化的。这意味着必须重视季节性、趋势性和平稳性等模式!
忽略以下关键因素可能会导致数据泄露或模型评估偏差:
- 如何在保留时间完整性的同时拆分数据
- 考虑季节性因素
- 处理缺失值
接下来,让我们一起探索这些重要的方面。
加载数据
我们将使用 Kaggle 的 Electric Production 时间序列数据集,你可以在这里下载。
以下是数据预处理代码:
# 数据预处理:将 'DATE' 转换为 datetime 格式并设置为索引
file_path = 'Electric_Production.csv'
df = pd.read_csv(file_path)
df['DATE'] = pd.to_datetime(df['DATE'])
df.set_index('DATE', inplace=True)
df.rename(columns={'IPG2211A2N': 'value'}, inplace=True)
接着,我们来看看数据:
# 绘制数据
plt.figure(figsize=(10, 6))
plt.plot(df['DATE'], df['value'], label='Electric Production')
# 添加标题和标签
plt.title('Electric Production Over Time')
plt.xlabel('Date')
plt.ylabel('Electric Production Value')
plt.xticks(rotation=45)
plt.grid(True)
plt.legend()
# 展示图表
plt.show()

图 1:展示电力生产数据随时间变化的折线图 | 作者绘制
(这里你可以进行许多探索性数据分析(EDA),但这里就不过多提及,毕竟不是本文的主旨。)
季节性和趋势:发现数据中的模式
什么是季节性?
季节性指的是数据中在特定时间间隔(如每天、每周、每月或每年)出现的周期性波动。
例如:
- 零售销售额通常在假期期间达到峰值。
- 电力消耗可能在夏季因空调使用而激增。
识别这些季节性模式非常重要,因为它们会显著影响模型的预测能力。忽视季节性可能会导致预测结果不准确,从而错失商业机会。
识别季节性
识别数据中的季节性可以通过可视化来实现。绘制时间序列图通常能揭示明显的周期模式。
不过,若想采用更严格的方法,可以使用分解技术。以下是一个使用 STL 分解(季节性-趋势-残差分解)的示例:
from statsmodels.tsa.seasonal import STL
import matplotlib.pyplot as plt
# 假设 'df' 是你的 DataFrame,其中有一个 'value' 列
stl = STL(df['value'], seasonal=12) # 根据数据调整 'seasonal' 参数
result = stl.fit()
# 提取分量
trend = result.trend
seasonal = result.seasonal
residual = result.resid
# 绘制分解结果
plt.figure(figsize=(12, 8))
plt.subplot(411)
plt.plot(df['value'], label='原始时间序列')
plt.legend(loc='upper left')
plt.subplot(412)
plt.plot(trend, label='趋势分量')
plt.legend(loc='upper left')
plt.subplot(413)
plt.plot(seasonal, label='季节性分量')
plt.legend(loc='upper right')
plt.subplot(414)
plt.plot(residual, label='残差分量')
plt.legend(loc='upper right')
plt.tight_layout()
plt.show()

图 2:STL 分解后的时间序列数据示例,包括季节性、趋势和残差分量 | 作者绘制
从图中可以看到,该数据具有年度季节性,并且呈现上升趋势。这种特征在能源生产数据中很常见,因为能源生产往往随季节变化。
STL 分解中的 seasonal 参数 用于控制季节性分量的平滑程度。你可以根据数据的频率(如每月、每季度)调整该参数。
注意:
STL 假设数据点按时间顺序排列。分解方法要求数据点严格按照其时间顺序排列。
理解趋势
与季节性捕捉可预测的波动不同,趋势表示数据的长期变化方向。
趋势可以是上升的、下降的或平坦的。识别趋势至关重要,因为趋势会影响预测的整体方向。
要分析趋势,可以通过滚动平均来平滑短期波动。以下是实现滚动平均的示例:
# 计算 12 个月滚动平均
df['rolling_avg'] = df['value'].rolling(window=12).mean()
plt.figure(figsize=(10, 5))
plt.plot(df['value'], label='原始数据')
plt.plot(df['rolling_avg'], label='12 个月滚动平均', color='orange')
plt.title('原始数据与滚动平均的对比')
plt.legend()
plt.show()

图 3:电力生产原始数据与 12 个月滚动平均趋势的对比 | 作者绘制
此图能够清晰地展现季节性波动中的趋势,为数据的整体方向提供更清晰的视图。
滚动窗口的选择
12 个月滚动平均通过对整年的数据取平均,平滑了短期波动,并揭示了长期趋势。
- 如果你希望捕捉短期趋势,同时避免过度平滑,可以尝试 6 个月窗口。
- 若进行长期规划且想减少噪声干扰,则可以选择 24 个月窗口。
通过调整窗口大小,你可以根据分析目标在灵敏度与平滑效果之间取得平衡。
季节性和趋势意识的重要性
将季节性和趋势分析融入时间序列建模中,可以显著提高预测的准确性。
此外,识别这些模式对特征工程也有帮助。你可以创建能够捕捉季节性效应的特征,例如月份或季度,或利用滞后变量来反映趋势。
这些特征能够增强模型对数据的理解,从而带来更好的预测效果!
时间序列数据中的平稳性:关键变换解析
接下来,我们来探讨时间序列分析中的另一个关键点,这也是许多经验丰富的数据科学家常常感到棘手的问题:平稳性。
为什么平稳性很重要?
平稳性就像时间序列数据的“自在极意” 😃。
它的定义是,数据的统计特性(如均值和方差)在时间上保持不变。
为什么这很重要?
许多时间序列模型都假设输入数据是平稳的。如果数据不平稳,模型的结果可能完全不可靠。
以下是检查平稳性的一个示例:
from statsmodels.tsa.stattools import adfuller
def check_stationarity(timeseries):
result = adfuller(timeseries, autolag='AIC')
return result[1] # 返回 p 值
# 假设 'df' 是一个包含多个列的 DataFrame
for column in df.columns:
p_value = check_stationarity(df[column])
print(f"Column '{column}': p-value = {p_value}")
if p_value <= 0.05:
print(f" The series '{column}' is likely stationary")
else:
print(f" The series '{column}' is likely non-stationary")
print()
程序输出如下:
Column 'value': p-value = 0.18621469116586592
The series 'value' is likely non-stationary
在这里,我们使用了 ADF 检验(Augmented Dickey-Fuller Test)。
ADF 检验的原理
简单来说,ADF 检验通过检验零假设(即时间序列存在单位根)来判断时间序列是否平稳。
- 如果存在单位根,说明时间序列非平稳。
- 如果没有单位根,说明时间序列平稳。
在上述示例中,数据集是非平稳的(p 值 > 0.05)。
如果你的 DataFrame 包含多个列,则需要分别对每一列进行测试。
如何判断平稳性
- 如果 p 值小于 0.05,则可以认为该数据是平稳的。(p值的检验标准和更具你选的significance level改变)
- 如果 p 值大于 0.05,不用慌——我们有解决方案!
后续可以使用一些技术(如差分或变换)来让数据达到平稳状态,从而满足模型的假设条件。
转换非平稳数据(不够自在极意🤣)
如果你的数据不是平稳的,可以尝试以下常用的转换方法:
1. 差分
通过从每个观测值中减去前一个值,消除趋势。
df['diff'] = df['value'].diff()应用差分后,diff 列会移除趋势,使序列更接近平稳状态。这对于许多预测模型来说是理想的。
2. 对数变换
对指数趋势的数据特别有效:
df['log'] = np.log(df['value'])3. 移动平均
通过滚动平均平滑短期波动:
df['MA'] = df['value'].rolling(window=12).mean()转换后重新检查平稳性
在对时间序列数据应用转换后,需要再次验证是否达到了平稳状态:
print("检查原始数据的平稳性:")
check_stationarity(df['value'])
print("\n检查差分后的平稳性:")
# 在检查平稳性前,需去除差分后的 NaN 值
check_stationarity(df['diff'].dropna())
反转转换以获得预测结果
当你使用在变换过的数据上训练的模型进行预测时,需要“反转”这些变换,才能得到有意义的结果。
以下是如何操作的示例(基于另一个数据集):
import numpy as np
# 假设我们在经过对数差分的数据上训练了模型
# 并生成了一些预测值
log_diff_predictions = model.predict(X_test)
# 第一步:反转对数变换
diff_predictions = np.exp(log_diff_predictions)
# 第二步:反转差分
# 需要从最后一个真实值开始反推
last_actual_value = df['value'].iloc[-1]
original_scale_predictions = []
for diff in diff_predictions:
prediction = last_actual_value + diff
original_scale_predictions.append(prediction)
last_actual_value = prediction
# 现在,'original_scale_predictions' 包含了以原始数据单位表示的预测值
print("原始尺度的预测值:")
print(original_scale_predictions)
说明
- 差分反转:通过累加预测的差分值和最后的真实值,恢复原始尺度的预测。
- 对数反转:应用指数运算(
np.exp)来逆转对数变换。
在这个示例中,我们首先使用 np.exp() 反转对数变换,然后通过累加差分值与上一个已知的真实值,逐步恢复原始尺度的预测值。
为什么这很重要?
想象一下,你正在向 CEO 展示销售预测:
“我们的模型预测下个月的销售额将是 1.2。”
CEO: “1.2 什么?百万?千元?”
你: “哦,这是上个月销售额差分后的自然对数。”
CEO: 😕
相反,通过反转这些变换,你可以自信地说:
“我们的模型预测下个月的销售额将是 450 万美元,比上个月增加了 50 万美元。”
CEO: 😃
这正是数据科学的目标——提供可执行的洞见!
时间序列特征工程:打造完美的数据“配方”
与传统数据集(特征通常是静态的)不同,时间序列数据拥有独特的时间依赖性模式,必须通过提取相关特征加以利用。
在这一部分,我们将探索一些最有效的特征工程技术。
滞后特征:利用过去的力量
时间序列特征工程中最简单且最强大的技术之一,就是创建滞后特征。
这些特征捕获目标变量的历史值,用于预测未来的值。毕竟,昨天的趋势往往能很好地指示明天的方向。
例如,你可以引入过去几个月或几年的能源生产数据作为特征,帮助模型更好地理解历史模式:
df['lag_1'] = df['value'].shift(1) # 创建前一个时间步的滞后特征
df['lag_7'] = df['value'].shift(7) # 创建 7 个月前的滞后特征核心理念:
通过对时间序列数据进行偏移(shift),我们让模型能够“回顾”过去的观测值,从而学习数据中的时间依赖性模式。
这对时间序列预测非常关键,因为过去的模式往往会延续到未来。
通过这些方法,你可以将预测值转换回原始数据的尺度,便于解读和使用。

例如,当你通过偏移数据创建滞后特征(例如 lag_1)时,前一行的数据值会被用作当前行的特征。然而,对于第一行数据,由于没有“前一行”可以引用,lag_1 列中的值会变为 NaN。
滚动统计:平滑噪声
时间序列数据通常伴有噪声,使得模型难以捕捉真正的模式。这时我们可以使用滚动统计来解决。
通过计算滚动平均值或其他统计指标(例如滚动标准差),我们可以平滑短期波动,突出显示潜在的趋势。
例如,为了平滑数据并观察更广泛的趋势,可以计算指定窗口(如 12 个月)的滚动平均值:
df['rolling_mean_12'] = df['value'].rolling(window=12).mean() # 12 个月滚动平均
df['rolling_std_12'] = df['value'].rolling(window=12).std() # 12 个月滚动标准差

图 5:滚动平均和标准差的示例 | 图片由作者提供。
解释:
rolling(window=12):这将创建一个 12 个时间步长(在本例中为月)的滚动窗口,用于计算滚动平均值。标准差的计算方法相同。
- 使用这些滚动统计,你可以获得数据的更稳定视图,使模型更容易捕捉长期趋势。
日期时间特征:提取时间信息
时间序列数据不仅关注数值随时间的变化,还需要了解这些变化发生的时间。
日期时间特征(如一天中的小时、一周中的某天、或一年中的月份)能够提供重要的见解,尤其当数据表现出季节性模式时。
你可以使用 Pandas 轻松提取这些特征:
df['hour'] = df.index.hour
df['day_of_week'] = df.index.dayofweek
df['month'] = df.index.month
df['year'] = df.index.year
对于本文使用的数据集,提取“一年中的月份”是合理的,因为月度数据通常会表现出季节性模式(例如,冬季或夏季的能源消耗较高)。
优势:
这些特征可以让模型识别周期性模式和季节性效应,从而使预测更加可靠。
差分:处理趋势和季节性
在某些情况下,时间序列数据可能表现出强烈的趋势或季节性,导致其非平稳。
为了解决这个问题,你可以创建差分特征,通过捕捉连续时间步之间的变化而不是绝对值来帮助模型。这可以帮助模型专注于随时间变化的差异,而不是序列本身的水平:
df['diff_1'] = df['value'].diff(1) # 连续观测值之间的差异
df['diff_7'] = df['value'].diff(7) # 7个月之间的差异
这些差分特征可以帮助稳定序列的均值,特别是在趋势或季节性变化主导时。
循环特征:处理周期性
当处理季节性数据时,简单的数值表示(例如月份或日期)可能无法满足预期。
例如,12月(第12个月)和1月(第1个月)之间的差异在现实中仅为一个月,但在数值上看起来会更大。为了解决这个问题,你可以通过将时间相关特征转换为正弦和余弦函数来创建循环特征:
df['sin_month'] = np.sin(2 * np.pi * df['month'] / 12)
df['cos_month'] = np.cos(2 * np.pi * df['month'] / 12)
这样,你就可以让模型更好地理解数据中的周期性关系,例如季节性高峰和低谷。
外部特征:为数据添加上下文
最后,别忘了时间序列数据并不是孤立存在的!
通常,加入外部数据(如天气状况、节假日或经济指标)可以显著提高模型的性能。
例如,在我们的数据中,加入温度数据作为特征或日照小时数可能会为模型提供重要的提升(如果这些数据可用):
df['avg_temperature'] = external_weather_data['avg_temp']
df['max_temperature'] = external_weather_data['max_temp']
df['sunlight_hours'] = external_weather_data['sunlight_hours']
这些外部特征为模型提供了背景信息,帮助其理解影响时间序列数据的外部因素。
时间序列数据拆分:如何避免机器学习中的数据泄漏
数据拆分是任何机器学习项目中的第一步,在时间序列分析中尤为重要。
与传统数据集中的数据点独立不同,时间序列数据是顺序性的,意味着观测值依赖于之前的值。
如果将这些数据随机拆分为训练集和测试集,可能会导致数据泄漏,即模型在训练过程中接触到未来信息,从而导致性能指标人为地被夸大。
时间序列数据的顺序拆分
在时间序列数据中,我们需要遵循观察数据的时间顺序。
最简单且最有效的方法是顺序拆分,即训练集包含某一时间点之前的数据,测试集包含该时间点之后的所有数据。
这种方式确保了在训练过程中,模型无法访问未来的信息。
如何顺序拆分数据
在这个示例中,我们将创建一个训练集,其中包括某个指定日期之前的所有数据点,测试集则包含剩余的数据。
许多实践者在拆分时间序列数据时使用Scikit-learn库中的TimeSeriesSplit:
import matplotlib.pyplot as plt
from sklearn.model_selection import TimeSeriesSplit
# 初始化 TimeSeriesSplit
tscv = TimeSeriesSplit()
# 绘制训练集拆分图,并显示日期范围
plt.figure(figsize=(10, 6))
for i, (train_index, test_index) in enumerate(tscv.split(df)):
train_data, test_data = df.iloc[train_index], df.iloc[test_index]
# 绘制带透明度的训练集拆分
plt.plot(train_data.index, train_data['value'], label=f"Train Split {i+1}", alpha=0.5)
# 获取训练数据的日期范围
train_start, train_end = train_data.index[0], train_data.index[-1]
# 打印训练集的日期范围和样本数
print(f"Train Split {i+1}: {train_start.date()} to {train_end.date()} ({len(train_data)} samples)")
plt.title("TimeSeriesSplit - Train Splits")
plt.xlabel("Date")
plt.ylabel("Electric Production Value")
plt.legend()
plt.xticks(rotation=45)
plt.grid(True)
plt.show()

图6:TimeSeriesSplit 可视化,显示电力生产数据随时间变化的训练集拆分。
它打印出:
Train Split 1: 1985-01-01 to 1993-04-01 (100 samples)
Train Split 2: 1985-01-01 to 2001-07-01 (199 samples)
Train Split 3: 1985-01-01 to 2009-10-01 (298 samples)
测试集拆分图和日期范围打印
# 绘制测试集拆分图,并显示日期范围
plt.figure(figsize=(10, 6))
for i, (train_index, test_index) in enumerate(tscv.split(df)):
test_data = df.iloc[test_index]
# 绘制带透明度的测试集拆分
plt.plot(test_data.index, test_data['value'], label=f"Test Split {i+1}", linestyle='--', alpha=0.7)
# 获取测试数据的日期范围
test_start, test_end = test_data.index[0], test_data.index[-1]
# 打印测试集的日期范围和样本数
print(f"Test Split {i+1}: {test_start.date()} to {test_end.date()} ({len(test_data)} samples)")
plt.title("TimeSeriesSplit - Test Splits")
plt.xlabel("Date")
plt.ylabel("Electric Production Value")
plt.legend()
plt.xticks(rotation=45)
plt.grid(True)
plt.show()
通过这种方法,测试集的日期范围可以显示为:

Test Split 1: 1993-05-01 to 2001-07-01 (99 samples)
Test Split 2: 2001-08-01 to 2009-10-01 (99 samples)
Test Split 3: 2009-11-01 to 2018-01-01 (99 samples)顺序拆分能够有效防止数据泄漏,并确保模型训练时仅访问历史数据,测试时使用未来数据进行评估。
这段代码使用了 Sklearn 中的 TimeSeriesSplit 方法,按照时间顺序将数据拆分为训练集和测试集。
它通过指定拆分的次数来初始化拆分器,并在每次拆分时获取训练集和测试集的索引。这种方法可以在不同的时间段内评估模型的性能,而不会破坏数据的顺序。
如何调整 n_splits 参数?
- 数据大小:较大的数据集可以容纳更多的拆分,而较小的数据集可能需要较少的拆分,以确保每个拆分都有足够的数据。
- 模型评估需求:更多的拆分可以通过使用不同的子集进行测试,提供更为稳健的评估,但也可能增加计算成本。
时间序列中的交叉验证:为什么需要一些调整
让我们讨论一下,为什么传统的 K 折交叉验证可能会在处理时间序列数据时导致问题。
为什么传统的交叉验证不起作用?
记得我们之前讨论过时间序列数据的顺序性吗?
这就是为什么传统交叉验证方法在时间序列数据中可能会产生问题。
标准的 K 折交叉验证假设数据点是独立且同分布的 (i.i.d.)。但在时间序列中,明天依赖于今天,而今天依赖于昨天……
使用传统的交叉验证方法可能会导致:
- 数据泄漏(你的模型看到了未来 🔮)
- 过于乐观的性能估计
- 在训练中表现良好的模型,实际应用时却失败
时间序列交叉验证
时间序列交叉验证:这种方法尊重数据的时间顺序。
TimeSeriesSplit 会创建固定数量的拆分,每个训练集包含逐渐增大的数据集,但每个折叠测试的时间段不会与前一个测试期重叠。
例如,TimeSeriesSplit 的操作如下:
- 第 1 折:训练集为 [1, 2, 3],测试集为 [4]
- 第 2 折:训练集为 [1, 2, 3, 4],测试集为 [5]
- 第 3 折:训练集为 [1, 2, 3, 4, 5],测试集为 [6]
这种方式确保了模型在评估时是现实的(使用过去的数据训练,使用未来的数据测试)。
在我们的数据集中,只有一个特征列。仅使用“值”列是不够的,因为它仅表示当前时间步的数据,没有提供任何历史上下文。时间序列模型需要理解过去的数据如何影响未来的结果。
创建像滞后值(前一个观察值)或滚动统计量(时间段内的平均值或趋势)这样的特征,可以让模型学习时间上的模式和关系。
实现示例
import pandas as pd
import numpy as np
from sklearn.model_selection import TimeSeriesSplit
import matplotlib.pyplot as plt
# 加载数据集(确保 'DATE' 列为索引)
df = pd.read_csv('Electric_Production.csv', index_col='DATE', parse_dates=True)
# 创建滞后特征作为特征矩阵 X
df['lag_1'] = df['value'].shift(1)
df['lag_2'] = df['value'].shift(2)
df['lag_3'] = df['value'].shift(3)
# 删除因滞后操作而产生的 NaN 值
df = df.dropna()
# 定义特征 X 和目标变量 y
X = df[['lag_1', 'lag_2', 'lag_3']] # 特征矩阵:滞后值
y = df['value'] # 目标变量
# 应用 TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
# 时间序列交叉验证可视化
fig, ax = plt.subplots(figsize=(10, 5))
for i, (train_index, test_index) in enumerate(tscv.split(X)):
ax.plot(train_index, [i] * len(train_index), 'b-', label='Train' if i == 0 else '')
ax.plot(test_index, [i] * len(test_index), 'r-', label='Test' if i == 0 else '')
ax.set_title('Time Series Cross-Validation')
ax.set_xlabel('Sample Index')
ax.set_ylabel('CV Iteration')
ax.legend()
plt.tight_layout()
plt.show()

图 8:使用 5 个拆分的时间序列交叉验证可视化。每个折叠使用一个逐渐增大的训练集,并对未来的时间段进行测试。
本图说明了TimeSeriesSplit 是如何作用的,每一折渐进式的增加了训练集及未来时间的测试集。
时间序列交叉验证的技巧:
注意间隙:考虑在训练集和测试集之间添加间隙,以模拟实际的预测场景。
tscv = TimeSeriesSplit(n_splits=5, gap=2)
间隙创建了一个训练数据和测试数据之间的时间段,使得模型不会接触到它在实际预测中不会遇到的数据。
例如,gap=2 表示在交叉验证期间,训练集和测试集之间跳过了两个时间步长。
季节性考虑:如果你的数据有强季节性,确保你的训练集和测试集跨越完整的季节周期。
示例:
假设你在预测电力消耗,而电力消耗遵循强烈的年度周期:夏季由于空调使用需求更高,冬季由于取暖需求更高。
如果你把数据分割成这样:训练集包含部分夏季数据,但测试集包含对应的冬季数据,模型可能无法学习完整的年度季节性,从而导致表现不佳。
解决方案:
假设你的数据涵盖了3年(2018年1月 - 2020年12月)的月度读数。如果你在一年内将数据进行训练和测试拆分(例如,训练集为2019年1月至5月,测试集为2019年6月至12月),这样无法捕获完整的年度模式(包括夏季和冬季的影响)。
相反,你应该在完整的年份数据上训练模型(例如,训练2018年和2019年的数据),并在完整的季节或一年(例如,2020年)上进行测试。这样,模型可以学习完整的季节性模式。
前向链式交叉验证:为了获得更稳健的方法,可以尝试前向链式交叉验证:
def forward_chaining(X, n_splits):
for i in range(n_splits):
train_end = int(len(X) * (i + 1) / (n_splits + 1))
yield np.arange(train_end), np.arange(train_end, len(X))
# 使用方法
for train_index, test_index in forward_chaining(X, n_splits=5):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 训练和评估模型

前向链式交叉验证是一种时间序列交叉验证技术,其中训练集随着每次拆分逐渐增大,始终包含过去的数据,而测试集则包含未来的数据。
与 TimeSeriesSplit 不同,每个拆分在逐渐增大的数据集上训练,但它使用训练集之后的所有剩余数据进行测试,而不仅仅是固定的折叠。
时间序列中的异常值:当数据开始异常
检测和处理时间序列数据中的异常值是数据科学家在大多数项目中都会遇到的另一个挑战。
异常值是偏离数据普遍模式的极端值,它们可能会扭曲分析结果,破坏趋势,并对模型性能产生负面影响。
异常值可能是由多种原因引起的,如突如其来的经济变化、测量错误,或是稀有但重要的事件(如自然灾害)。
虽然这些异常可能包含有价值的洞察,但它们也可能误导模型,尤其是在预测任务中。
识别异常值:区分信号与噪声
时间序列数据中的异常值并不总是容易识别。在某一时间点看起来像异常值的数据,实际上可能是季节性模式或短期趋势的一部分。
常见的时间序列异常值检测方法包括:
移动平均和滚动窗口:从滚动均值中突然的偏离可能表示异常值。
统计测试:诸如 Grubbs 测试或 Z 分数等方法可以使用,但需要考虑数据的顺序性。
可视化:绘制时间序列数据通常是检查异常尖峰或波动的最佳起点。
以下文章待更新:
我已经写了一系列关于时间序列异常值的文章。
第一篇文章介绍了探索视觉和统计方法,以有效地识别时间序列数据中的异常值:
The Ultimate Guide to Finding Outliers in Your Time-Series Data (Part 1)
有效的统计方法和工具来检测时间序列分析中的异常值。
第二篇文章我们探讨了几种机器学习技术来识别异常值:
The Ultimate Guide to Finding Outliers in Your Time-Series Data (Part 2)
有效的机器学习方法和工具来检测时间序列分析中的异常值。
第三篇文章讨论了如何管理这些异常值,考虑到时间序列数据的特殊因素:
The Ultimate Guide to Finding Outliers in Your Time-Series Data (Part 3)
异常值已发现:如何处理?一种处理方法指南。
最后总结
时间序列分析呈现出与传统机器学习任务不同的独特挑战。
在本文中,我们探讨了六种优化时间序列分析的关键技术:时间序列数据分割、交叉验证、平稳性、季节性、特征工程,最后是检测和处理异常值。
- 合适的数据拆分技术使你避免了数据泄漏,确保模型无法接触到未来的信息。
- 季节性很重要,因为它帮助我们识别数据中的重复模式,从而提高时间序列预测模型的准确性。
- 检查平稳性和趋势是确保模型捕捉到正确模式并做出准确预测的关键。
- 交叉验证方法如 TimeSeriesSplit 和前向链式交叉验证可以确保遵守时间序列数据的顺序性。
- 特征工程,包括创建滞后值和滚动统计量特征,使我们能够捕捉数据中的时间模式。
- 最后,检测和处理异常值能确保模型的预测更具可靠性。
最终,时间序列建模的核心是理解数据的节奏,尊重其顺序性,并做出符合实际应用的决策。
保持好奇,祝你预测成功!📈🔮