时间序列中异常值处理影响的终极指南

时间序列异常值处理评估

原文:towardsdatascience.com/evaluating-the-impact-of-outlier-treatment-in-time-series-b4fac4cabe94

(如果你没有会员资格,请在此处阅读文章这里)。

**想象一下:**你正在处理时间序列数据,寻找模式并调查随时间的变化趋势。

你已经对你的时间序列数据进行了探索性数据分析,并寻找了检测数据中异常值的最优方法。

在检测后,你可能忽略了它们,移除了它们,或者最有可能的是,你将它们进行了转换

现在是时候评估这种处理的影响了:你的数据分布是如何变化的?你的机器学习模型在预测目标变量方面表现如何?

此外,人们可能会好奇:

  • 你将使用哪些指标来评估模型的性能?

  • 你将如何可视化数据分布的变化?

  • 什么因素可能影响了你模型的预测?

  • 数据中是否存在可能影响评估的偏差?

我们将使用数据集来回答这些问题,以便你可以重现结果。

本文是该系列关于异常值处理文章的第四部分。请确保查看其他三篇,以便你能有一个完整的关于如何在时间序列数据中识别和处理异常值的了解

  1. 时间序列数据中异常检测的统计方法和工具

  2. 时间序列数据中异常检测的机器学习方法和工具

  3. 找到异常值后怎么办?处理选项指南

你可能还想查看这篇关于掌握时间序列分析重要技术的文章:

掌握时间序列分析的 5 个必知技巧

在本系列的最后一篇文章中,我们将探讨你处理异常值的选择如何影响模型的表现


你好!

我的名字是Sara Nóbrega,我是一名**数据科学家,专注于人工智能工程**。我拥有物理学硕士学位,后来转型进入了令人兴奋的数据科学领域。

我写有关数据科学、人工智能和数据科学职业建议的文章。请确保**关注我订阅**,以便在发布下一篇文章时收到更新!

Sara 的数据科学免费资源


数据备注

模拟了代表两个月内能源生产的时序数据,使用的是每 10 分钟一次的测量间隔。

我生成这些数据是为了模拟现实世界的模式:能源生产在白天较高,而在夜间自然较低。

大约 10%的数据点被标记为异常值,以模拟由于异常事件或测量错误导致的产量峰值。

这个数据集作为示例,在我们通过一些代码片段来检查处理异常值的效果的多种技术时,具有说明性。

这里是如何生成这些数据的方法:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

# Setting parameters for dataset generation
start_date = datetime(2023, 1, 1)
end_date = datetime(2023, 3, 1)
time_interval = timedelta(minutes=10)

# Generate the datetime index with 10-minute intervals
datetime_index = pd.date_range(start=start_date, end=end_date, freq='10T')

# Generate base energy production values simulating daily cycles (higher in the day, lower at night)
np.random.seed(42)
base_energy = []
for dt in datetime_index:
    hour = dt.hour
    # Simulate higher production in daytime hours and lower at night
    if 6 <= hour <= 18:
        # Daytime production (higher)
        energy = np.random.normal(loc=300, scale=30)  # Example daytime mean production
    else:
        # Nighttime production (lower)
        energy = np.random.normal(loc=50, scale=15)  # Example nighttime mean production
    base_energy.append(energy)

# Convert base_energy to a pandas series for easier manipulation
energy_production = pd.Series(base_energy)

# Introduce outliers by replacing 10% of the data with extreme values
num_outliers = int(0.1 * len(energy_production))
outlier_indices = np.random.choice(len(energy_production), num_outliers, replace=False)
energy_production.iloc[outlier_indices] *= np.random.choice([1.5, 2, 2.5, 3], size=num_outliers)  # Scale up for outliers

# Creating the final DataFrame
energy_data = pd.DataFrame({'Datetime': datetime_index, 'Energy_Production': energy_production})

energy_data.head(5)
Datetime Energy_Production
0 2023-01-01 00:00:00 57.450712
1 2023-01-01 00:10:00 47.926035
2 2023-01-01 00:20:00 89.572992
3 2023-01-01 00:30:00 72.845448
4 2023-01-01 00:40:00 46.487699

让我们看看数据:

