Pandas数据处理——玩转时间序列数据
进行金融数据分析或量化研究时,总避免不了时间序列数据的处理, 时间序列是指在一定时间内按时间顺序测量的某个变量的取值序列。常见的时间序列数据有一天内随着时间变化的温度序列 ,又或者交易时间内不断波动的 股票价格序列 。Pandas也因其强大的时序处理能力而被广泛应用于金融数据分析,这篇文章为大家介绍一下Pandas中的时间序列处理,所使用的数据是上证指数2019年的行情数据。
时间相关的数据类型
Pandas时序处理中最常见的两种数据类型为
datetime
和
timedelta
。一个
datetime
可以如下图所示:
datetime
顾名思义就是既有日期
date
也有时间
time
,表示一个具体的时间点(时间戳)。
timedelta
则表示两个时间点之间的差,比如
2020-01-01
和
2020-01-02
之间的
timedelta
即为一天,相信并不难理解。
将时间列转换为时间格式
大多数时候,我们是从
csv
文件中导入数据,此时
Dataframe
中对应的时间列是
字符串
的形式,如下:
In [5]: data.trade_date.head()
Out[5]:
0 20190102
1 20190103
2 20190104
3 20190107
4 20190108
Name: trade_date, dtype: object
运用
pd.to_datetime()
,可以将对应的列转换为Pandas中的
datetime64
类型,便于后期的处理
In [11]: data["trade_date"] = pd.to_datetime(data.trade_date)
In [12]: data.trade_date.head()
Out[12]:
0 2019-01-02
1 2019-01-03
2 2019-01-04
3 2019-01-07
4 2019-01-08
Name: trade_date, dtype: datetime64[ns]
时间序列的索引
时间序列中索引和Pandas普通的索引类似,大多时候调用
.loc[index,columns]
进行相应的索引,直接上代码看看
In [20]: data1 = data.set_index("trade_date")
# 2019年6月的数据
In [21]: data1.loc["2019-06"].head()
Out[21]:
close open high low
trade_date
2019-06-03 2890.0809 2901.7424 2920.8292 2875.9019
2019-06-04 2862.2803 2887.6405 2888.3861 2851.9728
2019-06-05 2861.4181 2882.9369 2888.7676 2858.5719
2019-06-06 2827.7978 2862.3327 2862.3327 2822.1853
2019-06-10 2852.1302 2833.0145 2861.1310 2824.3554
# 2019年6月-2019年8月的数据
In [22]: data1.loc["2019-06":"2019-08"].tail()
Out[22]:
close open high low
trade_date
2019-08-26 2863.5673 2851.0158 2870.4939 2849.2381
2019-08-27 2902.1932 2879.5154 2919.6444 2879.4060
2019-08-28 2893.7564 2901.6267 2905.4354 2887.0115
2019-08-29 2890.9192 2895.9991 2898.6046 2878.5878
2019-08-30 2886.2365 2907.3825 2914.5767 2874.1028
提取出时间/日期的属性
在时序数据处理过程中,经常需要实现下述需求:
- 求某个日期对应的星期数(2019-06-05是第几周)
- 判断一个日期是周几(2020-01-01是周几)
- 判断某一日期是第几季度(2019-07-08属于哪个季度)
……
当数据中的时间列(本数据中为
trade_date
列)已经转换为
datetime64
格式时,仅需调用
.dt
接口,即可快速求得想要的结果,下表中列出了
.dt
接口所提供的常见属性:
具体演示一下(下面仅显示2019-01-02的信息):
# 一年中的第几天
In [13]: data.trade_date.dt.dayofweek[0]
Out[13]: 2
# 返回对应日期
In [14]: data.trade_date.dt.date[0]
Out[14]: datetime.date(2019, 1, 2)
# 返回周数
In [15]: data.trade_date.dt.weekofyear[0]
Out[15]: 1
# 返回周几
In [16]: data.trade_date.dt.weekday_name[0]
Out[16]: 'Wednesday'
resample
resample
翻译过来是重采样的意思,官方文档中是这么描述
resample
的
resample()
is a time-based groupby
翻译过来就是
基于时间
的
groupby
操作,我个人认为这是Pandas时间序列处理中最重要的功能,也是本文的重中之重。
根据采样是 从低频到高频 还是 从高频到低频 可以分为 升采样 和 降采样 两种方式,先来看看降采样是啥
- 降采样
以一个实例来引入,我们使用的数据是上证指数2019年的 日级别 数据,如果现在想求 每季度的 平均收盘价,应该怎么操作呢?
从日级别数据求季度级别数据,是从
高频到低频
的
聚合
操作,其实就类似于
groupby
按季度进行操作,用
resample
来写是这样子
In [32]: data.resample('Q',on='trade_date')["close"].mean()
Out[32]:
trade_date
2019-03-31 2792.941622
2019-06-30 3010.354672
2019-09-30 2923.136748
2019-12-31 2946.752270
Freq: Q-DEC, Name: close, dtype: float64
其中
'Q'
是以季度为频率进行采样,
on
指定
datetime列
(如果索引为
Datetimeindex
,则
on
不需要指定,默认依据索引进行降采样)。整个过程图解如下:
整个过程其实就是一个
groupby
过程:
-
对原有的数据按照指定的频率进行切分,分到不同的
group
中 -
对不同的
group
执行操作 - 整合操作结果
其中,切分的频率可以为任何时间频率,可以为季度
Q
、月度
M
、星期
W
、N天
ND
,也可以为时
H
、分
T
,当然,如果切分后的频率小于原有的时间频率,就是我们下面要讲的升采样。
- 升采样
当采样的频率
低于
原有的频率时,即为升采样。升采样是对原有的时间粒度更为细粒度的划分,所以升采样时会产生
缺失值
。下面取
2019-01-02
至
2019-01-03
的数据按照
6H
的频率演示一下:
In [24]: example
Out[24]:
close
trade_date
2019-01-02 2465.2910
2019-01-03 2464.3628
In [25]: example.resample('6H').asfreq()
Out[25]:
close
trade_date
2019-01-02 00:00:00 2465.2910
2019-01-02 06:00:00 NaN
2019-01-02 12:00:00 NaN
2019-01-02 18:00:00 NaN
2019-01-03 00:00:00 2464.3628
对
resample
后的结果应用
.asfreq()
会返回新频率下的结果。可以看到升采样后产生了缺失值。如果想要填充缺失值可以采用向后填充
.bfill()
或向前填充
.ffill()
的方式:
# 向前填充,缺失值取2465.2910进行填充
In [29]: example.resample('6H').ffill()
Out[29]:
close
trade_date
2019-01-02 00:00:00 2465.2910
2019-01-02 06:00:00 2465.2910
2019-01-02 12:00:00 2465.2910
2019-01-02 18:00:00 2465.2910
2019-01-03 00:00:00 2464.3628
# 向后填充,缺失值取2464.3628进行填充
In [30]: example.resample('6H').bfill()
Out[30]:
close