时间序列中的异常检测方法最全总结

时间序列无处不在!时间序列数据在每个行业都以某种形式明显存在,如在网站上的用户行为、财富500强公司的股票价格或任何其他与时间有关的例子。

当然,它也是被研究的最多的数据类型之一。根据经验,你可以说时间序列是一种基于某种时间相关维度(如年、月或秒)进行采样的数据类型。

时间序列是按有序方式记录的观察结果,它们在时间上是相互关联的。

在分析时间序列数据时,我们必须确保异常值,就像我们在静态数据中做的那样。如果你以任何身份与数据打交道,你就知道异常值给分析师带来多大的痛苦。这些异常值在时间序列的行话中被称为 "异常值"。

在分析时间序列数据时,我们必须像在静态数据中检查和处离群值一样来处理时间序列中的离群值。众所周知,离群值会给数据分析师带来很大的痛苦。而这些离群值(outliers)在时间序列术语中称为 "异常值"(anomalies)。

推荐阅读:理论结合实践,一文搞定异常检测技术

时间序列数据中的  
异常值/离群值及其类型

从传统观点来看,离群值/异常值是:

“一个与其他观察结果相差甚远的观察结果,使人怀疑它是由另一种机制产生的。”

因此,可以将离群值视为不遵循预期行为的观察值。

如上图所示,时间序列中的离群值可以有两种不同的含义。它们之间的语义区分主要是基于你作为分析者的兴趣,或特定的场景。

这些观察值与噪音、错误或不需要的数据有关,这些数据本身对分析师来说并不有趣。在这些情况下,应该删除或更正异常值,以提高数据质量,并生成一个可被其他数据挖掘算法使用的更干净的数据集。例如,消除传感器传输误差以获得更准确的预测,因为主要目标是进行预测。

然而,近年来,特别是在时间序列数据领域,许多研究人员致力于检测和分析不寻常但有趣的现象。欺诈检测就是一个很好的例子——它的主要目标是检测和分析离群值本身。这些观察值通常被称为异常值。

时间序列的异常检测问题通常表述为识别相对于某个常态或通常信号的异常数据点。看看一些异常值类型:

点离群值

点离群点是指在一个特定的时间实例中,与时间序列中的其他数值(全局离群点)或与其相邻的点(局部离群点)相比,表现异常的数据。

例如:一批散户投资者买入了某支股票,以报复大型对冲基金,推动股价一路上涨。由于不太可能的事件而发生的突然的、短暂的峰值是一个加性(点)离群值。基于时间的值在短时间内的意外增长(看起来像突然的峰值)属于加性离群值。

点离群值可以是单变量的,也可以是多元变量的,分别取决于它们是影响一个还是多个随时间变化的变量。

下图a包含两个单变量点离群点O1和O2,而多变量时间序列由下图b中的三个变量组成,同时具有单变量(O3)和多变量(O1和O2)离群点。

时间序列数据中的异常值  
来源:https://arxiv.org/pdf/2002.04236.pdf

我们将在异常检测部分更深入地研究单变量点离群值。

子序列离群值

这意味着连续的时间点的联合行为是不寻常的,尽管每个单独的观察并不一定是一个离群点。子序列离群值也可以是全局的或局部的,并且可以影响一个(单变量子序列离群值)或多个(多变量子序列离群值)时间相关变量。

下图提供了一个单变量(图a中的O1和O2,以及图2b中的O3)和多元变量(图b中的O1和O2)子序列异常值的例子。请注意,后者不一定会影响所有变量(例如,图b中的O2)。

时间序列数据中的子序列离群值  
来源:https://arxiv.org/pdf/2002.04236.pdf

案例数据

plt.figure(figsize=(10,4),dpi=200)
plt.plot(lim_catfish_sales)
plt.title('Catfish Sales in 1000s of Pounds', fontsize=20)
plt.ylabel('Sales', fontsize=16)
for year in range(start_date.year,end_date.year):
    plt.axvline(pd.to_datetime(str(year)+'-01-01'), 
                color='k', linestyle='--', alpha=0.2)

时序数据中的异常检测技术

