时间序列分析

作者: Alex_杨策 | 来源:发表于2018-11-01 13:38 被阅读0次

    时间序列简介

    时间序列分析是数据分析过程中,尤其是在金融数据分析过程中会经常遇到的。时间序列,就是以时间排序的一组随机变量。例如国家统计局每年或每月定期发布的 GDP 或 CPI 指数;24 小时内某一股票、基金、指数的数值变化等,都是时间序列。

    时间序列处理

    当拿到一些时间序列的原始数据时,可能会遇到一些情况:
    1.某一段时间缺失,需要填充。
    2.时间序列错位,需要对齐。
    3.数据表a和数据表b所采用的时间间隔不一致,需要重新采样。
    ......
    面对这些问题,就需要通过一些手段来获得最终想要的数据。

    Timestamp时间戳

    时间戳,即代表一个时间时刻。可以直接用pd.Timestamp()来创建时间戳。

    In [1]: import pandas as pd
    
    In [2]: pd.Timestamp("2018-11-1")
    Out[2]: Timestamp('2018-11-01 00:00:00')
    
    In [3]: pd.Timestamp(2018,11,1)
    Out[3]: Timestamp('2018-11-01 00:00:00')
    
    In [4]: pd.Timestamp("2018-11-1 11:31:00")
    Out[4]: Timestamp('2018-11-01 11:31:00')
    

    时间戳索引

    单个时间戳为Timestamp数据,当时间戳以列表形式存在时,Pandas将强制转换为DatetimeIndex。这时就不能再使用pd.Timestamp()来创建时间戳了,而是pd.to_datetime()来创建:

    In [5]: pd.to_datetime(["2018-11-1", "2018-11-2", "2018-11-3"])
    Out[5]: DatetimeIndex(['2018-11-01', '2018-11-02', '2018-11-03'], dtype='datetime64[ns]', freq=None)
    

    pd.to_datetime()不仅仅可用来创建DatetimeIndex,还可以将时间戳序列格式进行转换等操作。常见的时间戳书写样式都可以通过pd.to_datetime()规范化。

    In [7]: pd.to_datetime(['Jul 10, 2018', '2018-12-12', None])
    Out[7]: DatetimeIndex(['2018-07-10', '2018-12-12', 'NaT'], dtype='datetime64[ns]', freq=None)
    
    In [8]: pd.to_datetime(['2017/10/11', '2018/11/1'])
    Out[8]: DatetimeIndex(['2017-10-11', '2018-11-01'], dtype='datetime64[ns]', freq=None)
    

    对于欧洲时区普遍采用的书写样式,可以通过dayfirst=True参数进行修正:

    In [9]: pd.to_datetime('1-10-2018')
    Out[9]: Timestamp('2018-01-10 00:00:00')
    
    In [10]: pd.to_datetime('1-10-2018', dayfirst=True)
    Out[10]: Timestamp('2018-10-01 00:00:00')
    

    Pandas熟悉的Series和DataFrame格式的字符串,也可以直接通过to_datetime转换:

    In [11]: pd.to_datetime(pd.Series(['2018-11-1', '2018-11-2', '2018-11-3']))
    Out[11]:
    0   2018-11-01
    1   2018-11-02
    2   2018-11-03
    dtype: datetime64[ns]
    
    In [12]: pd.to_datetime(['2018-11-1', '2018-11-2', '2018-11-3'])
    Out[12]: DatetimeIndex(['2018-11-01', '2018-11-02', '2018-11-03'], dtype='datetime64[ns]', freq=None)
    
    In [13]: pd.to_datetime(pd.DataFrame({'year': [2017, 2017], 'month': [1, 2],
        ...:  'day': [3, 4], 'hour': [5, 6]}))
    Out[13]:
    0   2017-01-03 05:00:00
    1   2017-02-04 06:00:00
    dtype: datetime64[ns]
    

    其中:

    • pd.to_datetime(Series/DataFrame)返回的是Series。
    • pd.to_datetime(List)返回的是DatetimeIndex。

    如果要转换如上所示的DataFrame,必须存在的列名有year,month,day。另外 hour, minute, second, millisecond, microsecond, nanosecond可选。

    当使用pd.to_datetime() 转换数据时,很容易遇到无效数据。有一些任务对无效数据非常苛刻,所以报错让我们找到这些无效数据是不错的方法。当然,也有一些任务不在乎零星的无效数据,这时候就可以选择忽略。

    # 遇到无效数据报错
    In [15]: pd.to_datetime(['2018-11-1', 'invalid'], errors='raise')
    ValueError: ('Unknown string format:', 'invalid')
    
    # 忽略无效数据
    In [17]: pd.to_datetime(['2018-11-1', 'invalid'], errors='ignore')
    Out[17]: array(['2018-11-1', 'invalid'], dtype=object)
    
    # 将无效数据显示为NaT
    In [18]: pd.to_datetime(['2018-11-1', 'invalid'], errors='coerce')
    Out[18]: DatetimeIndex(['2018-11-01', 'NaT'], dtype='datetime64[ns]', freq=None)
    

    生成DatetimeIndex的另一个重要方法pandas.data_range。
    可以通过指定一个规则,让pandas.data_range生成有序的DatetimeIndex.
    pandas.data_range带有的默认参数如下:

    pandas.date_range(start=None, end=None, periods=None, freq=’D’, tz=None, normalize=False,
    name=None, closed=None, **kwargs)

    常用参数的含义如下:

    • start= : 设置起始时间
    • end= : 设置截至时间
    • periods= :设置时间区间,若None则需要单独设置起止和截至时间。
    • freq= : 设置间隔周期。
    • 设置时区。

    其中,freq= 参数是非常关键的参数,可以设置的周期有:

    • freq='s' :秒
    • freq='min' :分钟
    • freq='H' :小时
    • freq='D' :天
    • freq='w' :周
    • freq='m' :月
    • freq='BM' :每个月最后一个工作日
    • freq='W' : 每周的星期日
    # 从2018-11-1 到 2018-11-2,以小时间隔
    In [19]: pd.date_range('2018-11-1', '2018-11-2', freq='H')
    Out[19]:
    DatetimeIndex(['2018-11-01 00:00:00', '2018-11-01 01:00:00',
                   '2018-11-01 02:00:00', '2018-11-01 03:00:00',
                   '2018-11-01 04:00:00', '2018-11-01 05:00:00',
                   '2018-11-01 06:00:00', '2018-11-01 07:00:00',
                   '2018-11-01 08:00:00', '2018-11-01 09:00:00',
                   '2018-11-01 10:00:00', '2018-11-01 11:00:00',
                   '2018-11-01 12:00:00', '2018-11-01 13:00:00',
                   '2018-11-01 14:00:00', '2018-11-01 15:00:00',
                   '2018-11-01 16:00:00', '2018-11-01 17:00:00',
                   '2018-11-01 18:00:00', '2018-11-01 19:00:00',
                   '2018-11-01 20:00:00', '2018-11-01 21:00:00',
                   '2018-11-01 22:00:00', '2018-11-01 23:00:00',
                   '2018-11-02 00:00:00'],
                  dtype='datetime64[ns]', freq='H')
    
    # 从2018-11-1 开始,以1s为间隔,向后推 10 次
    In [20]: pd.date_range('2018-11-1', periods=10, freq='s')
    Out[20]:
    DatetimeIndex(['2018-11-01 00:00:00', '2018-11-01 00:00:01',
                   '2018-11-01 00:00:02', '2018-11-01 00:00:03',
                   '2018-11-01 00:00:04', '2018-11-01 00:00:05',
                   '2018-11-01 00:00:06', '2018-11-01 00:00:07',
                   '2018-11-01 00:00:08', '2018-11-01 00:00:09'],
                  dtype='datetime64[ns]', freq='S')
    
    # 从2018-11-1 开始, 以 1H20min为间隔, 向后推 10 次
    In [21]: pd.date_range('11/1/2018', periods=10, freq='1H20min')
    Out[21]:
    DatetimeIndex(['2018-11-01 00:00:00', '2018-11-01 01:20:00',
                   '2018-11-01 02:40:00', '2018-11-01 04:00:00',
                   '2018-11-01 05:20:00', '2018-11-01 06:40:00',
                   '2018-11-01 08:00:00', '2018-11-01 09:20:00',
                   '2018-11-01 10:40:00', '2018-11-01 12:00:00'],
                  dtype='datetime64[ns]', freq='80T')
    

    除了生成DatetimeIndex,还可以对已有的DatetimeIndex进行操作。这些操作包括选择,切片等。类似于对Series的操作。

    In [23]: x = pd.date_range('2018-11-1', periods=10, freq='1D1H')
    
    In [24]: x
    Out[24]:
    DatetimeIndex(['2018-11-01 00:00:00', '2018-11-02 01:00:00',
                   '2018-11-03 02:00:00', '2018-11-04 03:00:00',
                   '2018-11-05 04:00:00', '2018-11-06 05:00:00',
                   '2018-11-07 06:00:00', '2018-11-08 07:00:00',
                   '2018-11-09 08:00:00', '2018-11-10 09:00:00'],
                  dtype='datetime64[ns]', freq='25H')
    
    # 选取索引为1的时间戳
    In [27]: x[1]
    Out[27]: Timestamp('2018-11-02 01:00:00', freq='25H')
    
    # 对索引从0到4的时间进行切片
    In [28]: x[:5]
    Out[28]:
    DatetimeIndex(['2018-11-01 00:00:00', '2018-11-02 01:00:00',
                   '2018-11-03 02:00:00', '2018-11-04 03:00:00',
                   '2018-11-05 04:00:00'],
                  dtype='datetime64[ns]', freq='25H')
    

    DateOffset对象

    上面,使用freq='1D1H'参数,可以生成间隔1天+1小时的时间戳索引。而在时间序列处理中,还常用到一种叫做DateOffset对象,可以对时间戳索引进行更加灵活的变化。DateOffset对象主要作用有:

    1. 可以让时间索引增加或减少一定时间段。
    2. 可以让时间索引乘以一个整数。
    3. 可以让时间索引向前或后移动到下一个或上一个特定的偏移日期。
    In [29]: from pandas import offsets
    
    In [30]: a = pd.date_range('2018-11-1', periods=10, freq='1D1H')
    
    In [31]: a
    Out[31]:
    DatetimeIndex(['2018-11-01 00:00:00', '2018-11-02 01:00:00',
                   '2018-11-03 02:00:00', '2018-11-04 03:00:00',
                   '2018-11-05 04:00:00', '2018-11-06 05:00:00',
                   '2018-11-07 06:00:00', '2018-11-08 07:00:00',
                   '2018-11-09 08:00:00', '2018-11-10 09:00:00'],
                  dtype='datetime64[ns]', freq='25H')
    
    # 使用DateOffset 对象让a依次增加1个月+2天+3小时
    In [33]: a + offsets.DateOffset(months=1, days=2, hours=3)
    Out[33]:
    DatetimeIndex(['2018-12-03 03:00:00', '2018-12-04 04:00:00',
                   '2018-12-05 05:00:00', '2018-12-06 06:00:00',
                   '2018-12-07 07:00:00', '2018-12-08 08:00:00',
                   '2018-12-09 09:00:00', '2018-12-10 10:00:00',
                   '2018-12-11 11:00:00', '2018-12-12 12:00:00'],
                  dtype='datetime64[ns]', freq='25H')
    
    # 使用DateOffset对象让a向后偏移2周
    In [34]: a + 2*offsets.Week()
    Out[34]:
    DatetimeIndex(['2018-11-15 00:00:00', '2018-11-16 01:00:00',
                   '2018-11-17 02:00:00', '2018-11-18 03:00:00',
                   '2018-11-19 04:00:00', '2018-11-20 05:00:00',
                   '2018-11-21 06:00:00', '2018-11-22 07:00:00',
                   '2018-11-23 08:00:00', '2018-11-24 09:00:00'],
                  dtype='datetime64[ns]', freq='25H')
    

    Period 时间间隔

    Pandas 中还存在 Period 时间间隔和 PeriodIndex 时间间隔索引对象。它们用来定义一定时间跨度。

    # 一年跨度
    In [35]: pd.Period('2018')
    Out[35]: Period('2018', 'A-DEC')
    
    # 一个月跨度
    In [36]: pd.Period('2018-11')
    Out[36]: Period('2018-11', 'M')
    
    # 一天跨度
    In [37]: pd.Period('2018-11-1')
    Out[37]: Period('2018-11-01', 'D')
    
    # 一小时跨度
    In [38]: pd.Period('2018-11-1 13')
    Out[38]: Period('2018-11-01 13:00', 'H')
    
    # 一分钟跨度
    In [39]: pd.Period('2018-11-1 13:22')
    Out[39]: Period('2018-11-01 13:22', 'T')
    
    # 一秒跨度
    In [40]: pd.Period('2018-11-1 13:22:12')
    Out[40]: Period('2018-11-01 13:22:12', 'S')
    

    同样可以通过pandas.period_range()方法来生成序列:

    In [42]: pd.period_range('2017-11', '2018-11', freq='M')
    Out[42]:
    PeriodIndex(['2017-11', '2017-12', '2018-01', '2018-02', '2018-03', '2018-04',
                 '2018-05', '2018-06', '2018-07', '2018-08', '2018-09', '2018-10',
                 '2018-11'],
                dtype='period[M]', freq='M')
    

    DatetimeIndex 的dtype 类型为 datetime64[ns],而 PeriodIndex 的 dtype 类型为 period[M]。另外,对于 Timestamp和 Period 的区别,在单独拿出来看一下:

    In [43]: pd.Period('2018-11-1')
    Out[43]: Period('2018-11-01', 'D')
    
    In [44]: pd.Timestamp('2018-11-1')
    Out[44]: Timestamp('2018-11-01 00:00:00')
    

    可以看到,上面代表是2017-01-01这一天,而下面仅代表 2017-01-01 00:00:00 这一时刻。

    时序数据检索

    DatetimeIndex 之所以称之为时间戳索引,当然是它的主要用途是作为 Series 或者 DataFrame 的索引。下面,就随机生成一些数据,看一看如何对时间序列数据进行操作。

    In [1]: import numpy as np
    In [2]: import pandas as pd
    
    # 生成时间索引
    In [3]: i = pd.date_range('2018-11-1', periods=20, freq='M')
    
    # 生成随机数据并添加时间作为索引
    In [4]: data = pd.Series(np.random.randn(len(i)), index=i)
    
    # 查看数据
    In [5]: data
    Out[5]:
    2018-11-30   -1.040781
    2018-12-31   -2.396724
    2019-01-31    0.370134
    2019-02-28   -1.655618
    2019-03-31   -0.755367
    2019-04-30   -1.465855
    2019-05-31   -1.212847
    2019-06-30   -0.816448
    2019-07-31    0.360213
    2019-08-31    0.100798
    2019-09-30    1.004533
    2019-10-31    0.488605
    2019-11-30   -2.452875
    2019-12-31   -1.495978
    2020-01-31    0.535245
    2020-02-29   -0.480371
    2020-03-31   -0.536331
    2020-04-30    0.640610
    2020-05-31    0.271148
    2020-06-30    0.522567
    Freq: M, dtype: float64
    

    上面就生成了一个以时间为索引的 Series 序列。这就回到了对 Pandas 中 Series 和 DataFrame 类型数据操作的问题。下面演示一些操作:

    # 检索2018年的所有数据
    In [6]: data['2018']
    Out[6]:
    2018-11-30   -1.040781
    2018-12-31   -2.396724
    Freq: M, dtype: float64
    
    # 检索2019年7月到2020年3月之间的所有数据
    In [7]: data['2019-07':'2020-03']
    Out[7]:
    2019-07-31    0.360213
    2019-08-31    0.100798
    2019-09-30    1.004533
    2019-10-31    0.488605
    2019-11-30   -2.452875
    2019-12-31   -1.495978
    2020-01-31    0.535245
    2020-02-29   -0.480371
    2020-03-31   -0.536331
    Freq: M, dtype: float64
    
    # 使用loc方法检索2019年1月的所有数据
    In [8]: data.loc['2019-01']
    Out[8]:
    2019-01-31    0.370134
    Freq: M, dtype: float64
    
    # 使用truncate方法检索2019-3-1 到 2020-4-2 期间的数据
    In [9]: data.truncate(before='2019-3-1', after='2020-4-2')
    Out[9]:
    2019-03-31   -0.755367
    2019-04-30   -1.465855
    2019-05-31   -1.212847
    2019-06-30   -0.816448
    2019-07-31    0.360213
    2019-08-31    0.100798
    2019-09-30    1.004533
    2019-10-31    0.488605
    2019-11-30   -2.452875
    2019-12-31   -1.495978
    2020-01-31    0.535245
    2020-02-29   -0.480371
    2020-03-31   -0.536331
    Freq: M, dtype: float64
    

    时间数据偏移

    这里可能会用到 Shifting 方法,将时间索引进行整体偏移。

    # 生成时间索引
    In [10]: i = pd.date_range('2017-1-1', periods=5, freq='M')
    
    # 生成随机数据并添加时间作为索引
    In [11]: data = pd.Series(np.random.randn(len(i)), index=i)
    
    # 查看数据
    In [12]: data
    Out[12]:
    2017-01-31   -0.440074
    2017-02-28    0.706395
    2017-03-31    0.823844
    2017-04-30    0.703313
    2017-05-31    0.920151
    Freq: M, dtype: float64
    
    # 将索引向前移位3个单位,也就是数据向后位移3个单位,缺失数据Pandas会用NaN填充
    In [13]: data.shift(3)
    Out[13]:
    2017-01-31         NaN
    2017-02-28         NaN
    2017-03-31         NaN
    2017-04-30   -0.440074
    2017-05-31    0.706395
    Freq: M, dtype: float64
    
    # 将索引向后位移3个单位,也就是数据向前位移3个单位
    In [14]: data.shift(-3)
    Out[14]:
    2017-01-31    0.703313
    2017-02-28    0.920151
    2017-03-31         NaN
    2017-04-30         NaN
    2017-05-31         NaN
    Freq: M, dtype: float64
    
    # 将索引的时间向后移动3天
    In [15]: data.shift(3, freq='D')
    Out[15]:
    2017-02-03   -0.440074
    2017-03-03    0.706395
    2017-04-03    0.823844
    2017-05-03    0.703313
    2017-06-03    0.920151
    dtype: float64
    

    时间数据重采样

    除了 Shifting 方法,重采样 Resample 也会经常用到。Resample 可以提升或降低一个时间索引序列的频率,大有用处。例如:当时间序列数据量非常大时,我们可以通过低频率采样的方法得到规模较小到时间覆盖依然较为全面的新数据集。另外,对于多个不同频率的数据集需要数据对齐时,重采样可以十分重要的手段。

    In [16]: i = pd.date_range('2017-01-01', periods=20, freq='D')
    
    In [17]: data = pd.Series(np.random.randn(len(i)), index=i)
    
    In [18]: data
    Out[18]:
    2017-01-01    1.096656
    2017-01-02   -2.404326
    2017-01-03   -0.883177
    2017-01-04    0.554299
    2017-01-05   -1.004089
    2017-01-06   -0.014365
    2017-01-07   -0.514893
    2017-01-08   -0.049173
    2017-01-09    1.633568
    2017-01-10    2.076252
    2017-01-11    0.132104
    2017-01-12   -1.011756
    2017-01-13   -1.330824
    2017-01-14    1.626463
    2017-01-15   -0.339399
    2017-01-16   -0.622435
    2017-01-17   -0.201180
    2017-01-18   -1.193216
    2017-01-19   -1.522457
    2017-01-20    1.217058
    Freq: D, dtype: float64
    
    # 按照2天进行降采样,并对2天对应的数据求和作为新数据
    In [19]: data.resample('2D').sum()
    Out[19]:
    2017-01-01   -1.307670
    2017-01-03   -0.328878
    2017-01-05   -1.018454
    2017-01-07   -0.564066
    2017-01-09    3.709820
    2017-01-11   -0.879652
    2017-01-13    0.295638
    2017-01-15   -0.961834
    2017-01-17   -1.394395
    2017-01-19   -0.305399
    dtype: float64
    
    # 按照2天进行降采样,并对2天对应的数据求平均值作为新数据
    In [20]: data.resample('2D').mean()
    Out[20]:
    2017-01-01   -0.653835
    2017-01-03   -0.164439
    2017-01-05   -0.509227
    2017-01-07   -0.282033
    2017-01-09    1.854910
    2017-01-11   -0.439826
    2017-01-13    0.147819
    2017-01-15   -0.480917
    2017-01-17   -0.697198
    2017-01-19   -0.152700
    dtype: float64
    
    # 按照2天进行降采样,并对2天对应的数据求最大值作为新数据
    In [21]: data.resample('2D').max()
    Out[21]:
    2017-01-01    1.096656
    2017-01-03    0.554299
    2017-01-05   -0.014365
    2017-01-07   -0.049173
    2017-01-09    2.076252
    2017-01-11    0.132104
    2017-01-13    1.626463
    2017-01-15   -0.339399
    2017-01-17   -0.201180
    2017-01-19    1.217058
    dtype: float64
    
    # 按照2天进行降采样,并将2天对应的数据的原值,最大值,最小值,以及临近值列出
    In [22]: data.resample('2D').ohlc()
    Out[22]:
                    open      high       low     close
    2017-01-01  1.096656  1.096656 -2.404326 -2.404326
    2017-01-03 -0.883177  0.554299 -0.883177  0.554299
    2017-01-05 -1.004089 -0.014365 -1.004089 -0.014365
    2017-01-07 -0.514893 -0.049173 -0.514893 -0.049173
    2017-01-09  1.633568  2.076252  1.633568  2.076252
    2017-01-11  0.132104  0.132104 -1.011756 -1.011756
    2017-01-13 -1.330824  1.626463 -1.330824  1.626463
    2017-01-15 -0.339399 -0.339399 -0.622435 -0.622435
    2017-01-17 -0.201180 -0.201180 -1.193216 -1.193216
    2017-01-19 -1.522457  1.217058 -1.522457  1.217058
    

    采样操作起来只是需要注意采样后对新数据不同的处理方法。上面介绍的是降频采样。也可以升频采样。

    # 时间频率从天提升到小时,并使用相同的数据对新增加行填充
    In [23]: data.resample('H').ffill()
    Out[23]:
    2017-01-01 00:00:00    1.096656
    2017-01-01 01:00:00    1.096656
    2017-01-01 02:00:00    1.096656
    2017-01-01 03:00:00    1.096656
                           ...
    2017-01-19 20:00:00   -1.522457
    2017-01-19 21:00:00   -1.522457
    2017-01-19 22:00:00   -1.522457
    2017-01-19 23:00:00   -1.522457
    2017-01-20 00:00:00    1.217058
    Freq: H, Length: 457, dtype: float64
    
    # 时间频率从天提升到小时,不对新增加行填充
    In [24]: data.resample('H').asfreq()
    Out[24]:
    2017-01-01 00:00:00    1.096656
    2017-01-01 01:00:00         NaN
    2017-01-01 02:00:00         NaN
    2017-01-01 03:00:00         NaN
    2017-01-01 04:00:00         NaN
                           ...
    2017-01-19 20:00:00         NaN
    2017-01-19 21:00:00         NaN
    2017-01-19 22:00:00         NaN
    2017-01-19 23:00:00         NaN
    2017-01-20 00:00:00    1.217058
    Freq: H, Length: 457, dtype: float64
    
    # 时间频率从天提升到小时,只对新增加前3行填充
    In [25]: data.resample('H').ffill(limit=3)
    Out[25]:
    2017-01-01 00:00:00    1.096656
    2017-01-01 01:00:00    1.096656
    2017-01-01 02:00:00    1.096656
    2017-01-01 03:00:00    1.096656
    2017-01-01 04:00:00         NaN
                           ...
    2017-01-19 20:00:00         NaN
    2017-01-19 21:00:00         NaN
    2017-01-19 22:00:00         NaN
    2017-01-19 23:00:00         NaN
    2017-01-20 00:00:00    1.217058
    Freq: H, Length: 457, dtype: float64
    

    相关文章

      网友评论

        本文标题:时间序列分析

        本文链接:https://www.haomeiwen.com/subject/tiwtxqtx.html