天天看點

時間序列分析|資料裁剪和滾動異常值檢測

目錄

  • ​​前言​​
  • ​​先看資料​​
  • ​​資料裁剪​​
  • ​​滾動異常檢測​​
  • ​​參考文獻​​

前言

在上一篇 《​​時間序列分析|異常值檢測​​》 一文中,我們介紹了如何利用TSOD子產品對5類異常值進行檢測,這一篇我們将要介紹如何利用時序本身計算出來的統計量進行時序資料裁剪,滾動異常值檢測。

先看資料

我們還是以air_passenger資料集為對象進行示範,先來看一下原資料長什麼樣子并對其進行可視化

import datetime
import time
import tsod
import pandas as pd
import pmdarima as pm
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
from sklearn.metrics import mean_absolute_error 
from tsod import RangeDetector,GradientDetector,ConstantGradientDetector,RollingStandardDeviationDetector,DiffDetector

air_passenger = pd.read_csv(r"D:\項目\時間序列\air_passenger.csv") #讀取資料
air_passenger['date'] = pd.to_datetime(air_passenger['date']) #datetime格式化

fig = plt.figure(figsize = (6,4)) #建立6*4的畫布
plt.plot(air_passenger['date'],air_passenger['passenger'] ,label = 'ts_origin') #原時序圖
plt.xticks(rotation =45) #橫坐标逆時針傾斜45度
plt.legend() 
plt.show()      
時間序列分析|資料裁剪和滾動異常值檢測

原資料就2列,一列是date,一列是passenger, 以date為橫坐标,passenger為縱坐标,繪制出air_passenger的時序圖,從air_passenger時序圖我們看到序列的時間跨度是從2019年09月之前開始一直到2022年09月,中間有4段處于停飛狀态,2020年05月到2020年09月中間有一段至暗時期,人數跌落到50人以下,姑且稱為波谷,但是再2021年05月到2021年09月有一段高光時期,人數突破了200大關,暫稱為波峰,除去這兩段時期外,其餘大部分位于150上下波動,且波動幅度不是特别大,那麼我們可以預判這些為平穩時期表現,其餘的如波峰,波谷,停飛期都可視為異常片段或者異常點,異常片段就是異常點形成聚集效果的時間段,現在要想辦法将其甄别出來并适當剔除。

資料裁剪

資料裁剪是指對整個時序進行切片,保留那些正常的可為模組化利用的時序片段。有時候,整個時序中會存在大時間跨度的空缺進而導緻時序不連貫,姑且稱為空檔期,因為空擋期的前後時序的特征極有可能發生的特征遷移,如上圖的2020年7月到2021年7月,時序就發生了特征遷移,假如利用空檔期前時序進行訓練,肯定不能用于空擋期後序列的預測,假如将空缺期前後序列混在一起進行訓練的話,勢必帶來大量噪聲,這對時序分析是大忌,如果整個時序觀測點,特别是空檔期後面的觀測樣本點比較豐富,完全可以進行時序截取,隻保留白檔期後面的時序進行訓練和測試,下面我們就針對這種情況進行資料截取示範。

首先,我們需要把那些空檔期找出來,為此,定義一個時間跨度的度量date_diff, 其計算方法就是date的向前一階差分序列,即用date列的後面一個值減去前面一個值求得相鄰兩個date的時間差。

air_passenger = air_passenger[["date", "passenger"]].sort_values(by="date") #按日期升序排序
air_passenger['day_diff'] = [16]+[(air_passenger['date'].tolist()[i+1] - air_passenger['date'].tolist()[i]).days for i in range(len(air_passenger)-1)] #計算天數差
leap_point = [air_passenger['date'].tolist()[i]  for i in range(len(air_passenger)) if air_passenger['day_diff'].tolist()[i]>15] #時間跳躍點

print(air_passenger, leap_point)      