# Plotting the time-series data with outliers
plt.figure(figsize=(14, 6))
plt.plot(energy_data['Datetime'], energy_data['Energy_Production'], label='Energy Production', color='blue', alpha=0.7)
plt.xlabel('Time')
plt.ylabel('Energy Production (kW)')
plt.title('Simulated Energy Production with Outliers (10-Minute Intervals Over 2 Months)')
plt.legend()
plt.tight_layout()
plt.show()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e072ac5378b6746b808266fbfa923f5f.png

图 1:模拟能源生产时序图,包含两个月内每 10 分钟间隔的异常值 | 图片由作者提供

如何评估异常值处理的效果:实用指南

异常值在处理数据时是个麻烦。一方面,它们可能会扭曲你的分析或模型。另一方面,移除或修改它们有时会出错。

一方面,它们可以扭曲你的分析或模型。另一方面,移除或修改它们有时会出错。那么,你如何知道你在处理异常值时做的是正确的事情

为什么评估异常值处理的效果很重要?

这就是为什么你需要花点时间来评估效果的原因:

1. 模型准确性和可解释性

异常值可能会扭曲你的模型预测。但它们也可以揭示重要的见解。例如,客户消费的突然增加可能看起来非常异常,但它可能表明了一位高价值客户。因此,确保处理异常值不会导致误解是很重要的。

2. 模型鲁棒性

如果你不对模型的影响进行评估,它可能过于僵化。这意味着它在训练数据上可能表现良好。但当应用于新的或未见过的数据时,它的表现会很糟糕。

移除过多的异常值会使你的模型变得脆弱。而忽略它们则可能导致过度调整。

3. 保留有价值的信息

记住,并非所有异常值都是错误或异常。

在许多情况下,这些可能是你数据中的重要模式。因此,与该领域的专家进行咨询并试图了解这些差异背后的原因非常重要。因为如果你删除它们,可能会丢失重要的数据…

这就是为什么后处理评估是必要的,以确保你做出了关于保留或修改哪些数据点的最佳决策!

如何评估异常值处理的影响

在确定了为什么这种后评估是一个必要的过程之后,让我们深入了解如何检查异常值处理是否有所帮助。

以下是一些使用代码片段来展示如何做到这一点的技巧。

1. 处理前后的统计比较

一个简单的比较点可能是处理异常值前后的数据基本统计

接下来,想象一下你处理了异常值并决定对这些极端值进行截顶

你首先通过比较摘要统计来确定处理是否显著改变了数据分布的形状。

下面是如何在 Python 中做到这一点的示例:

 # Calculate outliers using the IQR method
