1. 程式人生 > 其它 >時間序列分析 Tsfresh 準備和處理時間序列資料

時間序列分析 Tsfresh 準備和處理時間序列資料

原文地址:點這裡

時間序列分析定義:

時間序列分析是指從按時間排序的資料點中抽取有價值的總結和統計資訊行為

時間序列分析即包含了對過去資料的診斷,也包括對未來資料的預測

尋找時間軸

時間序列在我們身邊是廣泛存在的,但有時候在資料儲存時並沒有一列顯示存在的時間列,這時候就需要我們去人為尋找和構造。以下是一些不同的事件列存在形式的例子:

  • 以事件記錄的時間構造時間列
  • 以另一個和時間相關的元素構造時間列,例如在一個數據集中行駛距離和時間是正相關的,此時就可以以距離來構造時間列
  • 以物理軌跡的順序作為時間列,例如在醫學,天氣等領域有些資料是以圖片的形式儲存的,此時可以從影象中提取時間列

時間序列可能遇到的問題

在時間序列資料分析中,時間戳是一個十分重要的特徵,它能幫我們有效理解資料。

第一個我們會遇到的問題是時間值是在哪個過程產生的,以及何時產生的。通常事件發生的時間和事件被記錄的時間往往是不一致的。例如一個研究員先在筆記本上以手寫的方式記錄,然後在結束研究後再統一以csv的格式錄入資料庫。那麼此時時間戳究竟表示的是手動記錄的時間還是錄入資料庫的時間。

因此在我們看到一個新的時間特徵時,總是應當首先確認這個事件時間是如何產生的。作為一個數據分析師,我們應當時刻有這樣一種意識,理解資料,理解時間戳的產生過程是我們的責任,只有充分理解了這些,我們才能更好地和其他同事溝通,用資料為業務賦能

第二個我們會遇到的問題如果我們在處理歷史遺留資料,並沒有清洗記錄的文件說明,也無法找到處理資料流的人來確認時間戳產生的方式。這時需要我們做一些經驗上的調查和推斷。

有一些通用的方法能幫助我們理解時間特徵:1)通過比較不同類別特徵(如不同使用者)的資料來理解某些時間模式(pattern)是否是共同的;2)使用聚合資料分析來理解時間特徵,如該時間戳是本地時間時區還是標準時間時區,該時間反應的是使用者行為還是一些外部限制(網路通訊)。

第三個值得探討的問題是什麼是一個有意義的時間尺度。當我們拿到一組時間序列資料時,要思考該選擇怎麼樣的時間解析度,這對於後續特徵構造和模型有效性都有很大的影響。通常這取決於你所研究物件的領域知識,以及資料如何被收集的細節。舉個例子,假設你正在檢視每日銷售資料,但如果你瞭解銷售經理的行為就會知道在許多時候他們會等到每週末才報告數字,他們會粗略估計每天的數字,而不是每天記錄它們,因為由於退貨的存在,每天的數值常常存在系統偏差。所以你可能會考慮將銷售資料的解析度從每天更改為每週以減少這個系統誤差。

清洗資料

資料清洗是資料分析的一個重要環節,對於時間序列資料也不例外。

  • 缺失值處理
  • 改變事件頻率
  • 平滑資料

缺失值處理:

缺失值的出現很常見,例如在醫療場景中,一個時間序列資料出現卻缺失可能有以下原因:

  • 病人沒有遵從醫囑
  • 病人的健康狀態很好,因此沒必要在每個時刻都記錄
  • 病人被忘記了
  • 醫療裝置出現隨機性的技術故障
  • 資料錄入問題

最常用的處理確實值得方法包括 填補(imputation)和刪除(deletion)兩種

Imputation:基於完整資料集的其他值填補缺失值

Deletion:直接刪除有缺失值的時間段

一般來說,我們更傾向於保留資料而不是刪除,避免造成資訊損失。在實際案例中,採取何種方式要考慮是否可以承受刪除特定資料的損失。

下面介紹三種資料填補方法:

  • Forward fill
  • Moving average
  • Interpolation

Foreard fill

前向填充法是用來填補資料最簡單的方法之一,核心思想是用缺失值之前出現的最近一個時間點的數值來填補當前缺失值。使用這種方法不需要任何數學或複雜邏輯。

與前向填充相對應的,還有一種backward fill的方法,顧名思義,是指用缺失值之後出現的最近一個時間點的數值來填充。但是使用這種方法需要特別謹慎,因為這種方法是一種lookahead行為,只有當你不需要預測未來資料的時候才能考慮使用。

總結前向填充法的優點,計算簡單,很容易用於實時流媒體資料。

Moving average

移動平均法是填補資料的另一種方法,核心思想是取出缺失值發生之前的一段滾動時間內的值,計算其平均值或中位數來填補缺失。在有些場景下,這種方法會比前向填充效果更好,例如資料的噪聲很大,對於單個數據點有很大的波動,但用移動平均的方法就可以弱化這些噪聲。

