查找时间序列数据中异常值的终极指南(第 1 部分)

时间序列分析中异常值检测的有效统计方法和工具

      异常值:这些令人困扰的数据点可能会扭曲统计模型、扭曲预测并破坏决策过程。

       雲闪世界专门介绍时间序列数据中异常值的识别和管理的四部分系列文章的开篇,我们将探索视觉和统计方法来有效识别时间序列数据中的异常值。对于任何想要提高分析准确性的人来说,这些基础知识都至关重要。

在第二篇文章中,我专门介绍了机器学习方法,鉴于其重要性和复杂性,它们值得专门讨论:查找时间序列数据中异常值的终极指南(第2部分)用于学习时间序列中异常检测的有效机器学习方法和工具

第三篇文章探讨了如何 管理 这些异常值的各种策略,包括移除、保留和封顶技术,提供了一些处理异常值的实用方法。

第四篇也是最后一篇文章中,我将继续探讨管理异常值的方法,重点关注归因和转换方法,以及评估异常值处理的影响。

您应该关心时间序列数据中的异常值的一些原因:

  1. 异常值会严重扭曲和误传数据集的关键统计数据,例如平均值、方差和相关系数。
  2. 异常值会影响预测模型的性能。
  3. 异常值可以掩盖时间序列数据中的真实趋势和周期行为。
  4. 根据没有经过严格审查的异常数据做出的决策可能会导致糟糕的战略决策

还有许多其他原因可以说明为什么处理时间序列数据中的异常值对于有效分析至关重要,但这些理由足以激励我们开始探索。

离群值与异常

我将交替使用“异常”和“离群值”这两个术语,但它们的定义有细微的差别。异常可以指任何偏离常态的数据点,而离群值则具体表示远离大多数数据点的极值。许多方法都可以应用于异常和离群值。

如何为时间序列数据选择正确的异常值检测方法?

选择时间序列数据的最佳异常检测方法首先要深入了解数据集和预期异常。

话虽如此,首先要考虑数据集的大小和可用的计算资源

对于可解释性至关重要的数据集,Z 分数和移动平均值等简单方法可能是理想的选择。但是,更复杂的场景(例如需要检测细微模式的场景)可能会受益于 LSTM 网络等高级技术(将在本系列的第二部分中介绍),这些技术需要大量数据和计算能力。

请记住:数据集大小、计算资源、可解释性和任务性质是选择适当的异常值检测方法的关键。

尝试各种方法和指标以准确评估其性能可能会大有裨益。如果可能,请考虑使用多种方法来提高准确性。此外,使用您或领域专家对该领域的了解可以指导您选择方法。

没有一刀切的解决方案;最好的方法取决于您的数据的具体特征、您想要检测的异常的性质以及您的特定要求。

评估异常检测方法在欺诈检测等领域尤其具有挑战性,因为异常并不常见但很重要。

准确率、召回率和 F1 分数等指标对于评估这些方法在捕获欺诈活动和减少误报方面的有效性至关重要。

在预测性维护等领域,ROC 曲线和 AUC 指标对于及时识别潜在的机器故障非常有价值。

在医疗保健等行业中,可视化经常用于监测患者的生命体征,但此类方法的准确性在很大程度上取决于领域专业知识的正确解释。

单变量与多变量数据

在开始异常值分析之前,重要的是考虑您的数据是单变量还是多变量。

单变量时间序列数据由随时间记录的单个观察序列组成。典型示例包括每日股票价格、每月销售数据或年度天气数据。

单变量数据的示例
单变量数据的示例

相比之下,多元时间序列数据涉及在相同时间间隔观察和记录的多个变量或序列。

这种类型的数据捕捉不同变量之间的关系和相互作用以及它们各自的趋势和季节性变化。例如,多变量时间序列可以包括温度、湿度和风速的每日测量值,所有这些都是同时记录的。

多元数据的示例