分析师们可以采用一些技术来识别数据中的不同异常情况,从基本的统计分解到自动编码器。基本统计分解开始,了解它是如何和为什么有用。

STL分解

STL[1]是指基于LOESS的季节--趋势分解程序。该技术将时间序列信号分解成三个部分:季节性、趋势性和残差

它适用于季节性时间序列,这也是最常用的时间序列数据类型。为了生成STL分解图,我们只需使用的statsmodels来完成繁重的工作。

plt.rc('figure',figsize=(12,8))
plt.rc('font',size=15)
result = seasonal_decompose(lim_catfish_sales,
                            model='additive')
fig = result.plot()
1996-2000年的鲶鱼销售数据,其中1998年12月引入了一个反常现象

如果我们分析residue的偏差,并为其引入一些阈值,则得到一个异常检测算法。需要来自分解的残留物数据。

plt.rc('figure',figsize=(12,6))
plt.rc('font',size=15)
fig, ax = plt.subplots()
x = result.resid.index
y = result.resid.values
ax.plot_date(x, y, color='black',linestyle='--')
ax.annotate('Anomaly', 
            (mdates.date2num(x[35]), y[35]), 
                      xytext=(30, 20),
                    textcoords='offset points',
            color='red',
            arrowprops=dict(facecolor='red',
                            arrowstyle='fancy'))
fig.autofmt_xdate()
plt.show()
STL分解的残差
优点

它简单、稳健,可以处理很多不同的情况,而且仍然可以直观地解释所有的异常情况。

缺点

这种技术的最大缺点是僵化的调整选项。除了阈值和可能的置信区间之外,没有什么可以做的。例如,你正在跟踪你的网站上的用户,这个网站本来是不对公众开放的,后来突然被开放了。在这种情况下,你应该分别跟踪启动期前后出现的异常情况。

分类和回归树(CART树)

我们可以利用决策树的力量和稳健性来识别时间序列数据中的异常值/异常现象。

  • 首先,可以训练监督学习之树模型对异常和非异常数据点进行分类。为此需要有标记的异常数据点。

  • 并且,可以使用无监督学习隔离森林算法来预测某个点是否是离群点,而不需要任何标记的数据集的辅助。

与其他流行的离群点检测方法不同,Isolation Forest (孤立森林)基于决策树的集成树模型。主要思想是明确地识别异常点,而不是对正常数据点进行剖析。

换句话说,Isolation Forest[2] 检测异常完全基于异常是少量且不同的数据点这一事实。异常隔离不使用任何距离或密度测量。

  • 在应用孤立森林模型时,我们设置参数 contamination=outliers_fraction,也就是告诉模型数据中离群值的比例。这是一个试验/错误的指标。

  • 拟合和预测(数据)对数据进行异常值检测,正常返回1,异常返回-1。

  • 最后,我们用时间序列视图将异常情况可视化。

首先,将时间序列数据可视化。

plt.rc('figure',figsize=(12,6))
plt.rc('font',size=15)
catfish_sales.plot()
引入了不同的(多个)异常点的的鲶鱼销售数据

接下来,我们需要设置一些参数,如离群值,并训练我们的 IsolationForest 模型。我们可以利用scikit-learn来实现孤立森林算法。

outliers_fraction = float(.01)
scaler = StandardScaler()
np_scaled = scaler.fit_transform(catfish_sales.values.reshape(-1, 1))
data = pd.DataFrame(np_scaled)
# train isolation forest
model =  IsolationForest(contamination=outliers_fraction)
model.fit(data)

最后,运用可视化技术来直观地了解预测的情况。

catfish_sales['anomaly'] = model.predict(data)
# visualization
fig, ax = plt.subplots(figsize=(10,6))
a = catfish_sales.loc[catfish_sales['anomaly'] == -1, 
                      ['Total']] #anomaly
ax.plot(catfish_sales.index, 
        catfish_sales['Total'], 
        color='black', 
        label = 'Normal')
ax.scatter(a.index,a['Total'], 
           color='red', 
           label = 'Anomaly')