Q1 = energy_data['Energy_Production'].quantile(0.25)
Q3 = energy_data['Energy_Production'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# Count outliers based on IQR method
outliers_count_iqr = ((energy_data['Energy_Production'] < lower_bound) | 
                      (energy_data['Energy_Production'] > upper_bound)).sum()

# Calculate the percentage of outliers
percentage_outliers_iqr = (outliers_count_iqr / len(energy_data)) * 100

# Summary statistics before outlier treatment
before_treatment_stats = energy_data['Energy_Production'].describe()

# Apply outlier treatment (capping at 1st and 99th percentiles)
lower_cap = energy_data['Energy_Production'].quantile(0.01)
upper_cap = energy_data['Energy_Production'].quantile(0.99)
energy_data_capped_1_99 = energy_data['Energy_Production'].clip(lower=lower_cap, upper=upper_cap)

# Summary statistics after outlier treatment
after_treatment_stats_1_99 = energy_data_capped_1_99.describe()

# Display results
print("Outliers Count (IQR Method):", outliers_count_iqr)
print("Percentage of Outliers (IQR Method):", percentage_outliers_iqr)
print("nSummary Statistics Before Outlier Treatment:n", before_treatment_stats)
print("nSummary Statistics After Outlier Treatment (1st and 99th Percentiles):n", after_treatment_stats_1_99)
Outliers Count (IQR Method): 237
Percentage of Outliers (IQR Method): 2.789219724608685

Summary Statistics Before Outlier Treatment:
count    8497.000000
mean      209.829015
std       174.471194
min        -7.549833
25%        53.744142
50%       258.516008
75%       306.697167
max      1098.962075
Name: Energy_Production, dtype: float64

Summary Statistics After Outlier Treatment (1st and 99th Percentiles):
 count    8497.000000
mean      209.163717
std       171.357249
min        18.879831
25%        53.744142
50%       258.516008
75%       306.697167
max       880.731165
Name: Energy_Production, dtype: float64

由于这里应用的异常值处理方法,称为截顶温莎化,并没有消除任何数据点,而是改变了最极端观察值的值,使其落在某个范围内。

处理前的摘要统计

  • 平均值:209.83

  • 标准差:174.47

  • 最小值:-7.55(这是极端低异常值的指标)

  • 最大值:1098.96(这是极端高异常值的指标)

处理后的摘要统计(第 1 和第 99 百分位数)

  • 平均值:略微调整为 209.16

  • 标准差:降低到 171.36

  • 最小值:增加到 18.88

  • 最大值:降低到 880.73

我们可以看到,在 1 和 99 百分位数处截顶减少了范围和标准差

这种技术提供了稳定性和现实性之间非常好的平衡;它允许保留一个对极端值不太敏感的数据集,同时仍然具有代表值,以便进行进一步评估或建模。

2. 视觉评估

时间序列数据需要通过考虑时间结构的可视化来清理或处理异常值。

例如,仅凭直方图无法描述时间趋势。但有时其他绘图格式可能更合适:

  1. 线图:在折线图上绘制处理前后的值。这显示了处理如何影响时间序列趋势。从这个显示中,一个人能够判断它是否平滑了异常值,而没有扭曲序列的整体时间依赖结构。

  2. 滚动窗口或移动平均图: 在处理异常值前后在线图上绘制移动平均图,以显示处理对短期趋势模式的影响。这有时可以显示处理是否保留了或破坏了原始模式。

  3. 放大区域: 在某一时刻,异常情况更容易理解。通过放大时间序列的部分,你可以了解异常值处理如何改变这些部分的行为。

这里是一个处理前后线图对比的例子:

import matplotlib.pyplot as plt

# Plotting energy production before and after outlier treatment (1st and 99th percentiles) on the same plot
plt.figure(figsize=(14, 6))
plt.plot(energy_data['Datetime'], energy_data['Energy_Production'], label='Before Outlier Treatment', color='blue', alpha=0.7)
plt.plot(energy_data['Datetime'], energy_data_capped_1_99, label='After Outlier Treatment (1st &amp; 99th Percentiles)', color='cyan', linestyle='--', alpha=0.7)
plt.title('Energy Production Before and After Outlier Treatment (Capped at 1st and 99th Percentiles)')
plt.xlabel('Time')
plt.ylabel('Energy Production (kW)')
plt.legend()
plt.tight_layout()
plt.savefig('ff.png', format='png', dpi=300)
# Display the plot
plt.show()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5273ac102efa1dd57f4a13fdcbcaa314.png

图 2:使用 1%和 99%分位数截断前后模拟能源生产对比 | 图片由作者提供

如果你要向没有深厚技术知识的管理者或利益相关者展示结果,这种类型的图表会非常有帮助。

3. 分布比较(柯尔莫哥洛夫-斯米尔诺夫检验)

**柯尔莫哥洛夫-斯米尔诺夫检验(KS 检验)**是一种非参数检验,能够比较两个数据集的分布。

它可以用来检查在处理之后,目标变量的分布——或特征——是否发生了显著变化。这对于检查去除异常值是否改变了数据的整体形状是有用的。

from scipy.stats import ks_2samp

# Performing the KS test to compare the original and capped distributions
ks_stat, p_value = ks_2samp(energy_data['Energy_Production'], energy_data_capped_1_99)

# Displaying the KS statistic and p-value
print("Kolmogorov-Smirnov Test Results")
print(f"KS Statistic: {ks_stat}")
print(f"P-Value: {p_value}")
Kolmogorov-Smirnov Test Results
KS Statistic: 0.010003530657879251
P-Value: 0.7888857198183333

要寻找的内容

  • 高 p 值(> 0.05)表明分布相似,因此,处理没有显著改变数据的分布。

  • 低 p 值(< 0.05)意味着从统计学的角度来看,处理以某种有用的方式改变了数据的分布;它处理了异常值,或者可能引入了不希望的变化,这影响了数据的整体结构。当然,人们可能会进行进一步的分析以确保这些变化与数据预处理和建模的目标一致。

在我们的例子中:

  • KS 统计量: 这个统计量**= 0.0100表明原始数据和截断数据之间的分布差异可以忽略不计**。这表明在处理异常值后,累积分布的变化只有微小的偏移

  • P 值: p 值,0.7889,远远高于经验法则的阈值 0.05。因此,我们不能拒绝零假设。这表明,从统计学的角度来看,处理前后能源生产数据在分布上没有显著差异

因此,这些结果将表明,在 1%和 99%分位数处截断数据清除了极端值,而没有损失整体分布的性质。

这是一个理想的结局,因为在不扭曲数据集原始分布本质的情况下减少极端值通常是可取的,目的是让后续的分析或模型能够基于清洗后的数据更好地泛化。

4. 模型性能指标

根本上,关于处理异常值影响的考虑实际上简化为一个问题:

你的模型,无论是否进行了这些更改,在性能上是否有任何差异

  • 你可以考虑均方误差(MSE)或 R²等指标来评估回归模型

  • 准确率、精确率、召回率和 F1 分数是分类模型的标准指标。

下面是如何在处理异常值前后监控回归模型性能的方法。

 import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Step 1: Data Preparation - Adding time-based features
energy_data['hour'] = energy_data['Datetime'].dt.hour
energy_data['day_of_week'] = energy_data['Datetime'].dt.dayofweek

# Additional cyclic features for hour and day of the week
energy_data['hour_sin'] = np.sin(2 * np.pi * energy_data['hour'] / 24)
energy_data['hour_cos'] = np.cos(2 * np.pi * energy_data['hour'] / 24)
energy_data['day_sin'] = np.sin(2 * np.pi * energy_data['day_of_week'] / 7)
energy_data['day_cos'] = np.cos(2 * np.pi * energy_data['day_of_week'] / 7)

# Lagged feature and rolling mean
energy_data['prev_hour_production'] = energy_data['Energy_Production'].shift(1)
energy_data['3hr_moving_avg'] = energy_data['Energy_Production'].rolling(window=3).mean()

# Drop rows with NaN values due to lagging and rolling mean
energy_data.dropna(inplace=True)

# Define features and target for original data
X = energy_data[['hour', 'day_of_week', 'hour_sin', 'hour_cos', 'day_sin', 'day_cos', 'prev_hour_production', '3hr_moving_avg']]
y = energy_data['Energy_Production']

# Train-test split for original data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Step 2: Train and evaluate the regression model on original data
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

# Calculate performance metrics for original data
mse_original = mean_squared_error(y_test, y_pred)
r2_original = r2_score(y_test, y_pred)

# Step 3: Apply outlier treatment (1st and 99th percentiles) on Energy_Production
lower_cap = energy_data['Energy_Production'].quantile(0.01)
upper_cap = energy_data['Energy_Production'].quantile(0.99)
energy_data_capped = energy_data['Energy_Production'].clip(lower=lower_cap, upper=upper_cap)

# Define features and target for capped data
y_capped = energy_data_capped[energy_data.index]  # Align capped data with features

# Train-test split for capped data
X_train_capped, X_test_capped, y_train_capped, y_test_capped = train_test_split(X, y_capped, test_size=0.2, random_state=42)

# Train and evaluate the regression model on capped data
model.fit(X_train_capped, y_train_capped)
y_pred_capped = model.predict(X_test_capped)

# Calculate performance metrics for capped data
mse_capped = mean_squared_error(y_test_capped, y_pred_capped)
r2_capped = r2_score(y_test_capped, y_pred_capped)

# Display results with clearer labels and three decimal places
print("Results for Model Performance Comparison:")
print("nOriginal Data Performance:")
print(f"Mean Squared Error (MSE): {mse_original:.3f}")
print(f"R-squared (R²): {r2_original:.3f}")

print("nOutlier-Treated Data Performance (1st and 99th Percentiles):")
print(f"Mean Squared Error (MSE): {mse_capped:.3f}")
print(f"R-squared (R²): {r2_capped:.3f}")
Results for Model Performance Comparison:

Original Data Performance:
Mean Squared Error (MSE): 4533.002
R-squared (): 0.840

Outlier-Treated Data Performance (1st and 99th Percentiles):
Mean Squared Error (MSE): 4325.025
R-squared (): 0.845

我们通过在这个数据集的两个版本上训练线性回归模型来实现这一点,一个包含原始数据,另一个通过将能源生产值限制在 1%和 99%分位数来处理异常值,以减少极端值。

对于这两个数据集,我们添加了基于时间的特征,例如小时和星期几、周期性转换和滞后值,这些可能捕捉到能源生产随时间变化的模式和周期性。

结果:

  • 原始数据: MSE 为 4533.002,R²值为 0.840,显示出良好的拟合水平,但也提到极端值有一定的影响。

  • 异常值处理后的数据: 另一方面,均方误差(MSE)降至 4325.025,而R²值略有提升至 0.845。这反映了通过限制极端值带来的微小但积极的效益,并应证实模型性能的更好泛化稳定性。

这些MSE 和 R²的微小改进表明,处理异常值有助于模型对极端值变得不那么敏感,至少在泛化方面有所改善。

从这个角度来看,这在极端值存在于给定数据集中并扭曲预测的情况下可能很有帮助,从而使得构建一个更可靠的能源生产预测模型成为可能。

5. 交叉验证

时间序列数据的交叉验证: 这种技术将确保你的模型在它从未见过的数据上具有良好的泛化能力。请注意,这并不是以任何常规数据集的方式进行。我在这篇文章中探讨了这一点和其他相关问题:

掌握时间序列分析的 5 个必知技巧

交叉验证将你的数据集分成几个子集;它在一个子集上训练,在另一个子集上测试

这应该在处理异常值之前和之后都进行,以确保你确信处理确实提高了模型的鲁棒性。

下面是一个交叉验证的代码片段:

import numpy as np
import pandas as pd
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.linear_model import LinearRegression
from sklearn.metrics import make_scorer, mean_squared_error

# Preparing the feature matrix and target variable for the original data
X = energy_data[['hour', 'day_of_week', 'hour_sin', 'hour_cos', 'day_sin', 'day_cos', 'prev_hour_production', '3hr_moving_avg']]
y = energy_data['Energy_Production']

# Define the cross-validation strategy for time series
tscv = TimeSeriesSplit(n_splits=5)

# Define the MSE scoring metric
mse_scorer = make_scorer(mean_squared_error, greater_is_better=False)

# Perform cross-validation before outlier treatment and log individual scores
cv_scores_before = cross_val_score(LinearRegression(), X, y, cv=tscv, scoring=mse_scorer)
print("Before Outlier Treatment - Cross-Validation MSE:", -cv_scores_before.mean())

# Store individual scores for analysis
cv_results_df = pd.DataFrame({
    'Fold': range(1, len(cv_scores_before) + 1),
    'MSE_Before_Outlier_Treatment': -cv_scores_before
})

# Apply outlier treatment (capping at 1st and 99th percentiles)
lower_cap = y.quantile(0.01)
upper_cap = y.quantile(0.99)
y_capped = y.clip(lower=lower_cap, upper=upper_cap)

# Perform cross-validation after outlier treatment and log individual scores
cv_scores_after = cross_val_score(LinearRegression(), X, y_capped, cv=tscv, scoring=mse_scorer)
print("After Outlier Treatment - Cross-Validation MSE:", -cv_scores_after.mean())

# Add individual scores to the DataFrame
cv_results_df['MSE_After_Outlier_Treatment'] = -cv_scores_after

# Display or save the results
print("nCross-Validation Results by Fold:")
print(cv_results_df)
Before Outlier Treatment - Cross-Validation MSE: 5870.508803176994
After Outlier Treatment - Cross-Validation MSE: 5400.8711159136

Cross-Validation Results by Fold:
   Fold  MSE_Before_Outlier_Treatment  MSE_After_Outlier_Treatment
0     1                   6221.065872                  5772.671486
1     2                   6044.473486                  5375.268558
2     3                   5914.049891                  5581.564532
3     4                   5837.194581                  5218.241578
4     5                   5335.760186                  5056.609425

如果通过交叉验证显示,在处理异常值后,模型的性能变得良好,那么你就走上了正确的道路。

如果情况相反,您可能需要重新思考您处理这些异常值的方式。

在我们的例子中:

首先,我们使用均方误差-MSE 作为评估指标,对原始数据进行交叉验证。每个折叠的性能都被记录下来,以便进行适当的分析。

为了减少极端值的影响,我们在“能源生产”上进行了 1%和 99%的百分位数加帽,以创建加帽目标变量“y_capped”。

在此加帽数据上进行了交叉验证,这使得我们能够看到在异常值处理之后每个折叠中模型性能的变化*。*

在每个折叠中,记录了异常值处理前后的 MSE 分数,以便我们能够进行逐折叠的性能比较。

这些揭示了平均交叉验证 MSE 从异常值处理前的 5870.51 下降到处理后的 5400.87。放大到每个折叠,我们发现 MSE 是一致的较低,因此反映了模型更加稳定。

好吧,在这个例子中,异常值处理是有帮助的,因为它有助于绑定最极端的值,从而使模型从一次分割到下一次分割的性能更加一致。这增加了模型的鲁棒性以及其在时间序列预测中的泛化能力。

6. 残差分析

残差是观察值与预测值之间的差异

它们将帮助您评估在处理异常值后,您的模型拟合度如何。如果某些异常值对您的模型有不良影响,您可能会看到该特定数据点的残差更大。

在理想情况下,处理后应该更小且具有相同的方差

您可以绘制残差图来比较处理前后的分布:

import matplotlib.pyplot as plt

residuals_after = y_test_capped - y_pred_capped

# Plot residuals before and after outlier treatment
plt.figure(figsize=(14, 6))

# Plot residuals before outlier treatment
plt.subplot(1, 2, 1)
plt.scatter(y_pred, residuals_before, color='blue', alpha=0.5)
plt.axhline(y=0, color='black', linestyle='--')
plt.xlabel('Predicted Values')
plt.ylabel('Residuals')
plt.ylim(-400, 400)
plt.title('Residuals Before Outlier Treatment')

# Plot residuals after outlier treatment
plt.subplot(1, 2, 2)
plt.scatter(y_pred_capped, residuals_after, color='cyan', alpha=0.5)
plt.axhline(y=0, color='black', linestyle='--')
plt.xlabel('Predicted Values')
plt.ylabel('Residuals')
plt.ylim(-400, 400)
plt.title('Residuals After Outlier Treatment')

plt.tight_layout()
plt.show()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5b5dd9af127f8ad3517e8f220455b186.png

图 3:能源生产预测的残差分析:在 1%和 99%百分位数加帽前后异常值处理 | 作者图片。

要寻找的内容:

  • 处理后,残差应更接近零,并且更正态分布。

  • 残差分布不应过于极端,这意味着拟合更好。

在我们的例子中:

在右侧的图表中,残差总体较小,并且围绕零分布得更对称,至少对于预测值的主要簇来说是这样。它减少了极端残差,这表明在加帽异常值后,模型以更一致的方式拟合数据。分散度较低,这将表明极端值的影响有所减少。

结论: 异常值处理减少了大的残差,提高了模型拟合度;因此,预测模式的稳定性现在更加稳定和均衡。

这表明,截尾极端值导致模型更加稳健,因为这样可以减少异常值的影响。

7. 敏感性分析

敏感性分析意味着改变你模型的各个因素并观察其响应性。

对于异常值,你会在处理方式上做一些小的调整,例如改变阈值或改变方法,然后看看哪种处理策略能给出最稳定的性能。

这里有一个使用不同的上、下分位数进行截尾的敏感性分析的例子:

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# Define the quantiles to test
quantile_values = [(0.01, 0.99), (0.05, 0.95), (0.10, 0.90)]

# Prepare a list to store the results
results_list = []

# Define features and target variable
X = energy_data[['hour', 'day_of_week', 'hour_sin', 'hour_cos', 'day_sin', 'day_cos', 'prev_hour_production', '3hr_moving_avg']]
y = energy_data['Energy_Production']

# Loop through each quantile pair for sensitivity analysis
for lower_q, upper_q in quantile_values:
    # Apply quantile capping to the target variable
    lower_cap = y.quantile(lower_q)
    upper_cap = y.quantile(upper_q)
    y_capped = y.clip(lower=lower_cap, upper=upper_cap)

    # Train-test split with capped data
    X_train, X_test, y_train_capped, y_test_capped = train_test_split(X, y_capped, test_size=0.2, random_state=42)

    # Train the model and calculate predictions
    model = LinearRegression()
    model.fit(X_train, y_train_capped)
    y_pred_capped = model.predict(X_test)

    # Calculate performance metrics
    mse = mean_squared_error(y_test_capped, y_pred_capped)
    r2 = r2_score(y_test_capped, y_pred_capped)

    # Append results to the list
    results_list.append({
        'Lower Quantile': lower_q,
        'Upper Quantile': upper_q,
        'MSE': mse,
        'R²': r2
    })

# Convert the list of results to a DataFrame
sensitivity_results = pd.DataFrame(results_list)

# Display the results of the sensitivity analysis
print("Sensitivity Analysis Results:")
print(sensitivity_results)
Sensitivity Analysis Results:
   Lower Quantile  Upper Quantile          MSE        R²
0            0.01            0.99  4325.025305  0.844548
1            0.05            0.95  1824.866878  0.898331
2            0.10            0.90  1760.571111  0.886728

需要关注的内容:

  • 模型结果应该在不同的异常值处理中保持一致。

  • 如果在分位数变化很小的情况下性能有大幅变化,那么就太敏感了。

在我们的例子中:5%和 95%分位数处截尾实现了最佳的 R²性能,这在减少极端异常值和维护底层结构之间取得了很好的平衡。

该设置产生较低的均方误差和最高的 R²,反映出它可以在该数据集中取得更好的平衡。在 10%和 90%分位数处采取更激进的截尾阈值略微降低了均方误差,但也降低了 R²,反映出收益递减。

总体而言,在 5%和 95%分位数处截尾提供了最稳定或最可靠的性能敏感性。这将减少极端值的影响,但保留数据中足够多的自然变化。

8. 特征重要性分析

当与基于树的模型一起工作时,异常值会不成比例地影响特征重要性。

为了处理这个问题,你可以比较处理异常值前后特征的重要性,以便验证最重要的特征是否保持稳定。

这就是你可以这样做的方法(这个例子没有使用我们的数据集):

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Split the data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X_mock, y_mock, test_size=0.2, random_state=42)

