当前位置:技术分享 > 技术参考 > 正文

Pandas必备技能之“时间序列数据处理”2019-06-13 10:40:21 | 编辑:hely | 查看: | 评论:0

时间序列数据Time Series Data是在不同时间上收集到的数据,这类数据是按时间顺序收集到的,用于所描述现象随时间变化的情况。

时间序列数据Time Series Data是在不同时间上收集到的数据,这类数据是按时间顺序收集到的,用于所描述现象随时间变化的情况。

时间序列分析广泛应用于计量经济学模型中,通过寻找历史数据中某一现象的发展规律,对未来进行预测。

时间序列数据作为时间序列分析的基础,学会如何对它进行巧妙地处理是非常必要的,Python中的Pandas库为我们提供了强大的时间序列数据处理的方法,本文会介绍其中常用的几个。

【工具】

  • Python 3
  • Tushare

01、时间格式转换

有时候,我们获得的原始数据并不是按照时间类型索引进行排列的,需要先进行时间格式的转换,为后续的操作和分析做准备。

这里介绍两种方法。第一种方法是用pandas.read_csv导入文件的时候,通过设置参数parse_dates和index_col,直接对日期列进行转换,并将其设置为索引。关于参数的详细解释,请查看文档【1】。

如下示例中,在没有设置参数之前,可以观察到数据集中的索引是数字0-208,'date'列的数据类型也不是日期。


  1. In [8]: data = pd.read_csv('unemployment.csv')
  2. In [9]: data.info()
  3. <class 'pandas.core.frame.DataFrame'>
  4. RangeIndex: 209 entries, 0 to 208
  5. Data columns (total 2 columns):
  6. date 209 non-null object
  7. UNRATE 209 non-null float64
  8. dtypes: float64(1), object(1)
  9. memory usage: 3.3+ KB

设置参数parse_dates = ['date'] ,将数据类型转换成日期,再设置 index_col = 'date',将这一列用作索引,结果如下。


  1. In [11]: data = pd.read_csv('unemployment.csv', parse_dates=['date'], index_col='date')
  2.  
  3. In [12]: data.info()
  4. <class 'pandas.core.frame.DataFrame'>
  5. DatetimeIndex: 209 entries, 2000-01-01 to 2017-05-01
  6. Data columns (total 1 columns):
  7. UNRATE 209 non-null float64
  8. dtypes: float64(1)
  9. memory usage: 13.3 KB

这时,索引变成了日期'20000101'-'2017-05-01',数据类型是datetime。

第二种方法是在已经导入数据的情况下,用pd.to_datetime()【2】将列转换成日期类型,再用 df.set_index()【3】将其设置为索引,完成转换。

以tushare.pro上面的日线行情数据为例,我们把'trade_date'列转换成日期类型,并设置成索引。


  1. import tushare as ts
  2. import pandas as pd
  3.  
  4. pd.set_option('expand_frame_repr', False) # 列太多时不换行
  5. pro = ts.pro_api()
  6.  
  7. df = pro.daily(ts_code='000001.SZ', start_date='20180701', end_date='20180718')
  8.  
  9. df.info()
  10.  
  11. <class 'pandas.core.frame.DataFrame'>
  12. RangeIndex: 13 entries, 0 to 12
  13. Data columns (total 11 columns):
  14. ts_code 13 non-null object
  15. trade_date 13 non-null object
  16. open 13 non-null float64
  17. high 13 non-null float64
  18. low 13 non-null float64
  19. close 13 non-null float64
  20. pre_close 13 non-null float64
  21. change 13 non-null float64
  22. pct_chg 13 non-null float64
  23. vol 13 non-null float64
  24. amount 13 non-null float64
  25. dtypes: float64(9), object(2)
  26. memory usage: 1.2+ KB
  27. None
  28.  
  29.  
  30. df['trade_date'] = pd.to_datetime(df['trade_date'])
  31. df.set_index('trade_date', inplace=True)
  32. df.sort_values('trade_date', ascending=True, inplace=True) # 升序排列
  33.  
  34. df.info()
  35.  
  36. <class 'pandas.core.frame.DataFrame'>
  37. DatetimeIndex: 13 entries, 2018-07-02 to 2018-07-18
  38. Data columns (total 10 columns):
  39. ts_code 13 non-null object
  40. open 13 non-null float64
  41. high 13 non-null float64
  42. low 13 non-null float64
  43. close 13 non-null float64
  44. pre_close 13 non-null float64
  45. change 13 non-null float64
  46. pct_chg 13 non-null float64
  47. vol 13 non-null float64
  48. amount 13 non-null float64
  49. dtypes: float64(9), object(1)
  50. memory usage: 1.1+ KB