plt.legend()
plt.show();
使用孤立森林算法进行异常检测

该算法在识别人工植入的离群点方面做得相当好,但它也在开始时将几个正常的点标为 "离群点"。这是由两个原因造成的。

  • 在开始时,该算法初现雏形,无法理解什么是异常现象。随着得到的数据越多,它能看到的差异就越多,它就能自我调整。

  • 如果你看到很多真正的负样本,则参数contamination设置的太高了相反,如果在应该出现红点的地方没有看到,则参数contamination设置得太低。

优点

这种技术的最大优点是你可以引入尽可能多的随机变量或特征,以制作更复杂的模型。

缺点

越来越多的特征会显著地影响计算性能。此时应实现仔细选择特征。

利用预测进行检测

使用预测法的异常检测是基于这样一种方法:从过去的几个点生成下一个点的预测,其中添加了一些随机变量,这些随机变量通常是白噪声。

可以想象,未来的预测点将生成新的点,以此类推。它对预测地平线的明显影响是信号变得更加平滑。

使用这种方法的困难之处在于,你应该选择差值的数量、自回归的数量和预测误差系数。

每次处理一个新的信号时,都应该建立一个新的预测模型。

另一个重要的制约因素是,信号在差分后应该是静止的。即信号不应该依赖于时间。

我们可以利用不同的预测方法,如移动平均数、自回归方法和ARIMA及其不同的变体。ARIMA检测异常的过程如下

  • 从过去的基准点预测新的点,并找出与训练数据中那些点的大小差异。

  • 选择一个阈值,并根据该差异阈值来识别异常情况。

使用时间序列中的模块fbprophet来实现上述方法,该模块专门针对静止性和季节性,并可以用一些超参数进行调整。

引入了不同的(多个)异常点的的鲶鱼销售数据

我们将利用与上面相同的异常数据。首先,让我们把它导入并使其准备好用于环境。

from fbprophet import Prophet

定义预测函数。fbprophet 会增加一些额外的指标作为新特征,以帮助更好地识别异常情况。例如,预测的时间序列变量(由模型),目标时间序列变量的上限和下限,以及趋势指标。

def fit_predict_model(dataframe, 
                      interval_width = 0.99, 
                      changepoint_range = 0.8):
   m = Prophet(daily_seasonality = False, 
               yearly_seasonality = False, 
               weekly_seasonality = False,
               seasonality_mode = 'additive',
               interval_width = interval_width,
               changepoint_range = changepoint_range)
   m = m.fit(dataframe)
   forecast = m.predict(dataframe)
   forecast['fact'] = dataframe['y'].reset_index(drop = True)
   return forecast
  
pred = fit_predict_model(t)

现在要把pred变量推给另一个函数,它将根据时间序列变量中的下限和上限的阈值来检测异常情况。

def detect_anomalies(forecast):
   forecasted = forecast[['ds','trend', 'yhat', 
                          'yhat_lower', 'yhat_upper', 
                          'fact']].copy()
forecasted['anomaly'] = 0
   forecasted.loc[forecasted['fact'] > forecasted['yhat_upper'], 
                  'anomaly'] = 1
   forecasted.loc[forecasted['fact'] < forecasted['yhat_lower'], 
                  'anomaly'] = -1
#anomaly importances
   forecasted['importance'] = 0
   forecasted.loc[forecasted['anomaly'] ==1, 'importance'] = \
       (forecasted['fact'] - forecasted['yhat_upper'])/forecast['fact']
   forecasted.loc[forecasted['anomaly'] ==-1, 'importance'] = \
       (forecasted['yhat_lower'] - forecasted['fact'])/forecast['fact']

   return forecasted
pred = detect_anomalies(pred)

最后,我们只需要绘制上述预测,并将异常情况可视化。

使用孤立森林算法进行异常检测
优点

这个算法很好地处理了不同的季节性参数,如月度或年度,而且它对所有的时间序列指标都有本机支持。

如果你仔细观察,与孤立森林算法相比,这种算法可以很好地处理边缘案例。

弊端

由于这种技术是基于预测的,它在有限的数据情况下会很困难。在有限的数据中,预测的质量会降低,异常检测的准确性也会降低。