# Train Random Forest model on original data
rf_model = RandomForestRegressor(random_state=42)
rf_model.fit(X_train, y_train)

# Calculate feature importance before outlier treatment
feature_importance_before = pd.Series(rf_model.feature_importances_, index=X_mock.columns)

# Apply outlier treatment (capping at 5th and 95th percentiles)
lower_cap = y_mock.quantile(0.05)
upper_cap = y_mock.quantile(0.95)
y_capped = y_mock.clip(lower=lower_cap, upper=upper_cap)

# Train-test split on capped target
X_train_capped, X_test_capped, y_train_capped, y_test_capped = train_test_split(X_mock, y_capped, test_size=0.2, random_state=42)

# Train Random Forest model on capped data
rf_model.fit(X_train_capped, y_train_capped)

# Calculate feature importance after outlier treatment
feature_importance_after = pd.Series(rf_model.feature_importances_, index=X_mock.columns)

# Combine and display feature importance for comparison
feature_importance_comparison = pd.DataFrame({
    'Feature Importance Before Outlier Treatment': feature_importance_before,
    'Feature Importance After Outlier Treatment': feature_importance_after,
    'Absolute Change': (feature_importance_before - feature_importance_after).abs()
})

print("Feature Importance Comparison (Random Forest):")
print(feature_importance_comparison.sort_values(by='Absolute Change', ascending=False))