本文描述的某些方法更适合单变量数据,而其他方法则专门用于处理多变量。

但是,有些方法可以同时适用于这两种数据。在深入研究这些方法之前,我将在此概述针对这两种数据的几种常用方法:

对于单变量数据,通常使用时间序列图和箱线图等视觉检查方法,每次只关注一个变量。STL 分解也传统上用于单变量设置。Z 分数、改进的 Z 分数方法和 Grubbs 检验也用于此类数据。

孤立森林、LOF 和自动编码器等机器学习方法通​​常用于多变量数据的降维和异常检测,但它们也可以压缩和重建单变量时间序列数据,以根据重建误差识别异常。

异常值检测方法的思维导图
异常值检测方法的思维导图
异常值很多,远不止上述思维导图列出的这些

对于多变量数据,散点图分析是考察多变量关系的常用方法,而孤立森林、LOF、自编码器等自然适合处理高维数据。

请注意,一些 单变量方法也可以应用于多变量数据。例如,Z 分数方法也可以用于多变量场景,通过独立计算每个变量的 Z 分数。

箱线图可分别用于多变量数据集中的每个变量,以识别每个维度中的异常值。在多变量场景中,散点图可用于绘制变量对。STL分解虽然传统上是单变量的,但可以通过独立分解每个序列来分析多变量序列。

检测数据中的异常值的最佳方法是什么?

视觉方法

目视检查是识别时间序列数据中异常值的基本方法。数据的性质也会影响目视检查的实施方式。

时间序列图

这是时间序列数据最直接的图表。它允许您查看趋势、模式、季节性变化和随时间变化的潜在异常值。与其他数据有显著偏差的点通常很容易被发现。

import matplotlib.pyplot as plt
import matplotlib.dates as mdates

def plot_temporal_trends(df, columns):
 
    num_plots = len(columns)
    fig, axes = plt.subplots(num_plots, 1, figsize=(10, num_plots * 3), sharex=False)  # sharex=False to not share x-axis
    fig.suptitle(f'Temporal Trends', fontsize=16, y=1.02 + 0.01 * num_plots)
    
    if num_plots == 1:  # Ensure axes is iterable
        axes = [axes]
    
    for ax, col in zip(axes, columns):
        ax.plot(df.index, df[col], marker='o', markersize=4, linestyle='-', label=col)
        ax.set_title(f'{col} - {title}')
        ax.set_ylabel('Value')
        
        # Setting the date formatter for each subplot's x-axis
        ax.xaxis.set_major_locator(mdates.YearLocator(base=2))
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
        
        # Rotate and align the tick labels so they look better
        ax.tick_params(axis='x', rotation=45)
        
        ax.legend()
    
    plt.tight_layout(rect=[0, 0, 1, 0.97])  # Adjust layout to make room for the title
    plt.show()

columns = df.columns.tolist()
plot_temporal_trends(df, columns)

您能发现异常值吗?🤔 这种可视化可能会直接揭示异常值,也可能需要根据数据的复杂性进行更详细的分析。在大多数情况下,可能需要进行额外的可视化来确认异常值的怀疑。

异常值分析中的季节性考虑因素

按季节处理异常值非常有意义,尤其是在处理表现出季节性变化的数据时。许多时间序列数据集在一年中的特定时间显示出不同的模式。

事实上,当按季节处理异常值时,最初看似异常的数据点可能不会被视为异常值。同样,这取决于数据和问题的性质!

以我最近从事的一个项目为例,该项目涉及水坝水质测量的时间序列数据。很快我就发现,需要逐季分析异常值。每个季节都有自己的特点和趋势,以独特的方式影响参数。通过按季节细分数据,我可以更有效地将特定的异常值检测方法应用于每个子集。

季节性会对数据所表现出的趋势和行为产生显著影响,按季节进行分析可以提供更有见地和更相关的结论。