基于聚类的异常情况检测

目前,我们已经一起学习了使用无监督机器学习IsolationForest算法的异常检测方式。现在,我们将研究另一种无监督的技术--聚类!

该方法简单明了,一般情况下,落在定义的聚类之外的数据实例有可能被标记为异常值。接下来使用k-means聚类来做异常检测实例。

为了方便聚类结果的可视化,将使用一个不同的数据集,对应于一个或多个基于时间的变量的多变量时间序列。

数据集描述:数据包含购物和购买的信息,以及价格竞争力的信息。而这里使用的是该数据集的一个子集(列/特征是相同的)。

传送门:https://www.kaggle.com/c/expedia-personalized-sort/data

首先使用肘部法确认k-means簇群中心数。其中肘部法是一个簇群数量与解释的方差/目标/分数的图表。使用scikit-learn的K-means实现。

X = df[['price_usd', 
        'srch_booking_window', 
        'srch_saturday_night_bool']]
X = X.reset_index(drop=True)
km = KMeans(n_clusters=10)
km.fit(X)
km.predict(X)
labels = km.labels_
# Plotting
fig,ax = plt.subplots(figsize=(10,6))
ax = Axes3D(fig, rect=[0, 0, 0.95, 1], elev=48, azim=134)
ax.scatter(X.iloc[:,0], X.iloc[:,1], X.iloc[:,2],
           c=labels.astype(np.float), 
           edgecolor="k")
ax.set_xlabel("price_usd")
ax.set_ylabel("srch_booking_window")
ax.set_zlabel("srch_saturday_night_bool")
plt.title("K Means", fontsize=14);

从上面的肘部曲线中,我们看到图形在10个聚类后趋于平缓,即增加更多的聚类并不能解释相关变量的更多价格(美元)差异。

我们设置n_clusters=10,在生成k-means输出后,用数据来绘制三维聚类。

X = df[['price_usd', 
        'srch_booking_window', 
        'srch_saturday_night_bool']]
X = X.reset_index(drop=True)
km = KMeans(n_clusters=10)
km.fit(X)
km.predict(X)
labels = km.labels_
# #Plotting
fig,ax = plt.subplots(figsize=(10,6), dpi=200)
ax = Axes3D(fig, rect=[0, 0, 0.95, 1], elev=48, azim=134)
ax.scatter(X.iloc[:,0], X.iloc[:,1], X.iloc[:,2],
          c=labels.astype(np.float), edgecolor="k")
ax.set_xlabel("price_usd")
ax.set_ylabel("srch_booking_window")
ax.set_zlabel("srch_saturday_night_bool")
plt.title("K Means", fontsize=14);

现在需要找出要保留的组件(特征)的数量。

data = df[['price_usd', 'srch_booking_window', 
           'srch_saturday_night_bool']]
X = data.values
X_std = StandardScaler().fit_transform(X)
#计算协方差矩阵的特征值
mean_vec = np.mean(X_std, axis=0)
cov_mat = np.cov(X_std.T)
eig_vals, eig_vecs = np.linalg.eig(cov_mat)
# 创建一个(特征值,特征向量)图元的列表
eig_pairs = [ (np.abs(eig_vals[i]),eig_vecs[:,i]) for i in range(len(eig_vals))]

eig_pairs.sort(key = lambda x: x[0], reverse= True)

# 从特征值计算解释方差
tot = sum(eig_vals)
var_exp = [(i/tot)*100for i in sorted(eig_vals, reverse=True)] # 个体解释方差
cum_var_exp = np.cumsum(var_exp) # 累计解释方差

plt.figure(figsize=(12, 6), dpi=200)
plt.bar(range(len(var_exp)), var_exp, 
        alpha=0.3, align='center', 
        label='个体解释方差', 
        color = 'y')
plt.step(range(len(cum_var_exp)), 
         cum_var_exp, where='mid',
         label='累积解释方差')
plt.ylabel('解释的方差比率')
plt.xlabel('主成分')
plt.legend(loc='best')
ax= plt.gca()
add_name(ax)
plt.show();

