文章目录
- 一、删除法
- 二、替换法
- 三、插值法
- 四、滑动窗口
- 五、基于模型的替换
时间序列相关参考文章:
时间序列预测算法—ARIMA
时间序列预测算法—Prophet
时间序列分类任务—tsfresh
python时间序列处理
有季节效应的非平稳序列分析
时间序列异常值检测方法
时间序列异常值处理方法
一、删除法
import pandas as pd
from scipy.stats import zscore
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
data = pd.read_excel('data.xlsx')
data['ZScore'] = zscore(data['电量'])
outliers_removed = data[data['ZScore'].abs() <= 3]
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
axes[0].plot(data['date'],data['电量'], color='blue', alpha=0.6)
axes[0].set_title('Original Data')
axes[0].set_xlabel('date')
axes[0].set_ylabel('电量')
axes[0].grid(True)
axes[1].plot(outliers_removed['date'],outliers_removed['电量'], color='green', alpha=0.6)
axes[1].set_title('Cleaned Data')
axes[1].set_xlabel('date')
axes[1].set_ylabel('电量')
axes[1].grid(True)
plt.show()
二、替换法
替换法中包括均值、中位数、众数等。下图只展示中位数可视化方法,均值,众数修改计算条件同中位数。
import pandas as pd
from scipy.stats import zscore
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
# 读取数据
data = pd.read_excel('data.xlsx')
# 计算 Z-score
data['ZScore'] = zscore(data['电量'])
# 设置阈值来识别异常值,Z-score 大于 3 的被认为是异常值
threshold = 3
# 计算中位数
median_value = data['电量'].median() #26659.4476
#mean_value = data['电量'].mean() #26533.86 均值
# 将异常值替换为中位数,生成新的数据集
cleaned_data = data.copy() # 创建数据的副本
cleaned_data['电量'] = cleaned_data.apply(
lambda row: median_value if abs(row['ZScore']) > threshold else row['电量'], axis=1
)
# 重新计算 Z-score 以确认替换后的数据
cleaned_data['ZScore'] = zscore(cleaned_data['电量'])
# 绘图
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
# 原始数据图
axes[0].plot(data['date'], data['电量'], color='blue', alpha=0.7)
axes[0].set_title('Original Data with Outliers')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('电量')
axes[0].grid(True)
# 清理后的数据图
axes[1].plot(cleaned_data['date'], cleaned_data['电量'], color='green', alpha=0.7)
axes[1].set_title('Cleaned Data (Outliers Replaced)')
axes[1].set_xlabel('Date')
axes[1].set_ylabel('电量')
axes[1].grid(True)
plt.show()
三、插值法
线性插值:使用邻近的正常值进行插值。通常适用于时间序列数据,替换方法就是在异常值前后点之间进行线性插值。方法:value = (time_series[i-1] + time_series[i+1]) / 2 或者使用更复杂的插值函数。
多项式插值:适用于时间序列变化呈非线性趋势时,可以使用低次多项式进行插值。
前向/后向填充:对于短期的突发异常,可以选择用前一个或后一个正常值来填充,特别是对于电力、金融等行业的数据。方法:value = previous_value 或 value = next_value
- 线性插值
import pandas as pd
from scipy.stats import zscore
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
data = pd.read_excel('data.xlsx')
data['ZScore'] = zscore(data['电量']) # 计算 Z-score
threshold = 3 # 设置阈值来识别异常值,Z-score 大于 3 的被认为是异常值
outliers = abs(data['ZScore']) > threshold # 标记异常值位置
replaced_data = data.copy()
# 将异常值标记为 NaN(缺失值),以便进行插值
replaced_data['电量'] = replaced_data['电量'].where(~outliers, other=None)
replaced_data['电量'] = replaced_data['电量'].interpolate(method='linear') # 使用线性插值替换异常值
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
# 原始数据图
axes[0].plot(data['date'], data['电量'], color='blue', alpha=0.7)
axes[0].set_title('Original Data with Outliers')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('电量')
axes[0].grid(True)
# 清理后的数据图
axes[1].plot(replaced_data['date'], replaced_data['电量'], color='green', alpha=0.7)
axes[1].set_title('Cleaned Data (Outliers Replaced with Linear Interpolation)')
axes[1].set_xlabel('Date')
axes[1].set_ylabel('电量')
axes[1].grid(True)
plt.show()
- 前后项填充
import pandas as pd
from scipy.stats import zscore
import matplotlib.pyplot as plt
# 设置中文显示和负号显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
data = pd.read_excel('data.xlsx')
data['ZScore'] = zscore(data['电量']) # 计算 Z-score
threshold = 3 # 设置阈值来识别异常值,Z-score 大于 3 的被认为是异常值
outliers = abs(data['ZScore']) > threshold # 标记异常值位置
replaced_data = data.copy()
replaced_data['电量'] = replaced_data['电量'].where(~outliers, other=None) # 将异常值标记为 NaN(缺失值)
# 使用前向填充法(前后值插值)
# replaced_data['电量'] = replaced_data['电量'].ffill() # 前向填充
replaced_data['电量'] = replaced_data['电量'].bfill() # 后向填充(若前向无值)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
# 原始数据图
axes[0].plot(data['date'], data['电量'], color='blue', alpha=0.7)
axes[0].set_title('Original Data with Outliers')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('电量')
axes[0].grid(True)
# 清理后的数据图
axes[1].plot(replaced_data['date'], replaced_data['电量'], color='green', alpha=0.7)
axes[1].set_title('Cleaned Data (Outliers Replaced with Forward/Backward Fill)')
axes[1].set_xlabel('Date')
axes[1].set_ylabel('电量')
axes[1].grid(True)
plt.show()
四、滑动窗口
使用滑动窗口方法计算异常值的周围均值、中值或其他统计量,将异常值替换为滑动窗口内的均值或中值。例如,使用一个固定大小的窗口,在窗口内计算统计量,并用窗口中心的统计值替换异常点。
import pandas as pd
from scipy.stats import zscore
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
data = pd.read_excel('data.xlsx')
# 计算 Z-score
data['ZScore'] = zscore(data['电量'])
# 设置阈值来识别异常值,Z-score 大于 3 的被认为是异常值
threshold = 3
# 设置异常值前后的窗口大小
window_size = 3
# # 用附近的平均值替换异常值
# for i in range(window_size, len(data) - window_size): # 遍历数据,跳过前后 window_size 天
# # 检查当前点是否是异常值
# if abs(data['ZScore'][i]) > threshold:
# # 获取当前点的前后窗口数据,计算窗口内的平均值
# window_values = data['电量'][i - window_size:i + window_size + 1]
# mean_value = window_values.mean()
# # 替换异常值为附近的平均值
# replaced_data.loc[i, '电量'] = mean_value
# 使用 rolling() 函数计算窗口内的均值 (如:前后 3 天,即 7 天窗口)
rolling_mean = data['电量'].rolling(window=2*window_size + 1, min_periods=1).mean()
# 创建数据副本以保存清理后的数据
replaced_data = data.copy()
# 替换异常值为附近的平均值
for i in range(window_size, len(data) - window_size): # 遍历数据,跳过前后 window_size 天
if abs(data['ZScore'][i]) > threshold: # 如果是异常值
replaced_data.loc[i, '电量'] = rolling_mean[i] # 用滚动窗口均值替换异常值
# 重新计算 Z-score 以确认替换后的数据
replaced_data['ZScore'] = zscore(replaced_data['电量'])
# 绘图
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
# 原始数据图
axes[0].plot(data['date'], data['电量'], color='blue', alpha=0.7)
axes[0].set_title('Original Data with Outliers')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('电量')
axes[0].grid(True)
# 替换后的数据图
axes[1].plot(replaced_data['date'], replaced_data['电量'], color='green', alpha=0.7)
axes[1].set_title('Data with Outliers Replaced by Nearby Mean')
axes[1].set_xlabel('Date')
axes[1].set_ylabel('电量')
axes[1].grid(True)
# 显示图表
plt.show()
五、基于模型的替换
如果时间序列的数据模式较为复杂,简单的替换方法可能无法较好地处理异常。此时可以使用基于模型的方法来替代异常值:
ARIMA 模型:使用时间序列建模的方法(如 ARIMA)来预测每个时刻的值,异常值可以通过模型的预测值进行替代。可参考时间序列ARIMA
LSTM 或其他深度学习方法:对于具有长时间依赖性的序列数据,可以训练 LSTM 等神经网络模型来预测序列值,并用模型预测值替换异常值。
在某些情况下,数据的异常值与其他特征存在一定的关系,可以使用回归模型(如线性回归、决策树回归)根据其他变量预测异常值的合理范围。
线性回归法:可以利用时间序列的历史数据和其他相关变量来预测异常值的合理值。