需要关注的内容:

  • 特征重要性的大幅变化可能表明某些特征受到了异常值的过度影响。

  • 理想情况下,特征重要性在处理前后应该保持相似,这意味着异常值没有驱动你的模型。

总结

在这篇文章中,我们探讨了评估时间序列数据中异常值处理影响的一些最流行的方法

我们现在知道,进行这种评估很重要,这样我们才能了解它对模型性能和数据分布的影响。

基本统计比较开始,我们了解了均值和标准差等一些基本估计如何容易地给出由于异常值截尾而导致的分布变化的初步印象。

通过使用柯尔莫哥洛夫-斯米尔诺夫检验进行分布的视觉检查和比较有助于量化由于异常值处理导致的显著变化。

我们看到了如何比较关键指标,如处理前后的均方误差(MSE)和 R²,以及时间序列数据的交叉验证,帮助我们做出关于这种处理的最佳判断。

其他主要步骤包括残差分析,其中对异常值的处理减少了残差,从而更好地拟合模型;敏感性分析以查看在不同处理阈值下结果的稳定性;最后但同样重要的是,特征重要性分析以检查核心预测因子在异常值调整后是否保持稳定。


如果你觉得这篇文章有价值,我会很感激你通过点赞来支持我。你也可以欢迎关注我 在 Medium 上 (Medium),阅读类似的文章!

在这里预约与我通话,向我提问或发送你的简历:

Sara 的数据科学免费资源

参考文献

[2002.04236] 时间序列数据中异常/离群值检测综述

异常检测和处理:全面指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值