例如,雨季水质的异常可能是由于径流造成的,而在旱季,同样的异常可能是不寻常的

此外,季节性分析可以通过考虑反复出现的季节性影响来改进预测模型。这对于受季节变化影响严重的行业(如农业、旅游业和零售业)至关重要。

箱形图

可用于静态识别数据集或数据子集内的异常值。晶须外的点(通常设置为四分位数间距的 1.5 倍)是潜在异常值。

def plot_outliers(param_dfs):
    for key, df in param_dfs.items():
        plt.figure(figsize=(10, 6))
        df.boxplot()
        plt.title(f'Boxplot of {key}')
        plt.xticks(rotation=45)
        plt.show()

# Call the function to plot the outliers
plot_outliers(param_dfs)

在上图中,我分析了不同水深的特定参数,并将它们并排比较。图上的小黑圈 代表潜在的异常值。这些视觉提示对于快速识别每个子集中与常态有显著偏差的数据点至关重要。

散点图

如果时间序列数据与另一个变量相关,散点图可以帮助识别两个变量背景下的异常值。

# Plotting
plt.figure(figsize=(10, 6))
# Normal data
plt.scatter(df['Annual Income'][:-5], df['Credit Card Spending'][:-5], color='blue')
plt.title('Annual Income vs Credit Card Spending')
plt.xlabel('Annual Income ($)')
plt.ylabel('Credit Card Spending ($)')
plt.legend()
plt.grid(True)
plt.show()
两个相关变量的散点图示例

统计方法

STL 分解

众所周知,时间序列数据可以分为趋势、季节性和残留性。

趋势是长期内观察到的基本方向或模式,季节性是由于季节因素而发生的周期性波动,残差是在去除趋势和季节成分后数据中残留的随机变化。

为此,STL 分解非常有用,可以使用 LOESS(局部估计散点图平滑)将时间序列信号有效地分离为这三个不同的组件,从而增强我们基于对数据底层行为的更清晰洞察进行分析和预测的能力。

注意:STL 假设数据点按时间顺序排列。STL分解需要一系列数据点,其中数据点按其自然时间顺序一个接一个地排列。这对于准确估计趋势和季节性变化至关重要,其中每个点都有助于理解后面的点。

模拟数据集的 STL 分解

STL 如何帮助您识别异常值?

STL 分解的残差成分表示无法用季节和趋势成分解释的数据部分。

理想情况下,残差应该是随机噪声。异常值通常可以检测为残差序列中异常的峰值或下降,这些峰值或下降与噪声水平的典型值有显著偏差。因此,请在残差中寻找极值。由于残差理想情况下代表随机噪声,因此任何显著偏差都可能表明存在异常值。

通过分析残差的标准差,您可以识别与平均残差距离异常的点,这些点通常被归类为异常值。例如,与平均残差相差超过 2 或 3 个标准差的数据点可被视为异常值。

趋势部分可平滑短期波动并突出数据集的更广泛变动。异常值可能会导致趋势发生意外转变或突然变化,不符合大多数数据所建立的平滑模式。

步骤 1:查看残差图

首先查看 STL 分解产生的残差图。此图显示无法通过趋势或季节性成分解释的数据点。

第 2 步:计算统计指标

计算残差的平均值和标准差。这将有助于确定哪些数据点与我们在正态分布的噪声模式中预期的数据有显著差异。

步骤 3:定义异常值的阈值

通常,距离平均值超过 2 或 3 个标准差的数据点被视为异常值。您可以根据对异常值的敏感度来选择阈值。对于许多应用来说,使用 3 个标准差是很常见的。

步骤 4:识别异常值

识别残差分量中超过此阈值的数据点。这些是潜在的异常值。

步骤5:目视检查和交叉验证

您可以重新查看原始时间序列图以及趋势和季节性成分的图。查看已识别的异常值相对于这些成分的位置。