同樣的,你也可以使用缺失值發生之後的時間點計算均值,但需要注意lookahead問題。

另外一個小trick是,計算均值時可以根據實際情況採取多種方法,如指數加權,給最近的資料點賦予更高的權重。

Interpolation

插值是另一種確定缺失資料點值的方法,主要基於我們希望整體資料如何表現的各種影象上的約束。 例如,線性插值要求缺失資料和鄰近點之間滿足一定的線性擬合關係。因此插值法是一種先驗方法,使用插值法時需要代入一些業務經驗。

在許多情況下,線性(或樣條)插值都是非常合適的。例如考慮平均每週溫度,其中存在已知的上升或上升趨勢,氣溫下降取決於一年中的時間。或者考慮一個已知年度銷售資料 不斷增長的業務。在這些場景下,使用插值法都能取得不錯的效果。

當然也有很多情況不適合線性(或樣條)插值的場景。例如在天氣資料集中缺少降水資料,就不應在已知天數之間進行線性推斷,因為降水的規律不是這樣的。同樣,如果我們檢視某人每天的睡眠時間,我們也不應該利用已知天數的睡眠時間線性外推。

下面看程式碼例例項:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 設定最多顯示行數
pd.options.display.max_rows = 1000

# 匯入美國年度失業率資料
unemploy = pd.read_csv('..\\srouce\\UnEmployment\\USA.csv')
print(unemploy.head())

# 構建一列隨機缺失值列
unemploy['missing'] = unemploy['Value']
# 隨機選擇10%行 手動填充缺失值
mis_index = unemploy.sample(frac=0.1, random_state=999).index
unemploy.loc[mis_index, 'missing'] = None

1、使用forward fill填補缺失值
def forwardfill():
    unemploy['f_fill'] = unemploy['missing']
    unemploy['f_fill'].ffill(inplace=True)

    # 觀察填充效果
    plt.scatter(unemploy.TIME, unemploy.Value, s=10)
    plt.plot(unemploy.TIME, unemploy.Value, label='real')
    plt.scatter(unemploy[~unemploy.index.isin(mis_index)].TIME, unemploy[~unemploy.index.isin(mis_index)].f_fill, s=10,
                c='r')
    plt.scatter(unemploy.loc[mis_index].TIME, unemploy.loc[mis_index].f_fill, s=50, c='r', marker='v')
    plt.plot(unemploy.TIME, unemploy.f_fill, label='forward fill')
    plt.legend()
    plt.show()

2、使用moving average填補缺失值
 def movingaverage():
    unemploy['moveage'] = np.where(unemploy['missing'].isnull(),
                                   unemploy['missing'].shift(1).rolling(3, min_periods=1).mean(),
                                   unemploy['missing'])
    # 顯示
    plt.scatter(unemploy.TIME, unemploy.Value, s=10)
    plt.plot(unemploy.TIME, unemploy.Value, label='real')
    plt.scatter(unemploy[~unemploy.index.isin(mis_index)].TIME, unemploy[~unemploy.index.isin(mis_index)].f_fill, s=10,
                c='r')
    plt.scatter(unemploy.loc[mis_index].TIME, unemploy.loc[mis_index].f_fill, s=50, c='r', marker='v')
    plt.plot(unemploy.TIME, unemploy.f_fill, label='forward fill', c='r', linestyle='--')
    plt.scatter(unemploy[~unemploy.index.isin(mis_index)].TIME, unemploy[~unemploy.index.isin(mis_index)].moveage, s=10,
                c='r')
    plt.scatter(unemploy.loc[mis_index].TIME, unemploy.loc[mis_index].moveage, s=50, c='g', marker='^')
    plt.plot(unemploy.TIME, unemploy.moveage, label='moving average', c='g', linestyle='--')
    plt.legend()
    plt.show()
3、使用interpolation填補缺失值
 def interpolation():
    # 嘗試線性插入和多項式插入
    unemploy['inter_lin'] = unemploy['missing'].interpolate(method='linear')
    unemploy['inter_poly'] = unemploy['missing'].interpolate(method='polynomial', order=3)

    # 觀察填充效果
    plt.plot(unemploy.TIME, unemploy.Value, label='real')
    plt.plot(unemploy.TIME, unemploy.inter_lin, label='linear interpolation', c='r', linestyle='--')
    plt.plot(unemploy.TIME, unemploy.inter_poly, label='polynomial interpolation', c='g', linestyle='--')
    plt.legend()
    plt.show()

改變資料集的時間頻率

通常我們會發現來自不同資料來源的時間軸常常無法一一對應,此時就要用到改變時間頻率的方法進行資料清洗。由於無法改變實際測量資料的頻率,我們能做的是改變資料收集的頻率,也就是本節提到的上取樣(upsamping)和下采樣(downsampling)。

