1. Python數(shù)據(jù)分析實(shí)戰(zhàn):用Pandas 處理時(shí)間序列

        共 7732字,需瀏覽 16分鐘

         ·

        2020-09-07 22:58



        low profile,no profile























        前言


        時(shí)間序列的處理是傳統(tǒng)經(jīng)濟(jì)學(xué)里面的一個(gè)重要篇章,在數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)的背景下,時(shí)間序列分析所包含的內(nèi)容更加復(fù)雜。
        計(jì)量經(jīng)濟(jì)學(xué)里的時(shí)間序列特指一元時(shí)間序列,也就是數(shù)據(jù)包含兩列,第一列是時(shí)間戳,第二列是觀察對象。這屬于比較經(jīng)典的時(shí)間序列。有時(shí)候你會注意到一些時(shí)間序列的模型或者算法,比如ARIMA,prophet等,都是針對這類時(shí)間序列
        商業(yè)里面的交易歷史信息也是一元時(shí)間序列。工業(yè)領(lǐng)域中,一些監(jiān)測數(shù)據(jù),比如天氣溫度,也是一元時(shí)間序列。但是時(shí)間序列不止有一元時(shí)間序列,當(dāng)同一個(gè)時(shí)間戳對應(yīng)的觀測對象不只一個(gè)時(shí),我們就有了多元時(shí)間序列。比如某個(gè)城市的空氣PM2.5的預(yù)測,我們可以通過PM2.5的歷史時(shí)間觀測值來預(yù)測。我們也可以通過當(dāng)天(或者近期)的其他觀測對象來預(yù)測,比如風(fēng)速,溫度,濕度等。
        多元時(shí)間序列在表現(xiàn)形式上就是數(shù)據(jù)包含多列(大于兩列),第一列是時(shí)間戳,后面的列都是觀察對象。當(dāng)時(shí)間序列是多元時(shí),很多經(jīng)典的機(jī)器學(xué)習(xí)模型可以施展拳腳,比如回歸模型,分類模型,這些模型都依賴于多元的特征。對于我們本文以及后續(xù)的分析中,我們不會將時(shí)間序列特指為一元時(shí)間序列。
        無論是一元時(shí)間序列的分析還是多元時(shí)間序列的分析,對于時(shí)間相關(guān)的預(yù)處理格外重要。今天我們就討論pandas在時(shí)間序列處理中應(yīng)用。






        導(dǎo)入數(shù)據(jù)


        這里我們采用美國能源消耗數(shù)據(jù)集進(jìn)行分析和討論,數(shù)據(jù)集可以在kaggle上下載,如果有問題,可以留言討論。該數(shù)據(jù)集包含了美國一家能源公司的長達(dá)數(shù)十年的能源消耗數(shù)據(jù),數(shù)據(jù)分辨率為小時(shí)。這里我們下載了兩個(gè)數(shù)據(jù)集進(jìn)行對比分析,PJME_hourly 和PJMW_hourly (分別對應(yīng)東區(qū)和西區(qū))。數(shù)據(jù)集默認(rèn)放在同目錄的data文件夾下。
        import seaborn as sns
        import pandas as pd
        import matplotlib.pyplot as plt
        pjme_file = 'data/PJME_hourly.csv'
        pjmw_file = 'data/PJMW_hourly.csv'

        通過pandas 的read_csv 來讀取數(shù)據(jù)。
        df_1 = pd.read_csv(pjme_file)
        df_2 = pd.read_csv(pjmw_file)

        print(df_1.info())
        print(df_2.info())
        數(shù)據(jù)集并不大,只有2.2MB左右。df_1 包含了145366 行數(shù)據(jù),df_2 包含了143206 行數(shù)據(jù),這里可以看到兩個(gè)數(shù)據(jù)集的樣本個(gè)數(shù)不同,如果我們需要對比兩個(gè)數(shù)據(jù)或者進(jìn)行比較分析,需要對數(shù)據(jù)進(jìn)行處理。

        RangeIndex: 145366 entries, 0 to 145365
        Data columns (total 2 columns):
        # Column Non-Null Count Dtype
        --- ------ -------------- -----
        0 Datetime 145366 non-null object
        1 PJME_MW 145366 non-null float64
        dtypes: float64(1), object(1)
        memory usage: 2.2+ MB
        None


        RangeIndex: 143206 entries, 0 to 143205
        Data columns (total 2 columns):
        # Column Non-Null Count Dtype
        --- ------ -------------- -----
        0 Datetime 143206 non-null object
        1 PJMW_MW 143206 non-null float64
        dtypes: float64(1), object(1)
        memory usage: 2.2+ MB
        None






        時(shí)間列(時(shí)間戳)的處理


        默認(rèn)讀取的時(shí)間列為字符形式,我們可以通過pandas的describe函數(shù)來進(jìn)行統(tǒng)計(jì),首先我們對原始時(shí)間列進(jìn)行統(tǒng)計(jì)。
        print(df_1['Datetime'].describe())
        結(jié)果如下表,我們可以看到unique 數(shù)值不同于count數(shù)值,這說明有重復(fù)的時(shí)間戳。更重要是,由于當(dāng)前的時(shí)間戳是字符串格式,無法進(jìn)行時(shí)間相關(guān)的統(tǒng)計(jì)。
        count 145366
        unique 145362
        top 2015-11-01 02:00:00
        freq 2
        我們通過to_datetime 將字符串轉(zhuǎn)換為pandas 的Timestamp 格式。這里需要指定字符串的格式。需要注意的是指定的時(shí)間格式需要完全匹配樣本的格式,而且要確保所有樣本的時(shí)間戳格式是一致的。
        df_1['Datetime'] = pd.to_datetime(df_1['Datetime'],format='%Y-%m-%d %H:%M:%S')
        print(df_1['Datetime'].describe())
        轉(zhuǎn)換后的時(shí)間列重新進(jìn)行統(tǒng)計(jì),結(jié)果如下:
        count 145366
        unique 145362
        top 2014-11-02 02:00:00
        freq 2
        first 2002-01-01 01:00:00
        last 2018-08-03 00:00:00
        Name: Datetime, dtype: object
        這里可以看到和時(shí)間相關(guān)的統(tǒng)計(jì)信息,比如開始的時(shí)間是2002-01-01 01:00:00,結(jié)束的時(shí)間是2018-08-03 00:00:00。
        如果我們顯示數(shù)據(jù)集的前5行(如下圖),就會發(fā)現(xiàn)第一行的時(shí)間并不等于上面的開始時(shí)間,這說明樣本并不是按照時(shí)間順序嚴(yán)格排序的,這對于時(shí)間序列分析來說是很大的坑:千萬不要輕信時(shí)間系列是默認(rèn)排序正確的?。?!
        Datetime PJME_MW
        0 2002-12-31 01:00:00 26498.0
        1 2002-12-31 02:00:00 25147.0
        2 2002-12-31 03:00:00 24574.0
        3 2002-12-31 04:00:00 24393.0
        4 2002-12-31 05:00:00 24860.0






        時(shí)間序列數(shù)據(jù)清理


        對于這個(gè)數(shù)據(jù)集來說,目前有兩處需要清理:
        1. 出現(xiàn)重復(fù)的時(shí)間戳及樣本,需要我們移除

        2. 樣本排序混亂

        對于一般的時(shí)間序列去重,我們可以通過保留第一個(gè)或者最后一個(gè)來進(jìn)行清理,這里我們采用求均值的方法,也就是對重復(fù)時(shí)間戳的樣本進(jìn)行求均值。理由如下:
        1. 其實(shí)該數(shù)據(jù)集是單純的重復(fù)類型,保留第一個(gè),最后一個(gè),或者求均值,結(jié)果是一致的

        2. 重要的是想展示一個(gè)pivot_table的用法。

        我們采用pivot_table,將時(shí)間列設(shè)為index,將觀察對象列設(shè)為value,aggfuc采用mean。這樣我們就消除了重復(fù)項(xiàng),確保時(shí)間列的每個(gè)值是唯一的。
        之后我們用sort_values進(jìn)行重新排序,并且設(shè)置時(shí)間列為index(索引)。
        df_1 = pd.pivot_table(data=df_1,values='PJME_MW',index='Datetime',aggfunc='mean').reset_index()
        df_1.sort_values(by='Datetime',inplace=True)
        df_1.set_index('Datetime',inplace=True)
        這樣我們就得到清理后的數(shù)據(jù),并且索引為時(shí)間戳。我們對df_2 進(jìn)行同樣的操作,然后進(jìn)行對比。






        時(shí)間序列可視化


        對于時(shí)間序列,最常用的plot就是趨勢圖。直接用pandas的plot函數(shù)即可,也可以用seaborn的lineplot。這里我們采用兩種方式分別畫出df_1和df_2的趨勢,通過對比我們也可以看到兩種plot方式的細(xì)微差別,尤其是對于y軸標(biāo)簽和圖例默認(rèn)值的處理上。
        # plot data
        fig,ax = plt.subplots(2,1)
        df_1.plot(ax =ax[0])
        sns.lineplot(data=df_1,x=df_1.index,y='PJME_MW',ax=ax[1])

        兩個(gè)時(shí)間序列的都呈現(xiàn)明顯的周期性,這是可以理解的。因?yàn)槟茉聪模ê碾娏浚┍緛砭褪呛苋祟惢顒?dòng)息息相關(guān),自然會和日歷的周期性有一定的吻合。
        當(dāng)我們把兩個(gè)df合并在一起時(shí),就會得到一個(gè)多元的時(shí)間序列,對于多元的時(shí)間序列,相關(guān)分析也是最常用的分析方式
        df = pd.concat([df_1,df_2],axis=1)
        sns.scatterplot(x='PJME_MW',y='PJMW_MW',data=df)

        從上圖來看,兩者還是存在明顯的正相關(guān),也就是東區(qū)耗電量增加時(shí),西區(qū)耗電量也增加。






        時(shí)間序列重采樣


        對于原始數(shù)據(jù)來說,時(shí)間分辨率是小時(shí)。有時(shí)候我們需要對數(shù)據(jù)的分辨率進(jìn)行調(diào)整,比如為了查看每月的耗電量的的情況。因此resample (重采樣)必不可少。
        # resample data
        day_df = df_1.resample(rule='D').mean()
        week_df = df_1.resample(rule='W').mean()
        month_df = df_1.resample(rule='M').mean()
        quarter_df = df_1.resample(rule='Q').mean()
        year_df = df_1.resample(rule='Y').mean()

        print(month_df.info())
        fig,ax = plt.subplots(2,1)
        sns.lineplot(data=df_1,x=df_1.index,y='PJME_MW',ax=ax[0])
        sns.lineplot(data=month_df,x=month_df.index,y='PJME_MW',ax=ax[1])
        常用的周期D,W,M,Q,Y分別代表每天,每周,每月,每季度和每年。我們對比每月重采樣的數(shù)據(jù)和原始數(shù)據(jù)。可以看到按月重采樣的曲線更加光滑,這是因?yàn)槊恐芎兔刻斓闹芷谛畔⒁呀?jīng)被過濾了。

        其實(shí)重采樣就是時(shí)間序列分解的”思想原型“,通過重采樣我們可以看到每個(gè)時(shí)間周期的”信號分量“。
        num_ax = 5
        fig,ax = plt.subplots(num_ax,1)
        #[ax[i].set_ylim(10000,22000) for i in range(num_ax)]
        day_df.plot(ax=ax[0])
        week_df.plot(ax=ax[1])
        month_df.plot(ax=ax[2])
        quarter_df.plot(ax=ax[3])
        year_df.plot(ax=ax[4])

        當(dāng)然了,對于每一種重采樣,后續(xù)采用的統(tǒng)計(jì)方式不一定是均值(mean),也可以選擇其他,比如sum(求和)來獲取每月耗電量之和。
        month_sum_df = df_1.resample(rule='M').sum()






        時(shí)間切片與索引

        DataFrame數(shù)據(jù)用時(shí)間戳作為索引,最大的好處是可以快速對樣本進(jìn)行索引和切片。進(jìn)行索引和切片時(shí),不一定需要完全匹配時(shí)間戳的格式,比如,你可以快速索引某個(gè)年度的所有樣本。

        print(day_df.loc['2014-02-12']) #獲得某一天的樣本
        print(day_df['2015']) #獲得某一年的額樣本
        print(day_df['2014-02-12':'2014-02-19']) #獲取某個(gè)時(shí)間段
        #print(day_df['2014-02-12']) !!!這是錯(cuò)誤示例
        print(month_df.asof('2014-02')) #獲取某一月
        這里需要注意的是:當(dāng)返回結(jié)果只有一個(gè)時(shí),無法采用[]進(jìn)行索引,比如

        #print(day_df['2014-02-12']) !!!這是錯(cuò)誤示例。因?yàn)閐ay_df的每一天只有一個(gè)樣本,此時(shí)只有iloc可以進(jìn)行索引。詳細(xì)的解釋參考如下。







        時(shí)間序列特征工程


        一元時(shí)間序列如果需要進(jìn)行回歸分析或者分類預(yù)測,必然需要通過特征工程擴(kuò)展特征數(shù)量,常用的特征工程有三類:
        1. 時(shí)間信息的提取

        2. 基于時(shí)間窗口的時(shí)域統(tǒng)計(jì)

        3. 基于時(shí)間窗口的頻域統(tǒng)計(jì)

        時(shí)間信息的提取指的是對時(shí)間列進(jìn)行特征工程,提取時(shí)間戳中和人類活動(dòng)日歷相關(guān)的時(shí)間信息,比如是否是月末,是否是周末,這是幾月等等。這里列出常用的時(shí)間信息的提取。
        # get more datetime attributes
        df_1['day']= df_1.index.day # means which day in this month
        df_1['dayofweek']= df_1.index.dayofweek
        df_1['dayofyear']= df_1.index.dayofyear
        df_1['days_in_month']= df_1.index.days_in_month # how many days in this month
        df_1['daysinmonth']= df_1.index.daysinmonth # same as days_in_month
        df_1['is_month_end']= df_1.index.is_month_end
        df_1['is_month_start']= df_1.index.is_month_start
        df_1['is_quarter_start']= df_1.index.is_quarter_start
        df_1['is_quarter_end']= df_1.index.is_quarter_end
        df_1['month']= df_1.index.month
        df_1['week']= df_1.index.week
        df_1['weekofyear']= df_1.index.weekofyear # same as week
        df_1['year']= df_1.index.year
        df_1['date']= df_1.index.date
        df_1['time']= df_1.index.time
        基于時(shí)間窗口的統(tǒng)計(jì)分析,可以分析時(shí)域分析和頻域分析。頻域分析主要用于高頻時(shí)間序列(信號)的分析,比如聲音也算是時(shí)間序列。我們先不做講解,這里主要說一下時(shí)域分析。
        時(shí)域分析很簡單,當(dāng)一個(gè)時(shí)間窗口確定后,意味著我們有一段有限長度的時(shí)間序列(有限的數(shù)據(jù)樣本),我們可以進(jìn)行統(tǒng)計(jì)分析,比如求均值,方差,眾數(shù),中位數(shù)等等。
        df_1['window_mean']= df_1['PJME_MW'].rolling(window=24,center=True).mean() # it will generate null
        print(df_1.head(24))
        這里采用rolling 函數(shù)進(jìn)行”滾動(dòng)窗口“,然后對每個(gè)滾動(dòng)窗口內(nèi)的所有樣本進(jìn)行求統(tǒng)計(jì)均值等操作。需要注意的是,采用滾動(dòng)窗口的方式,會出現(xiàn)某些樣本的窗口樣本不足指定數(shù)量,從而結(jié)果為NaN,實(shí)踐中需要進(jìn)行缺失值處理。
        PJME_MW window_mean
        Datetime
        2002-01-01 01:00:00 30393.0 NaN
        2002-01-01 02:00:00 29265.0 NaN
        2002-01-01 03:00:00 28357.0 NaN
        2002-01-01 04:00:00 27899.0 NaN
        2002-01-01 05:00:00 28057.0 NaN
        2002-01-01 06:00:00 28654.0 NaN
        2002-01-01 07:00:00 29308.0 NaN
        2002-01-01 08:00:00 29595.0 NaN
        2002-01-01 09:00:00 29943.0 NaN
        2002-01-01 10:00:00 30692.0 NaN
        2002-01-01 11:00:00 31395.0 NaN
        2002-01-01 12:00:00 31496.0 NaN
        2002-01-01 13:00:00 31031.0 31017.500000
        2002-01-01 14:00:00 30360.0 30922.833333
        2002-01-01 15:00:00 29798.0 30846.666667
        2002-01-01 16:00:00 29720.0 30802.666667
        2002-01-01 17:00:00 31271.0 30787.416667
        2002-01-01 18:00:00 35103.0 30801.916667
        2002-01-01 19:00:00 35732.0 30889.166667
        2002-01-01 20:00:00 35639.0 31114.875000
        2002-01-01 21:00:00 35285.0 31436.458333
        2002-01-01 22:00:00 34007.0 31743.916667
        2002-01-01 23:00:00 31857.0 32008.208333
        2002-01-02 00:00:00 29563.0 32231.666667






        總結(jié)


        本文涵蓋了時(shí)間序列分析的基本處理操作,包括時(shí)間戳的處理,排序,去重,索引與切片等。對于時(shí)間序列,可以進(jìn)行重采樣來滿足特定的分辨率需求,也可以以此查看基本的周期趨勢。一元時(shí)間序列可以通過滾動(dòng)窗口時(shí)域分析,時(shí)間列信息提取等方法進(jìn)行特征工程,為最終的機(jī)器學(xué)習(xí)模型做好準(zhǔn)備。
        掃描二維碼添加好友↓

        推薦閱讀

        (點(diǎn)擊標(biāo)題可跳轉(zhuǎn)閱讀)

        大數(shù)據(jù)面前,統(tǒng)計(jì)學(xué)的價(jià)值在哪里?

        【AI行業(yè)鄙視鏈大曝光】

        機(jī)器學(xué)習(xí)基礎(chǔ):可視化方式理解決策樹剪枝

        推薦一款科研必備的Python數(shù)據(jù)可視化神器

        三連支持,混臉熟,進(jìn)福利群↓↓

        瀏覽 439
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 互换玩着娇妻3pChinese | 亚洲AV秘 无码 | 成人欧美一区二区三区黑人3p | 尤物视频yw.193.c㎝ | 日韩成人视频在线观看 |