检查异常值是否只是不寻常但真实的变化,或者它们是否可能表示错误或异常。这可能涉及有关数据的上下文知识。

import numpy as np
#Example

# Calculate the mean and standard deviation of the residuals
residuals = result.resid
mean_resid = np.mean(residuals)
std_resid = np.std(residuals)

# Define a threshold for outlier detection
threshold = 3 * std_resid

# Identify potential outliers
outliers = residuals[np.abs(residuals - mean_resid) > threshold]

# Print outlier dates and values
print(outliers)

Z 分数和改进的 Z 分数方法

Z 分数,也称为标准分数,用于衡量数据点与数据集平均值之间的标准差。其计算公式如下:

Z 分数方程 
X是单个数据点。
  • μ是数据集的平均值。
  • σ是数据集的标准差。

Z 分数大于某个阈值(通常为 2 或 3)的数据点被视为异常值。

注意:Z 分数法假设数据服从正态分布或至少近似正态分布。它还假设异常值相对于平均值和标准差具有极值。

import numpy as np 

def  determine_outliers_with_z_score ( df, z_thresh=z_thresh ): 
    print ( "使用 Z 分数方法的异常值:" ) 
    for col in df.columns: 
        if np.issubdtype(df[col].dtype, np.number):   # 仅数值数据
            df_col = df[col].dropna()   # 删除 NaN 值
            z_scores = np.abs ( (df_col - df_col.mean()) / df_col.std(ddof= 0 )) 
            outliers = df_col[z_scores > z_thresh] 
            print ( f " {col} - Outliers: { len (outliers)} " ) 
            print (outliers, "\n" ) 

z_thresh= 3
 determine_outliers_with_z_score(df)

请记住,选择 Z 分数阈值来识别数据集中的异常值是一项重要决定,它会对数据分析结果产生重大影响。

Z 分数阈值的选择(通常为 1、2 或 3)取决于检测异常值所需的敏感度级别和数据的性质。

请记住,z 分数为 1 覆盖正态分布下的曲线约68% ,z 分数为 2 覆盖约95%,z 分数为 3 覆盖约99.7%:

可视化正态分布中的标准差

如果您想知道使用不同类型的阈值时数据有多少个异常值,您也可以绘制它们并进行比较:

def count_outliers_by_z_threshold(series, z_thresholds=[1, 2, 3]):
    """Return counts of outliers for given Z-score thresholds."""
    mean = series.mean()
    std = series.std()
    z_scores = np.abs((series - mean) / std)
    
    # Count outliers for each Z-score threshold
    counts = {}
    for z_thresh in z_thresholds:
        counts[z_thresh] = (z_scores > z_thresh).sum()
    return counts

# Assuming 'param_dfs' is your dict of DataFrames
z_thresholds = [1, 2, 3]
outlier_counts_by_threshold = {z: 0 for z in z_thresholds}

for df in param_dfs.values():
    for column in df.columns:
        series = df[column].dropna()  # Drop NaN values
        counts = count_outliers_by_z_threshold(series, z_thresholds)
        for z_thresh, count in counts.items():
            outlier_counts_by_threshold[z_thresh] += count

# Convert counts to a list for plotting
counts = [outlier_counts_by_threshold[z] for z in z_thresholds]

# Plotting
plt.figure(figsize=(8, 6))
bars = plt.bar(z_thresholds, counts, color=['blue', 'orange', 'green'])

plt.xlabel('Z-score Threshold')
plt.ylabel('Number of Outliers')
plt.title('Outliers Count by Z-score Threshold')
plt.xticks(z_thresholds)

# Annotate each bar with its height value
for bar in bars:
    yval = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2, yval + 0.05 * max(counts), int(yval), ha='center', va='bottom')

plt.show()

由于我们处理的是时间序列数据,因此可视化每年的异常值分布会很有用:

df['Z-Score'] = zscore(df['Value'])