下采樣

下采樣指的是減少資料收集的頻率,也就是從原始資料中抽取子集的方式

以下是一些下采樣會用到的場景

  • 資料原始的解析度不合理:例如有一個記錄室外溫度的資料,時間頻率是每秒鐘一次。我們都知道,氣溫不會在秒鐘這個級別有明顯的變化,而且秒級的氣溫資料的測量誤差甚至會比資料本身的波動還要大,因此這個資料集有著大量的冗餘。在這個案例中,每隔n個元素取一次資料可能更合理一些。
  • 關注某個特定季節的資訊:如果擔心某些資料存在季節性的波動,我們可以只選擇某一個季節(或月份)進行分析,例如只選擇每年一月份的資料進行分析。
  • 進行資料匹配:例如你有兩個時間序列資料集,一個更低頻(年度資料),一個更高頻(月度資料),為了將兩個資料集匹配進行下一步的分析,可以對高頻資料進行合併操作,如計算年度均值或中位數,從而獲得相同時間軸的資料集

上取樣

上取樣在某種程度上是憑空獲得更高頻率資料的方式,我們要記住的是使用上取樣,只是讓我們獲得了更多的資料標籤,而沒有增加額外的資訊。

以下是一些上取樣會用到的場景:

  • 不規律的時間序列:用於處理多表關聯中存在不規則時間軸的問題
    例如現在有兩個資料,一個記錄了捐獻的時間和數量

    另一個記錄了公共活動的時間和代號

    這是我們需要合併這兩個表的資料,為每次捐獻打上標籤,記錄每次捐獻之前最近發生的一次公共活動,這種操作叫做rolling join,關聯後的資料結果如下。

  • 進行資料匹配:雷士下采樣的場景,例如我們有一月度的失業率資料,為了和其他資料匹配需要轉換成日度的資料,如果我們假定新工作一般都是從每個月的第一天開始的,那麼可以推演認為這個月每天的失業率都等於該月的失業率。

通過以上的案例我們發現,即使是在十分乾淨的資料集中,由於需要比較來自不同維度的具有不同尺度的資料,也經常需要使用到上取樣和下采樣的方法

平滑資料

資料平滑也是一個常用的資料清洗的技巧,為了能講述一個更能被理解的故事,在資料分析前常常會進行平滑處理。資料平滑通常是為了消除一些極端值或測量誤差。即使有些極端值本身是真實的,但是並沒有反應出潛在的資料模式,我們也會把它平滑掉。

在講述資料平滑的概念時,需要引入下圖層層遞進

weighted averaging,也就是上文曾經講過的moving average,也是一種最簡單的平滑技術,即可以給予資料點相同的權重,也可以給越鄰近的資料點更高的權重

exponential smoothing,本質上和weighted averaging類似,都是給越鄰近的資料點更高的權重,區別在於衰減的方式不同,指數平滑法顧名思義,從最鄰近到最早的資料點的權重呈現指數型下降的規律,weighted averaging需要為每一個權重指定一個確定值。指數平滑法在很多場景下效果都很好,但它也有一個明顯的缺點,無法適用於呈現趨勢變化或季節性變化的資料。

t時刻的指數平滑後的值可以用以下公式表示,

其中St,St-1表示當前時刻和上一時刻的平滑值,xt表示當前時刻的實際值,α表示平滑係數,該係數越大則越近鄰的資料影響越大。

Holt Exponential Smoothing,這種技術通過引入一個額外的係數,解決了指數平滑無法應用於具有趨勢特點資料的不足,但但是依然無法解決具有季節性變化資料的平滑問題。

Holt-Winters Exponential Smoothing,這種技術通過再次引入一個新系數的方式同時解決了Holt Exponential Smoothing無法解決具有季節性變化資料的不足。簡單來說,它是在指數平滑只有一個平滑係數的基礎上,額外引入了趨勢係數和季節係數來實現的。這種技術在時間序列的預測上(例如未來銷售資料預測)有著很廣泛的應用。

(emm...  後面這幾種平滑我也不懂。。。)

下面看指數平滑的例子:

指數平滑
 def logflow():
    """
    實現指數平滑資料
    """

    # alpha 平滑係數
    unemploy['smooth_0.5'] = unemploy.Value.ewm(alpha=0.5).mean()
    unemploy['smooth_0.9'] = unemploy.Value.ewm(alpha=0.9).mean()

    plt.plot(unemploy.TIME, unemploy.Value, label='actual')
    plt.plot(unemploy.TIME, unemploy['smooth_0.5'], label='alpha=0.5')
    plt.plot(unemploy.TIME, unemploy['smooth_0.9'], label='alpha=0.9')
    plt.xticks(rotation=45)  # rotation 表示標籤的旋轉角度
    plt.legend()
    plt.show()