我们看到,第一部分几乎解释了50%的方差。第二部分解释了30%以上。然而,几乎没有一个成分是真正可以忽略不计的。前两个成分包含了80%以上的信息。因此,我们将设定n_components=2

基于聚类的异常检测的基本假设是,如果对数据进行聚类,正常数据将被归属于常规簇群,而异常数据将不归属于任何簇群,或被归属于小簇群。

我们使用以下步骤来寻找和显示异常情况。

  • 计算每个点与最近的中心点之间的距离。最大的距离被认为是反常的。

  • 我们使用outliers_fraction来向算法提供关于我们的数据集中存在的异常值比例的信息,与IsolationForest算法类似。这主要是一个需要直接设置、试验或网格搜索来设置的超参数——从起始outliers_fraction=0.1开始估计。

  • 使用outliers_fraction来计算number_of_outliers

  • 将阈值设置为这些离群值的最小距离。

  • anomaly1的异常结果包含上述方法Cluster(0:正常,1:异常)。

  • 用聚类视图可视化异常情况。

  • 用时间序列视图可视化异常情况。

# 返回每个点之间的距离以及它与最近的中心点之间的距离系列
def getDistanceByPoint(data, model):
    distance = pd.Series()
    for i in range(0,len(data)):
        Xa = np.array(data.loc[i])
        Xb = model.cluster_centers_[model.labels_[i]-1]
        distance.at[i]=np.linalg.norm(Xa-Xb)
    return distance

outliers_fraction = 0.1
# 得到每个点与它最近的中心点之间的距离。最大的距离被认为是异常的。
distance = getDistanceByPoint(data, kmeans[9])
number_of_outliers = int(outliers_fraction*len(distance))
threshold = distance.nlargest(number_of_outliers).min()
# anomaly1包含上述方法的异常结果 Cluster (0:normal, 1:anomaly)
df['anomaly1'] = (distance >= threshold).astype(int)
fig, ax = plt.subplots(figsize=(12,6), dpi=200)
colors = {0:'blue', 1:'red'}
ax.scatter(df['principal_feature1'], 
           df['principal_feature2'], 
           c=df["anomaly1"].apply(lambda x: colors[x]))
plt.xlabel('主要特征1')
plt.ylabel('主要特征2')
ax= plt.gca()
add_name(ax)
plt.show();

为了查看与现实世界特征相对应的异常情况,对上一步创建的数据框架进行处理。

df = df.sort_values('date_time')
#df['date_time_int'] = df.date_time.astype(np.int64)
fig, ax = plt.subplots(figsize=(12,6), dpi=200)

a = df.loc[df['anomaly1'] == 1, ['date_time', 'price_usd']] #anomaly

ax.plot(pd.to_datetime(df['date_time']), 
        df['price_usd'], 
        color='k',
        label='正常')
ax.scatter(pd.to_datetime(a['date_time']),
           a['price_usd'], 
           color='red', 
           label='异常')
ax.xaxis_date()
plt.xlabel('日期')
plt.ylabel('价格($)')
plt.legend()
fig.autofmt_xdate()
plt.show();

这种方法能够很好地封装峰值,当然也有一些遗漏。部分问题可能是 outlier_fraction 没有处理很多值。

优点

这种技术的最大优势类似于其他无监督技术,即你可以引入尽可能多的随机变量或特征来训练更复杂的模型。

缺点

缺点是,越来越多的特征会很快开始影响你的计算性能。因此在性能方面总是有可能出现高模型方差,因此在性能方面总是有可能出现高模型方差。

自动编码器

现今,在谈论数据技术时,不能没有深度学习!因此,云朵君将和大家一起学习一下使用自动编码器(Autoencoders)的异常检测

自动编码器是一种无监督的技术,在通过不同维度提取其特征的同时,重新创建输入数据。换句话说,如果使用自动编码器的数据的潜在表征,它对应于降维技术。

为什么要应用降维来寻找离群值?