# Define outliers based on Z-score greater than 3 or less than -3
df['Outlier'] = (df['Z-Score'] > 3) | (df['Z-Score'] < -3)

# Count outliers per year
outlier_counts = df.resample('Y')['Outlier'].sum().astype(int)

# Reset index to get the year in a separate column and prepare for plotting
outlier_counts = outlier_counts.reset_index()
outlier_counts['Year'] = outlier_counts['index'].dt.year
outlier_counts.drop('index', axis=1, inplace=True)

plt.figure(figsize=(10, 6))
colors = plt.cm.viridis(np.linspace(0, 1, len(outlier_counts)))
plt.bar(outlier_counts['Year'], outlier_counts['Outlier'], color=colors)
plt.title('Distribution of Outliers Per Year')
plt.xlabel('Year')
plt.ylabel('Number of Outliers')
plt.xticks(outlier_counts['Year'])  # Ensure all years are shown
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()

或者按季节:

稳健的 Z 分数

该技术也称为改进的 Z 分数法,它通过使用中位数 (M) 代替平均值来改进传统的 Z 分数,因为平均值通常不是最可靠的统计指标。此外,它采用中位数绝对偏差 (MAD) 而不是标准偏差:

稳健的 Z 分数方程
 

     修改后的 Z 分数公式中的因子 0.6745 用于使 MAD 的尺度与正态分布中的标准差的尺度相当。

这种调整是必要的,因为根据定义,对于同一数据集,MAD 小于标准差。因此,需要调整阈值以考虑这种缩放。

注意:中位数和 MAD 对异常值的稳健性比平均值和标准差更高。这种稳健性有时会导致较小的 MAD 值,尤其是当异常值极端时。因此,可能需要稍高的阈值来确保只有最极端的观测值才会被标记为异常值(3.5、4 甚至 5,具体取决于上下文)。

from scipy.stats import median_absolute_deviation

# Calculate modified Z-score using median and median absolute deviation (MAD)
df['Median'] = df['Value'].median()
df['MAD'] = median_absolute_deviation(df['Value'])
df['Modified_Z-Score'] = 0.6745 * (df['Value'] - df['Median']) / df['MAD']

# Define outliers based on modified Z-score greater than 3 or less than -3
df['Outlier'] = (df['Modified_Z-Score'] > 3.5) | (df['Modified_Z-Score'] < -3.5)

# Count outliers per year
outlier_counts = df.resample('Y')['Outlier'].sum().astype(int)

# Reset index to get the year in a separate column and prepare for plotting
outlier_counts = outlier_counts.reset_index()
outlier_counts['Year'] = outlier_counts['index'].dt.year
outlier_counts.drop('index', axis=1, inplace=True)

# Plotting per year
plt.figure(figsize=(10, 6))
colors = plt.cm.viridis(np.linspace(0, 1, len(outlier_counts)))
plt.bar(outlier_counts['Year'], outlier_counts['Outlier'], color=colors)
plt.title('Distribution of Outliers Per Year')
plt.xlabel('Year')
plt.ylabel('Number of Outliers')
plt.xticks(outlier_counts['Year'])  # Ensure all years are shown
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
每年异常值的分布

结合统计和视觉方法

让我们在时间序列图中直观地看到 Z 分数方法(阈值为 3)检测到的异常值。

