pandas时间序列数据处理

一、时间格式转换

有时候,我们获得的原始数据并不是按照时间类型索引进行排列的,需要先进行时间格式的转换,为后续的操作和分析做准备。
这里介绍两种方法。第一种方法是用pandas.read_csv导入文件的时候,通过设置参数parse_dates和index_col,直接对日期列进行转换,并将其设置为索引。关于参数的详细解释。
如下示例中,在没有设置参数之前,可以观察到数据集中的索引是数字0-208,'date'列的数据类型也不是日期。

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

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

In [11]: data = pd.read_csv('unemployment.csv', parse_dates=['date'], index_col='date')

In [12]: data.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 209 entries, 2000-01-01 to 2017-05-01
Data columns (total 1 columns):
UNRATE    209 non-null float64
dtypes: float64(1)
memory usage: 13.3 KB

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

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

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

import tushare as ts
import pandas as pd

pd.set_option('expand_frame_repr', False)  # 列太多时不换行
pro = ts.pro_api()

df = pro.daily(ts_code='000001.SZ', start_date='20180701', end_date='20180718')

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 13 entries, 0 to 12
Data columns (total 11 columns):
ts_code       13 non-null object
trade_date    13 non-null object
open          13 non-null float64
high          13 non-null float64
low           13 non-null float64
close         13 non-null float64
pre_close     13 non-null float64
change        13 non-null float64
pct_chg       13 non-null float64
vol           13 non-null float64
amount        13 non-null float64
dtypes: float64(9), object(2)
memory usage: 1.2+ KB
None
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.set_index('trade_date', inplace=True)
df.sort_values('trade_date', ascending=True, inplace=True) # 升序排列

df.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 13 entries, 2018-07-02 to 2018-07-18
Data columns (total 10 columns):
ts_code      13 non-null object
open         13 non-null float64
high         13 non-null float64
low          13 non-null float64
close        13 non-null float64
pre_close    13 non-null float64
change       13 non-null float64
pct_chg      13 non-null float64
vol          13 non-null float64
amount       13 non-null float64
dtypes: float64(9), object(1)
memory usage: 1.1+ KB

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

df.head()
Out[15]: 
              ts_code  open  high   low  close  pre_close  change  pct_chg         vol       amount
trade_date                                                                                         
2018-07-02  000001.SZ  9.05  9.05  8.55   8.61       9.09   -0.48    -5.28  1315520.13  1158545.868
2018-07-03  000001.SZ  8.69  8.70  8.45   8.67       8.61    0.06     0.70  1274838.57  1096657.033
2018-07-04  000001.SZ  8.63  8.75  8.61   8.61       8.67   -0.06    -0.69   711153.37   617278.559
2018-07-05  000001.SZ  8.62  8.73  8.55   8.60       8.61   -0.01    -0.12   835768.77   722169.579
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日线行情数据,选出特定的几列。

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

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


1.jpg

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

2.jpg

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

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

freq = '1W'
df_weekly = df[['open']].resample(rule=freq).first()
df_weekly['high'] = df['high'].resample(rule=freq).max()
df_weekly['low'] = df['low'].resample(rule=freq).min()
df_weekly['close'] = df['close'].resample(rule=freq).last()
df_weekly['vol'] = df['vol'].resample(rule=freq).sum()

df_weekly
Out[33]: 
            open  high   low  close         vol
trade_date                                     
2018-07-08  9.05  9.05  8.45   8.66  5125563.53
2018-07-15  8.69  9.03  8.58   8.88  4901983.84
2018-07-22  8.85  8.90  8.66   8.70  1590354.68

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

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

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

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

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

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

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

时间窗口函数

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

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

df = df[['ts_code', 'close']]
df
Out[58]: 
              ts_code  close
trade_date                  
2018-07-02  000001.SZ   8.61
2018-07-03  000001.SZ   8.67
2018-07-04  000001.SZ   8.61
2018-07-05  000001.SZ   8.60
2018-07-06  000001.SZ   8.66
2018-07-09  000001.SZ   9.03
2018-07-10  000001.SZ   8.98
2018-07-11  000001.SZ   8.78
2018-07-12  000001.SZ   8.88
2018-07-13  000001.SZ   8.88
2018-07-16  000001.SZ   8.73
2018-07-17  000001.SZ   8.72
2018-07-18  000001.SZ   8.70

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

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

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

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

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

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

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

总结

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

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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345