這樣,我們就多了一列air_passenger[‘day_diff’]用來表示時間差,為了資料對齊,我們在最前面加了一個機關長度的清單[16]拼接而成,這裡的16可依據業務場景而定,因為我這裡認為半個月(15天)停飛就是很不正常了,是以一切大于15天的都會被識别為空檔期,設定為16恰好比半個月多一天,自然會被識别出來; 并且可以将這些空檔期的日期leap_point(跳躍點)挑出來做成一個清單。

0   2019-06-30        153        16
1   2019-07-07        154         7
2   2019-07-14        156         7
3   2019-07-21        158         7
4   2019-07-28        152         7
..         ...        ...       ...
405 2022-08-29        142         2
406 2022-08-30        155         1
407 2022-09-01        185         2
408 2022-09-03        147         2
409 2022-09-05        132         2

[410 rows x 3 columns] [Timestamp('2019-06-30 00:00:00'), Timestamp('2019-10-27 00:00:00'), Timestamp('2020-05-18 00:00:00'), Timestamp('2021-03-28 00:00:00'), Timestamp('2022-07-09 00:00:00')]      

我們看到一共有5個leap_point, 分别是2019-06-30,2019-10-27,2020-05-18,2021-03-28,2022-07-09,對應圖中空檔期的尾巴,現在隻要把所有跳躍點都裁剪掉便得到後面的不含空檔期的時序了,完成了資料裁剪。

ts_without_gap = air_passenger[air_passenger['date']>=leap_point[-1]] #取最近一個時間跳躍點後面的時序

fig = plt.figure(figsize = (6,4)) #建立6*4的畫布
plt.plot(ts_without_gap['date'],ts_without_gap['passenger'] ,label = 'ts_without_gap') #原時序圖
plt.xticks(rotation =45) #橫坐标逆時針傾斜45度
plt.legend() 
plt.show()      
時間序列分析|資料裁剪和滾動異常值檢測

由上圖我們可以看到,裁剪後的時序是從2022-07-09開始的,這段時序還是比較正常的,連貫且沒有跳躍點,可以作為後續訓練時序模型的原資料。

滾動異常檢測

上一文在RollingStandardDeviationDetector檢測一節中說過滾動标準差檢測是以某個固定的滾動視窗進行向前滾動統計其均值和标準差,這完全可以用rolling函數把滾動均值算出來,然後将其标準差計算出來,規定其上下幾倍個标準差的即為正常範圍,超出這個範圍的即認為是異常值。

air_passenger.set_index("date", inplace = True) #重置索引, 将date_設定為索引index
ts = air_passenger["passenger"] #乘客人數序列
print("原序列\n", ts)
ts_rolling = ts.rolling(window = 7).mean() #7階截尾滑動平均
ts_rolling_std = ts_rolling.std() #标準差
# print("滑動平均:\n", ts_rolling, ts_rolling_std)
upper = (lambda x, y: x>y+1.5*ts_rolling_std)(ts, ts_rolling)
lower = (lambda x, y: x<y-1.5*ts_rolling_std)(ts, ts_rolling)
# print(upper, lower)
ts_filter = ts.drop(labels = ts[upper].index) #去上異常值
ts_filtered = ts_filter.drop(labels = ts[lower].index)#去下異常值
print("過濾後序列\n", ts_filtered)
plt.plot(ts, color = 'red', alpha = 1.0, label = 'ts_origin') #原序列
plt.plot(ts_rolling,   color = 'blue', alpha = 0.8, label = 'ts_rolling',) #移動平均序列
plt.plot(ts_filtered, color = 'green', alpha = 0.5,label = 'ts_filtered') #過濾後序列
plt.fill_between(ts_rolling.index, ts_rolling-2*ts_rolling_std, ts_rolling+2*ts_rolling_std, color='yellow', alpha= 0.2)
plt.legend()
plt.show()      
時間序列分析|資料裁剪和滾動異常值檢測

從上圖可以看到那些超出來的紅色樣本就是異常值了,透明度為0.2的黃色便是滾動均值的1.5倍标準差可行範圍,也隻有紅色曲線超出了這個範圍,便可以将其過濾掉,過濾後樣本點由原來的410個減少到385個,減少了一些噪聲點。

參考文獻