#Here, outliers is a dict 
def plot_outliers(df, outliers_dict):
    # Number of rows in the subplot will be the number of columns with outliers per stage
    for stage, columns_outliers in outliers_dict.items():
        # Determine the number of plots
        num_plots = len(columns_outliers)
        if num_plots == 0:
            continue  # Skip if no outliers for this stage
        
        # Create subplots
        fig, axes = plt.subplots(nrows=num_plots, figsize=(15, num_plots * 5), sharex=True)
        fig.suptitle(f'Outlier Visualization for {stage} Stage', fontsize=16)
        
        if num_plots == 1:  # If only one plot, wrap it in a list to make iterable
            axes = [axes]
        
        for ax, (column, outliers) in zip(axes, columns_outliers.items()):
            # Plot the full data series
            ax.plot(df.index, df[column], label=f'{column} (Full Series)', color='black', linestyle='-', marker='', alpha=0.5)
            
            # Highlight the outliers
            if not outliers.empty:
                ax.scatter(outliers.index, outliers, color='red', label='Outliers', marker='o', s=50)
            
            ax.set_title(f'{column} Outliers')
            ax.set_ylabel('Values')
            ax.legend()
            
            # Set x-axis major locator and formatter
            ax.xaxis.set_major_locator(mdates.YearLocator())
            ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
            plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
        
        plt.tight_layout(rect=[0, 0, 1, 0.96])  # Adjust layout
        plt.show()
带有异常值的时间序列图

格鲁布斯检验

格鲁布斯检验,也称为最大正态化残差检验,通过将数据集中的最大值或最小值与数据集的平均值和标准差进行比较来识别潜在的异常值。

注意:假设数据遵循单变量正态分布,并且异常值相对于平均值和标准差具有极值。

值得注意的是,格鲁布斯检验通常一次对一个数据点进行,要么针对数据集中的最大值,要么针对最小值。

格鲁布斯检验方程

    G是格鲁布斯检验统计量,X是疑似异常值(数据集中的最大值或最小值),X(带条)是数据集的平均值,s是数据集的标准差。

为了确定疑似异常值是否显著,可以将计算出的检验统计量G与相应统计分布(通常是 T 分布)的临界值进行比较。

如果G超过临界值,则认为该疑似异常值具有统计学意义,表明它很可能是一个异常值。

from pyod.models.grubbs import grubbs_test

# Apply Grubbs' test using PyOD
outliers_grubbs = []
for col in df.columns:
    outliers = grubbs_test(df[col])
    outliers_grubbs.extend([(idx, col) for idx in outliers])
print("Outliers detected by Grubbs' test:")
print(outliers_grubbs)

评估指标

         我只会通过一些例子来提及主要的评估指标。

选择正确的评估指标对于评估异常检测方法的有效性至关重要,尤其是在时间序列分析中。

最佳指标取决于数据的性质、您所针对的特定异常以及假阳性和假阴性之间所需的平衡。

示例:欺诈检测

在欺诈检测中,异常虽然罕见但至关重要,因此精确度、召回率和 F1 分数至关重要。精确度衡量标记的异常中有多少是真正的异常。召回率衡量该方法正确识别的真正异常数量。F1 分数平衡了精确度和召回率,让您全面了解方法的性能。

示例:预测性维护

对于预测性维护,及时识别异常至关重要。在这里,接收者操作特性 (ROC) 曲线和曲线下面积 (AUC)是您的首选指标。这些指标可帮助您了解真阳性率和假阳性率之间的权衡,这对于优化检测阈值以满足不同的运营需求至关重要。

例如:医疗保健

在医疗保健领域,可解释性至关重要,因此可视化也至关重要。它们允许领域专家验证检测到的异常并了解其对决策的影响。通过整合领域知识,这些可视化工具增强了结果的相关性和可解释性,确保检测到的异常既可操作又重要。

选择正确的指标可以对您检测和管理时间序列数据中异常的效率产生重大影响。

       暂时就这些!如果你喜欢这篇文章,一定要点个赞 😋。也欢迎你关注我 ,获取类似的文章!

别忘了查看本系列的第二篇文章:

查找时间序列数据中异常值的终极指南(第2部分)用于学习时间序列中异常检测的有效机器学习方法和工具。

感谢关注雲闪世界。(亚马逊aws和谷歌GCP服务协助解决云计算及产业相关解决方案)


 订阅频道(https://t.me/awsgoogvps_Host)


 TG交流群(t.me/awsgoogvpsHost)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值