打印出前5行,效果如下。


  1. df.head()
  2. Out[15]:
  3. ts_code open high low close pre_close change pct_chg vol amount
  4. trade_date
  5. 2018-07-02 000001.SZ 9.05 9.05 8.55 8.61 9.09 -0.48 -5.28 1315520.13 1158545.868
  6. 2018-07-03 000001.SZ 8.69 8.70 8.45 8.67 8.61 0.06 0.70 1274838.57 1096657.033
  7. 2018-07-04 000001.SZ 8.63 8.75 8.61 8.61 8.67 -0.06 -0.69 711153.37 617278.559
  8. 2018-07-05 000001.SZ 8.62 8.73 8.55 8.60 8.61 -0.01 -0.12 835768.77 722169.579
  9. 2018-07-06 000001.SZ 8.61 8.78 8.45 8.66 8.60 0.06 0.70 988282.69 852071.526

02、时间周期转换

在完成时间格式转换之后,我们就可以进行后续的日期操作了。下面介绍一下如何对时间序列数据进行重采样resampling。

重采样指的是将时间序列从⼀个频率转换到另⼀个频率的处理过程。将⾼频率数据聚合到低频率称为降采样downsampling,如将股票的日线数据转换成周线数据,⽽将低频率数据转换到⾼频率则称为升采样upsampling,如将股票的周线数据转换成日线数据。

降采样:以日线数据转换周线数据为例。继续使用上面的tushare.pro日线行情数据,选出特定的几列。


  1. df = df[['ts_code', 'open', 'high', 'low', 'close', 'vol']] # 单位:成交量 (手)
  2.  
  3.  
  4. ts_code open high low close vol
  5. trade_date
  6. 2018-07-02 000001.SZ 9.05 9.05 8.55 8.61 1315520.13
  7. 2018-07-03 000001.SZ 8.69 8.70 8.45 8.67 1274838.57
  8. 2018-07-04 000001.SZ 8.63 8.75 8.61 8.61 711153.37
  9. 2018-07-05 000001.SZ 8.62 8.73 8.55 8.60 835768.77
  10. 2018-07-06 000001.SZ 8.61 8.78 8.45 8.66 988282.69
  11. 2018-07-09 000001.SZ 8.69 9.03 8.68 9.03 1409954.60
  12. 2018-07-10 000001.SZ 9.02 9.02 8.89 8.98 896862.02
  13. 2018-07-11 000001.SZ 8.76 8.83 8.68 8.78 851296.70
  14. 2018-07-12 000001.SZ 8.60 8.97 8.58 8.88 1140492.31
  15. 2018-07-13 000001.SZ 8.92 8.94 8.82 8.88 603378.21
  16. 2018-07-16 000001.SZ 8.85 8.90 8.69 8.73 689845.58
  17. 2018-07-17 000001.SZ 8.74 8.75 8.66 8.72 375356.33
  18. 2018-07-18 000001.SZ 8.75 8.85 8.69 8.70 525152.77

为了方便大家观察,把这段时间的日历附在下面,'2018-07-02'正好是星期一。

转换的思路是这样的,以日历中的周进行聚合,如'20180702'-'20180708',取该周期内,日线开盘价的第一个值作为周开盘价,日线最高价的最大值作为周最高价,日线最低价的最小值作为周最低价,日线收盘价的最后一个值作为周最收盘价,日线最高价的最大值作为周最高价,日线成交量的求和作为周成交量(手),如下图黄色方框所示。

我们可以通过.resample()【4】方法实现上述操作,对DataFrame和Series都适用。其中,参数rule设置需要转换成的频率,'1W'是一周。

具体转换的代码如下,日期默认为本周的星期日,如果周期内数据不全,如'20180722'这周只有3行数据,也会按照上述方法进行转换。


  1. freq = '1W'
  2. df_weekly = df[['open']].resample(rule=freq).first()
  3. df_weekly['high'] = df['high'].resample(rule=freq).max()
  4. df_weekly['low'] = df['low'].resample(rule=freq).min()
  5. df_weekly['close'] = df['close'].resample(rule=freq).last()
  6. df_weekly['vol'] = df['vol'].resample(rule=freq).sum()
  7.  
  8. df_weekly
  9.  
  10. Out[33]:
  11. open high low close vol
  12. trade_date
  13. 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
  14. 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
  15. 2018-07-22 8.85 8.90 8.66 8.70 1590354.68