如果我们降低维度,不是会失去一些信息吗,包括离群值?答案是,一旦主要模式被识别出来,异常值就会显现出来。许多基于距离的技术(如KNNs)在计算整个特征空间中每个数据点的距离时,都会受到维度的诅咒。高维度必须被降低。

有趣的是,在降维的过程中,异常值被识别出来。我们可以说离群点检测是降维的一个副产品。

为什么是自动编码器?

有许多有用的工具,如主成分分析(PCA)用于检测离群值。为什么我们需要自动编码器?原因是PCA使用线性代数进行转换。相比之下,自动编码器技术可以通过其非线性激活函数和多层来进行非线性转换。用自动编码器训练几层,而不是用PCA训练一个巨大的变换,这样更有效率。因此,当数据问题是复杂的、非线性的时候,自动编码器技术就显示出其优点。

建立模型

我们可以用Tensorflow或Pytorch等流行的框架来实现自动编码器,为了简单起见,我们将使用一个叫做PyOD的python模块,它在内部使用来自用户的少量输入来构建自动编码器。

对于数据部分,让我们使用PyOD的实用函数generate_data()来生成25个变量、500个观测值和10%的离群值。

第三方包,需要安装:!pip install pyod

import numpy as np
import pandas as pd
from pyod.models.auto_encoder import AutoEncoder
from pyod.utils.data import generate_data

contamination = 0.1# 异常值的百分比
n_train = 500# 训练点的数量
n_test = 500# 测试点的数量
n_features = 25# 特征的数量

X_train, y_train, X_test, y_test = generate_data(
    n_train=n_train, n_test=n_test,
    n_features= n_features, 
    contamination=contamination,random_state=1234)

X_train = pd.DataFrame(X_train)
X_test = pd.DataFrame(X_test)

当做无监督学习时,将预测因子标准化是非常有必要的。

from sklearn.preprocessing import StandardScaler
X_train = StandardScaler().fit_transform(X_train)
X_train = pd.DataFrame(X_train)
X_test = StandardScaler().fit_transform(X_test)
X_test = pd.DataFrame(X_test)

为了很好地了解数据,用PCA将其还原为两个维度,并绘制相应的图表。

from sklearn.decomposition import PCA
pca = PCA(2)
x_pca = pca.fit_transform(X_train)
x_pca = pd.DataFrame(x_pca)
x_pca.columns=['PC1','PC2']
cdict = {0: 'red', 1: 'blue'}
# 绘图
import matplotlib.pyplot as plt
plt.scatter(X_train[0], X_train[1], c=y_train, alpha=1)
plt.title('散点图')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

聚集在一起的黑色点是典型的观测值,黄色的点是离群值。

模型设定

[25, 2, 2, 25]。输入层和输出层分别有25个神经元。有两个隐藏层,每个有两个神经元。

第1步 建立模型
clf = AutoEncoder(hidden_neurons = [25, 2, 2, 25])
clf.fit(X_train)
第2步 确定切割点

应用训练好的模型clf来预测测试数据中每个观察点的异常分数。如何定义离群点?离群点是一个与其他点有一定距离的点,所以离群点的分数是由距离来定义的。PyOD函数.decision_function()计算了每个数据点的距离,即异常点得分。

# 获得训练数据的离群点分数
y_train_scores = clf.decision_scores_
# 预测异常点分数
y_test_scores = clf.decision_function(X_test) # 异常点分数
y_test_scores = pd.Series(y_test_scores)
# 绘制它!
import matplotlib.pyplot as plt
plt.hist(y_test_scores, bins='auto')
plt.title("Clf1模型异常得分的直方图")
plt.show()

如果用直方图来计算异常得分的频率,我们会看到高分对应的是低频率——离群值的证据。我们选择4.0作为切入点,那些>=4.0的是离群值。

第3步 按集群获得汇总统计

我们把那些异常分数小于4.0的观测值分配到聚类0,而把那些大于4.0的观测值分配到聚类1。同时,用.groupby()计算各群组的汇总统计。这个模型已经确定了50个异常值(未显示)。

df_test = X_test.copy()
df_test['score'] = y_test_scores
df_test['cluster'] = np.where(df_test['score']<4, 0, 1)
df_test['cluster'].value_counts()
df_test.groupby('cluster').mean()

