异常值处理是数据分析中必不可少的一步,但许多人发现它很有挑战性。
借助本文中的见解,我希望能够简化这一过程,让它变得更加有趣。
本文是专门讨论时间序列数据中异常值的识别和管理的四部分系列文章的第三部分。
本系列的第一篇文章是关于探索视觉和统计方法,以有效识别时间序列数据中的异常值:
在第二篇文章中,我们探讨了几种识别异常值的机器学习技术:
今天讲述的第三篇文章中,我们将探讨如何 管理 这些异常值的各种策略, 并考虑到时间序列数据的特殊因素。我们将探讨移除、保留和封顶技术,提供一些处理异常值的实用方法。
在下一篇也是最后一篇文章中,我将继续探索管理异常值的方法,重点关注归因和转换方法,以及评估异常值处理的影响。
欢迎来到雲闪世界。感兴趣的小伙伴需要完整代码及相关资料可联系博主
正确处理异常值的重要性
无论在哪个领域,正确处理异常值对于保证我们的研究准确性和可靠性都至关重要。
为什么?原因有几个。例如,异常值会扭曲集中趋势和离散度的测量值,从而严重扭曲您的统计分析。
![](https://img-blog.csdnimg.cn/img_convert/e7aaba73a98ecdc09af04f830e9d3b11.png)
研究发现,即使是一个极值也会极大地影响平均值、标准差和相关性。
在机器学习中,异常值会导致模型出现偏差和泛化能力差,尤其是在对极值敏感的方法中。
当数据科学家、研究人员或数据分析师没有正确处理异常值时,可能会得出误导性的结论并产生现实世界的后果。
背景是异常值处理的关键。
领域知识对于区分错误的数据点和可能提供重要见解的真正不寻常的观察结果至关重要。
一个领域中的异常值可能是另一个领域中的关键数据点!
正确处理异常值意味着选择最佳方法,要么删除它们,要么转换它们,要么使用强大的统计技术。
记录您如何识别和处理异常值以保持事情清晰和可重复也非常重要。
评估异常值的性质
了解异常值的原因和意义
异常值的原因和意义可能有很大差异。
异常值可以分为全局异常值(偏离整个数据集)和局部异常值(偏离附近点)。
![](https://img-blog.csdnimg.cn/img_convert/c03da388075746c3f69238180b19b35d.png)
数据包含异常值的原因有很多:可能是由于数据的自然变化、测量误差或数据处理错误。
异常值的重要性取决于领域。举个例子:在金融领域,异常值可能表示存在欺诈交易,而在医疗保健领域,异常值可能表示罕见但危急的医疗状况。
确定异常值是合法的还是错误的
确定异常值是否合法或错误是数据分析中的关键步骤。
为了识别异常值,可以同时使用统计和机器学习方法,以及领域知识甚至数据质量评估。
这意味着检查数据收集和处理方法以确定潜在的错误来源。这可能涉及检查仪器故障、抄写错误或数据损坏。
统计上看似异常的数据点可能是对罕见事件的真实观察!
时间序列数据的特殊注意事项
保留时间结构
时间序列数据由于其固有的时间结构和潜在的季节性,在异常值检测方面面临着独特的挑战。
因此,在发现和处理异常时保持时间模式完整非常重要。
处理时间序列数据中的异常值时,保留时间序列数据的时间结构至关重要。
异常值还会扭曲自相关 模式和趋势,这可能会导致您误解数据的真实结构。
处理季节性异常值
处理季节性异常值可能会有点棘手。
区分合法的季节性波动和真正的异常是一个巨大的挑战。
![](https://img-blog.csdnimg.cn/img_convert/038d09f95f2247b964b6de93b44287d3.png)
季节性分解技术 可以帮助将季节性成分与趋势和残差分离,从而有助于异常值检测
时间序列数据集可以分为三个主要部分:
- 趋势:数据的长期发展趋势。
- 季节性:数据内重复的短期循环。
- 残差:去除趋势和季节性成分后的剩余数据部分,其中包括噪音和潜在的异常值。
残差分量突显了数据中的不规则模式。通过消除可预测的趋势和季节性变化,剩下的就是这些分量无法解释的偏差,从而使异常值更加明显。
在时间序列数据中查找异常值意味着要在保持数据的时间和季节模式完整与发现真正的异常之间找到一条平衡。
在本系列的第一篇和第二篇文章中,我们将更多地了解如何发现时间序列数据中的异常值。
处理异常值的基本策略
在处理异常值时,研究人员通常面临两种基本策略:保留或删除。
保留异常值
当这些数据点代表真实的(尽管不寻常的)观察结果时,通常最好保留异常值,因为研究人员表示异常值可以为正在研究的现象提供有价值的见解。
当保留而不是删除异常值时,您可以应用稳健的统计技术来最大限度地减少它们对分析的影响。
然而,你需要谨慎,因为这些保留的异常值仍然会影响统计测量和模型估计。因此,在你的研究结果中报告保留异常值的存在和处理方式非常重要。
如果您转换异常值,例如通过对数转换,您是否仍保留它们?
根据我的研究,我得出结论,这个问题没有直接的答案。
如果您将数据点保留在数据集中但应用转换来减少它们的影响,这通常被视为一种保留形式,特别是当转换是可逆的或保留数据点的相对位置时。
然而,应该清楚地报告所使用的具体方法,因为一些转换(如winsorization,更多内容见下文)属于保留和删除之间的灰色区域。
删除异常值
在某些情况下,删除异常值可能是合适的。
该领域的现有文献表明,在以下情况下移除是合理的:
- 测量误差:由于测量仪器故障或读数不准确而导致的异常值。
- 抽样误差:由于非代表性的抽样过程而产生的异常值,导致不能反映真实总体分布的极端值。
- 实验误差:由于程序错误、污染或意外情况导致科学实验中的异常值,使数据点变得无关紧要。
- 人为错误:由于数据输入错误而导致的异常值,记录的值远远超出预期范围。
- 数据处理错误:数据预处理或转换步骤中引入的异常值由于计算错误或算法问题而明显不正确。
常见的移除技术包括修剪(移除极值)和封顶/缩尾(用不太极端的值替换极值)。这些方法将在下一节中详细介绍。
如果不仔细考虑就删除异常值可能会导致失去宝贵的见解,甚至可能扭曲结果。
当异常值被删除时,记录哪些数据点被排除以及为什么它们被删除是至关重要的。
异常值处理的封盖(Winsorization)
封顶,也称为 winsorization,是一种通过限制数据集中的极值来处理异常值的技术。
该方法旨在减少异常值的影响,同时保留其在数据中的存在。这意味着我们将其值降低到一定限度。
为了使此方法有效,我们需要定义上限和下限。
设置上限和下限
这些界限定义了数据值被视为可接受的范围。
有多种方法可以设置这些边界,包括基于标准差或四分位距的方法。边界的选择将影响数据分布。
该领域的专家警告称,随意设定边界可能会扭曲数据的结构和关系。此外,固定边界可能并不适合所有数据集,尤其是那些自然分布倾斜的数据集。
基于百分位数的上限
基于百分位数的上限为缩尾算法提供了一种更加数据驱动的方法。
# Create a DataFrame with the time-series data
df = pd.DataFrame({'time': time, 'value': data})
# Apply percentile-based capping (Winsorization)
lower_percentile = 5
upper_percentile = 95
lower_bound = np.percentile(df['value'], lower_percentile)
upper_bound = np.percentile(df['value'], upper_percentile)
df['value_capped'] = np.clip(df['value'], lower_bound, upper_bound)
# Plot the original and capped time-series data
plt.figure(figsize=(14, 7))
plt.plot(df['time'], df['value'], label='Original Data with Outliers', color='blue', linestyle='--')
plt.plot(df['time'], df['value_capped'], label='Capped Data', color='green')
plt.title('Percentile-based Capping (Winsorization) on Time-Series Data')
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend()
plt.show()
![](https://img-blog.csdnimg.cn/img_convert/f861c55ce95b970a7f7725f356b11e84.png)
此方法使用数据分布的特定百分位数作为上限点。常见的选择包括第 5/95 百分位数或第 1/99 百分位数,具体取决于所需的保守程度。
研究人员表示,基于百分位数的方法通常比固定值上限(如下所述)更为稳健,因为它们适应数据的自然分布。
替代封盖方法
随着数据集变得越来越复杂,已经出现了替代性的上限方法来解决一些异常情况。
这些方法提供了设置界限的不同方法,每种方法都有各自的优点和局限性。
固定价值上限
固定值上限涉及设置上限和下限的特定数字阈值。
它非常简单且易于理解,尤其是当您借助领域知识了解其局限性时。
但请记住,这种方法不是很灵活,并且对于具有不断发展的趋势 的时间序列数据集来说它可能是一个缺点。
# Create a DataFrame with the time-series data
df = pd.DataFrame({'time': time, 'value': data})
# Define dynamic capping thresholds based on rolling statistics
window_size = 15 # Rolling window size for calculating dynamic bounds
df['rolling_mean'] = df['value'].rolling(window=window_size, min_periods=1).mean()
df['rolling_std'] = df['value'].rolling(window=window_size, min_periods=1).std()
# Define dynamic upper and lower bounds
df['lower_bound'] = df['rolling_mean'] - 2 * df['rolling_std']
df['upper_bound'] = df['rolling_mean'] + 2 * df['rolling_std']
# Apply dynamic capping
df['value_capped'] = np.clip(df['value'], df['lower_bound'], df['upper_bound'])
# Plot the original and capped time-series data with dynamic bounds
plt.figure(figsize=(14, 7))
plt.plot(df['time'], df['value'], label='Original Data with Outliers', color='blue', linestyle='--')
plt.plot(df['time'], df['value_capped'], label='Capped Data with Dynamic Bounds', color='green')
plt.fill_between(df['time'], df['lower_bound'], df['upper_bound'], color='gray', alpha=0.2, label='Dynamic Bounds (±2σ)')
plt.title('Dynamic Capping on Time-Series Data with Multiple Outliers')
plt.xlabel('Time')
plt.legend()
plt.show()
图 5:时间序列数据的固定值上限|图片由作者提供。
您可以根据领域知识、四分位数和百分位数等统计指标以及数据的典型范围来选择固定值上限的下限和上限。
动态封顶
此方法对于时间序列数据特别有用,因为它允许调整界限以适应不断变化的趋势!
研究人员强调,动态封顶需要定期更新和仔细监控才能保持其有效性。
# Create a DataFrame with the time-series data
df = pd.DataFrame({'time': time, 'value': data})
# Define dynamic capping thresholds based on rolling statistics
window_size = 15 # Rolling window size for calculating dynamic bounds
df['rolling_mean'] = df['value'].rolling(window=window_size, min_periods=1).mean()
df['rolling_std'] = df['value'].rolling(window=window_size, min_periods=1).std()
# Define dynamic upper and lower bounds
df['lower_bound'] = df['rolling_mean'] - 2 * df['rolling_std']
df['upper_bound'] = df['rolling_mean'] + 2 * df['rolling_std']
# Apply dynamic capping
df['value_capped'] = np.clip(df['value'], df['lower_bound'], df['upper_bound'])
# Plot the original and capped time-series data with dynamic bounds
plt.figure(figsize=(14, 7))
plt.plot(df['time'], df['value'], label='Original Data with Outliers', color='blue', linestyle='--')
plt.plot(df['time'], df['value_capped'], label='Capped Data with Dynamic Bounds', color='green')
plt.fill_between(df['time'], df['lower_bound'], df['upper_bound'], color='gray', alpha=0.2, label='Dynamic Bounds (±2σ)')
plt.title('Dynamic Capping on Time-Series Data with Multiple Outliers')
plt.xlabel('Time')
plt.legend()
plt.show()
![](https://img-blog.csdnimg.cn/img_convert/ad5a1c0ca76ed4cc50fd3c14abe32a7e.png)
动态上限根据滚动统计数据(例如移动平均线和标准差)调整上限和下限,使其能够适应随时间变化的数据模式,同时减轻异常值的影响。
动态上限阈值如何定义?
- window_size:应足够大以捕捉数据中的潜在模式或周期,但又足够小以快速检测变化。常见的选择范围从几个时期到一年或更长时间,具体取决于数据的频率。
- min_periods:确定计算有效滚动统计数据所需的最小数据点数,并确保初始周期在分析中得到体现。通常,它会被设置为一个既能提供有意义的见解又能避免对早期数据过度敏感的值。
这些参数通常是通过实验并考虑数据固有模式和噪声水平来选择的。
定期更新界限可确保其保持相关性和有效性,以维护数据完整性。
自适应封顶
适应时间序列数据的变化特征至关重要。
这种方法可以确保在添加新的数据点和模式演变时,上限仍然有效。
自适应上限采用机器学习算法来确定最佳上限点,因为这些方法可以适应复杂的多维数据结构。
具体方法包括孤立森林算法和局部异常值因子,可以识别高维空间中的异常值。
# Create a DataFrame with the time-series data
df = pd.DataFrame({'time': time, 'value': data_with_anomalies})
# Apply Isolation Forest for outlier detection
clf = IsolationForest(contamination=0.1, random_state=0) # Contamination parameter can be adjusted
df['is_outlier'] = clf.fit_predict(df[['value']])
# Define dynamic capping bounds based on Isolation Forest predictions
window_size = 10
df['rolling_mean'] = df['value'].rolling(window=window_size, min_periods=1).mean()
df['rolling_std'] = df['value'].rolling(window=window_size, min_periods=1).std()
# Define dynamic upper and lower bounds
df['lower_bound'] = df['rolling_mean'] - 2 * df['rolling_std']
df['upper_bound'] = df['rolling_mean'] + 2 * df['rolling_std']
# Apply dynamic capping using np.clip()
df['value_capped'] = np.clip(df['value'], df['lower_bound'], df['upper_bound'])
# Plot the original and capped time-series data with outliers identified by Isolation Forest
plt.figure(figsize=(14, 7))
plt.plot(df['time'], df['value'], label='Original Data with Anomalies', color='blue', linestyle='-')
plt.scatter(df['time'][df['is_outlier'] == -1], df['value'][df['is_outlier'] == -1], color='red', label='Detected Outliers')
plt.plot(df['time'], df['value_capped'], label='Capped Data (Adaptive Isolation Forest)', color='green')
plt.title('Adaptive Capping using Isolation Forest on Time-Series Data with Anomalies')
plt.xlabel('Time')
plt.ylabel('')
plt.legend()
plt.show()
![](https://img-blog.csdnimg.cn/img_convert/980e9c5b4bcd1821bfbce016e3490eda.png)
在上面的代码片段中:
- 孤立森林首先通过为每个数据点分配异常分数来识别异常值。分数低于某个阈值的点(通常设置为检测特定的污染水平,例如 10%)。
- 然后,我们将其定义为 10,它决定了用于计算时间序列数据动态统计数据(平均值和标准差
window_size
)的滚动窗口的大小。 - 利用这些滚动统计数据,我们定义动态上限和下限(
lower_bound
和upper_bound
)。这些界限是根据滚动平均值和标准差设定的。
图 7 中的图表直观地显示了包含异常值的原始数据和上限数据,展示了孤立森林如何处理时间序列分析中的异常值。
这些方法确实很有效,但它们会使数据预处理变得更加复杂。
关于如何调整这些参数的观察:
如果时间序列数据显示频繁的短期异常,请考虑降低该值window_size
以增加对这些异常的敏感度。相反,如果数据具有稳定的长期趋势,偶尔出现较大的峰值,则较大的值window_size
可能有助于平滑这些峰值,同时仍能捕捉到重大变化。
关于边界调整,在定义lower_bound
和时,尝试使用不同的因子(例如 1.5、2、2.5)乘以滚动标准差upper_bound
。这可让您调整边界以更好地处理数据中看到的起伏。
处理异常值的注意事项和最佳实践
无论选择哪种方法,都必须:
- 验证上限对数据集的影响
- 比较封顶前后的描述性统计数据,以了解集中趋势和离散度的度量如何变化(有关评估指标的更多信息,请参阅即将发表的文章)。
- 使用直方图或箱线图可视化封盖前后的数据分布,以识别数据形状的任何显著改变。
- 通过改变上限阈值并观察这如何影响主要分析结果来进行敏感性分析。
- 考虑对变量之间相关性的影响,因为上限可以改变多元数据集中的关系。
2.确保所选方法符合数据的自然分布和研究目标。
评估您的研究问题是否对极端值敏感。在某些情况下,这些极端值可能是研究的重点,不应受到限制!
请记住,对于时间序列数据,上限可能会掩盖重要的时间模式或事件。
3. 透明地记录封盖过程,包括所选方法和界限背后的理由。
- 确保清楚地提及您使用的上限方法,包括具体的阈值或百分位数。
- 解释为什么您选择该方法,并将其与您的数据特征以及您希望通过研究实现的目标联系起来。
- 此外,请务必报告有多少数据点受到了上限的影响,包括数字和百分比。
在本文中,我们探讨了适当的异常值处理的重要性,讨论了时间序列数据的特殊考虑,并介绍了保留和删除异常值等基本策略。
此外,我们还探讨了上限和下限设定、基于百分位数的上限设定和其他方法。最后,我们介绍了一些维护数据完整性和优化分析结果的最佳实践。
虽然保留或删除异常值都是基本策略,但有一种中间立场通常可以兼具两全其美的效果:转换异常值。
这种方法涉及对数据应用数学变换以减少异常值的影响,而不是完全丢弃它们。
请继续关注即将发布的下一篇文章,我将在其中探讨几种处理异常值的转换技术,并详细介绍异常值处理的关键步骤:评估异常值处理的影响,包括进行敏感性分析。