升采样:以周线数据转换日线数据为例。继续使用上面刚刚转换好的周线数据,我们再试着把它转换成日线数据。先通过.resample('D').asfreq()【5】方法,将周线数据的频率转换成日线,效果如下。


  1. df_daily = df_weekly.resample('D').asfreq()
  2. print(df_daily)
  3.  
  4. Out[52]:
  5. open high low close vol
  6. trade_date
  7. 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
  8. 2018-07-09 NaN NaN NaN NaN NaN
  9. 2018-07-10 NaN NaN NaN NaN NaN
  10. 2018-07-11 NaN NaN NaN NaN NaN
  11. 2018-07-12 NaN NaN NaN NaN NaN
  12. 2018-07-13 NaN NaN NaN NaN NaN
  13. 2018-07-14 NaN NaN NaN NaN NaN
  14. 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
  15. 2018-07-16 NaN NaN NaN NaN NaN
  16. 2018-07-17 NaN NaN NaN&nnbsp; NaN NaN
  17. 2018-07-18 NaN NaN NaN NaN NaN
  18. 2018-07-19 NaN NaN NaN NaN NaN
  19. 2018-07-20 NaN NaN NaN NaN NaN
  20. 2018-07-21 NaN NaN NaN NaN NaN
  21. 2018-07-22 8.85 8.90 8.66 8.70 1590354.68

结果中出现了很多空值,需要我们按照一定的方法进行填充,可以通过添加.ffill()或者.bfill()实现。

其中,.ffill()代表用前值进行填充,也就是用前面的非空值对后面的NaN值进行填充,如'20180709'-20180714' 的NaN值都等于'20180708'这一行的非空值,效果如下。


  1. df_daily = df_weekly.resample('D').ffill()
  2. df_daily
  3.  
  4. Out[54]:
  5. open high low close vol
  6. trade_date
  7. 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
  8. 2018-07-09 9.05 9.05 8.45 8.66 5125563.53
  9. 2018-07-10 9.05 9.05 8.45 8.66 5125563.53
  10. 2018-07-11 9.05 9.05 8.45 8.66 5125563.53
  11. 2018-07-12 9.05 9.05 8.45 8.66 5125563.53
  12. 2018-07-13 9.05 9.05 8.45 8.66 5125563.53
  13. 2018-07-14 9.05 9.05 8.45 8.66 5125563.53
  14. 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
  15. 2018-07-16 8.69 9.03 8.58 8.88 4901983.84
  16. 2018-07-17 8.69 9.03 8.58 8.88 4901983.84
  17. 2018-07-18 8.69 9.03 8.58 8.88 4901983.84
  18. 2018-07-19 8.69 9.03 8.58 8.88 4901983.84
  19. 2018-07-20 8.69 9.03 8.58 8.88 4901983.84
  20. 2018-07-21 8.69 9.03 8.58 8.88 4901983.84
  21. 2018-07-22 8.85 8.90 8.66 8.70 1590354.68

同理,.bfill()代表用后值对空值进行填充,效果如下。


  1. df_daily = df_weekly.resample('D').bfill()
  2. df_daily
  3. Out[55]:
  4. open high low close vol
  5. trade_date
  6. 2018-07-08 9.05 9.05 8.45 8.66 5125563.53
  7. 2018-07-09 8.69 9.03 8.58 8.88 4901983.84
  8. 2018-07-10 8.69 9.03 8.58 8.88 4901983.84
  9. 2018-07-11 8.69 9.03 8.58 8.88 4901983.84
  10. 2018-07-12 8.69 9.03 8.58 8.88 4901983.84
  11. 2018-07-13 8.69 9.03 8.58 8.88 4901983.84
  12. 2018-07-14 8.69 9.03 8.58 8.88 4901983.84
  13. 2018-07-15 8.69 9.03 8.58 8.88 4901983.84
  14. 2018-07-16 8.85 8.90 8.66 8.70 1590354.68
  15. 2018-07-17 8.85 8.90 8.66 8.70 1590354.68
  16. 2018-07-18 8.85 8.90 8.66 8.70 1590354.68
  17. 2018-07-19 8.85 8.90 8.66 8.70 1590354.68
  18. 2018-07-20 8.85 8.90 8.66 8.70 1590354.68
  19. 2018-07-21 8.85 8.90 8.66 8.70 1590354.68
  20. 2018-07-22 8.85 8.90 8.66 8.70 1590354.68

03、时间窗口函数

当我们想要比较数据在相同时间窗口的不同特征和变化时,可以借助窗口函数rolling【6】进行计算。

看一个实例:计算股票收盘价的移动平均值。


  1. df = df[['ts_code', 'close']]
  2. df
  3. Out[58]:
  4. ts_code close
  5. trade_date
  6. 2018-07-02 000001.SZ 8.61
  7. 2018-07-03 000001.SZ 8.67
  8. 2018-07-04 000001.SZ 8.61
  9. 2018-07-05 000001.SZ 8.60
  10. 2018-07-06 000001.SZ 8.66
  11. 2018-07-09 000001.SZ 9.03
  12. 2018-07-10 000001.SZ 8.98
  13. 2018-07-11 000001.SZ 8.78
  14. 2018-07-12 000001.SZ 8.88
  15. 2018-07-13 000001.SZ 8.88
  16. 2018-07-16 000001.SZ 8.73
  17. 2018-07-17 000001.SZ 8.72
  18. 2018-07-18 000001.SZ 8.70