下面的输出显示了每个群组的平均变量值。聚类 "1"(异常聚类)的数值与聚类 "0"(正常聚类)的数值差别很大。"分数" 值显示了这些观测值与其他观测值的平均距离。高 "分数" 意味着观察结果与正常情况相差甚远。

这样,就可以很完美地区分和标注典型基准点和异常点。

优点
  • 自动编码器可以轻松地处理高维数据。

  • 由于其非线性行为,它可以在高维数据集中找到复杂的模式。

缺点
  • 由于这是一个基于深度学习的策略,如果数据较少,它将尤其困难。

  • 如果网络的深度增加,并且在处理大数据时,计算成本将急剧上升。

到目前为止,我们已经看到了如何检测和识别异常现象。但是,真正的问题是在发现它们之后出现的。现在怎么办?我们该怎么做?

如何处理这些异常情况?

在检测之后,就会出现一个大问题,即如何处理我们确定的东西。有许多方法来处理新发现的信息。我将根据我的经验列出其中的一些方法,让你对如何处理这个问题有一个初步的了解。

了解商业案例

一般情况,异常现象是为我们的问题提供新的信息和视角。股票价格突然上涨?这肯定是有原因的。因此,了解飙升背后的原因可以帮助我们以有效的方式解决这个问题。

了解业务用例也可以帮助我们更好地识别问题。例如,你可能正在进行某种欺诈检测,这意味着你的主要目标确实是了解数据中的异常值。

如果这些都不是你关心的问题,你可以删除或忽略离群点。

调整异常值的统计方法

统计学方法可以用于调整异常点的数值,使之与原始分布相匹配。下面我们一起学习一种使用统计平均值来平滑异常值的方法。

使用平均值来平滑离群值

这个想法是通过使用前一个日期时间的数据来平滑异常情况。例如,为了平衡由于你家发生的事件而导致的突然用电情况,你可以取往年同月的平均用电量。

我们使用之前的鲶鱼销售数据,调整平均值。

adjusted_data = lim_catfish_sales.copy()
adjusted_data.loc[curr_anomaly] = december_data[
   (december_data.index != curr_anomaly) & 
    (december_data.index < test_data.index[0]) ].mean()

绘制调整后的数据和旧的数据将看起来像这样。

plt.figure(figsize=(10,4))
plt.plot(lim_catfish_sales, color='firebrick', alpha=0.4)
plt.plot(adjusted_data)
plt.title('Catfish Sales in 1000s of Pounds', fontsize=20)
plt.ylabel('Sales', fontsize=16)
for year in range(start_date.year,end_date.year):
    plt.axvline(pd.to_datetime(str(year)+'-01-01'), 
                color='k', linestyle='--', alpha=0.2)
plt.axvline(curr_anomaly, color='k', alpha=0.7)

这样,我们就可以继续应用预测或分析,而不必太担心结果中的偏斜性。

其实,处理非时间序列数据的方法相对较多,打但由于基础结构的不同,这些基本方法并不能直接用于Timeseries。非时间序列的处理方法涉及很多基于分布的方法,不能简单地转化为Timeseries数据。

去除异常点

如果以上两种方法都没有在你的解决方案中引发任何争论,那么最后一个选择就是把异常点处理掉。不建议这样做(因为你基本上摆脱了一些潜在的有价值的信息),除非它是绝对必要的,而且不会损害未来的分析。

我们可以在识别后使用pandas的.drop()函数来删除复杂的异常值。

写在最后

本文中,云朵君和大家一起学习了何为异常点,如何发现它们,以及如何处理异常值。

  • 时间序列数据因商业案例的不同而有很大的差异,所以最好是进行实验,找出有效的方法,而不是仅仅应用你发现的东西。经验可以创造奇迹!

  • 除了我们在本文中讨论的内容外,还有大量的异常检测技术。欢迎在评论区讨论。

参考资料

[1] STL分解: http://www.wessa.net/download/stl.pdf

[2] Isolation Forest: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.IsolationForest.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值