调用rolling函数,通过设置参数window的值规定窗口大小,这里设置为3,并且调用.mean()方法计算窗口期为3天的均值,结果如下。

其中,'20180704'当天的平均值等于'20180702'-'20180704'三天的收盘价取平均的结果,'20180705'当天的平均值等于'20180703'-'20180705'三天的收盘价取平均的结果,以此类推。


  1. df['MA3'] = df['close'].rolling(3).mean()
  2. df
  3. Out[76]:
  4. ts_code close MA3
  5. trade_date
  6. 2018-07-02 000001.SZ 8.61 NaN
  7. 2018-07-03 000001.SZ 8.67 NaN
  8. 2018-07-04 000001.SZ 8.61 8.630000
  9. 2018-07-05 000001.SZ 8.60 8.626667
  10. 2018-07-06 000001.SZ 8.66 8.623333
  11. 2018-07-09 000001.SZ 9.03 8.763333
  12. 2018-07-10 000001.SZ 8.98 8.890000
  13. 2018-07-11 000001.SZ 8.78 8.930000
  14. 2018-07-12 000001.SZ 8.88 8.880000
  15. 2018-07-13 000001.SZ 8.88 8.846667
  16. 2018-07-16 000001.SZ 8.73 8.830000
  17. 2018-07-17 000001.SZ 8.72 8.776667
  18. 2018-07-18 000001.SZ 8.70 8.716667

还有一个常用的窗口函数是expanding,每增加一行数据,窗口会相应的增大。比如,我们想计算某只股票每天的累计涨跌幅,就可以调用此函数。


  1. df = df[['ts_code', 'pct_chg']] # 列pct_chg单位是(%)
  2.  
  3. Out[71]:
  4. ts_code pct_chg
  5. trade_date
  6. 2018-07-02 000001.SZ -5.28
  7. 2018-07-03 000001.SZ 0.70
  8. 2018-07-04 000001.SZ -0.69
  9. 2018-07-05 000001.SZ -0.12
  10. 2018-07-06 000001.SZ 0.70
  11. 2018-07-09 000001.SZ 4.27
  12. 2018-07-10 000001.SZ -0.55
  13. 2018-07-11 000001.SZ -2.23
  14. 2018-07-12 000001.SZ 2.78
  15. 2018-07-13 000001.SZ 0.00
  16. 2018-07-16 000001.SZ -1.69
  17. 2018-07-17 000001.SZ -0.11
  18. 2018-07-18 000001.SZ -0.23

对列'pct_chg'调用窗口函数expanding,再调用.sum()方法求累计值。


  1. df['cum_pct_chg'] = df['pct_chg'].expanding().sum()
  2. df
  3. Out[78]:
  4. ts_code pct_chg cum_pct_chg
  5. trade_date
  6. 2018-07-02 000001.SZ -5.28 -5.28
  7. 2018-07-03 000001.SZ 0.70 -4.58
  8. 2018-07-04 000001.SZ -0.69 -5.27
  9. 2018-07-05 000001.SZ -0.12 -5.39
  10. 2018-07-06 000001.SZ 0.70 -4.69
  11. 2018-07-09 000001.SZ 4.27 -0.42
  12. 2018-07-10 000001.SZ -0.55 -0.97
  13. 2018-07-11 000001.SZ -2.23 -3.20
  14. 2018-07-12 000001.SZ 2.78 -0.42
  15. 2018-07-13 000001.SZ 0.00 -0.42
  16. 2018-07-16 000001.SZ -1.69 -2.11
  17. 2018-07-17 000001.SZ -0.11 -2.22
  18. 2018-07-18 000001.SZ -0.23 -2.45

04、总结

本文介绍了Pandas库中处理时间序列数据的几种常用方法。

在时间格式转换部分,介绍了两种将时间转化成日期类型的方法,分别是通过设置参数parse_dates和调用方法pd.to_datetime()。

接着,介绍了时间周期的转换,通过调用.resample()方法实现,包括降采样和升采样。

最后,介绍两个常用的窗口函数rolling和expanding。

希望大家能灵活掌握本文中提到的方法,并应用到实际工作和学习中去!

译者简介:

Little monster,北京第二外国语学院国际商务专业研一在读,目前在学习Python编程和量化投资相关知识。

上一篇:优酷背后的大数据秘密 基于Kafka的实时计算引擎如何选择?Spark or Flink ?下一篇:

公众平台

搜索"raincent"或扫描下面的二维码