最常见的时间序列具有如下特征:
- 时间戳,时间的实例
- 有固定的时间间隔,一天,一小时或一分钟
- 有起止点
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime,timedelta
Python标准库
Python标准库中内置了对时间类型的支持
now=datetime.now()
print(now.year,now.month,now.day)
2018 4 17
datetime类型可以做运算,默认单位为’天’:
time_delta=datetime(2011,1,7)-datetime(2011,1,5)
time_delta.days
2
datetime(2000,1,1)+2*timedelta(5)
datetime.datetime(2000, 1, 11, 0, 0)
datetime与字符串的转换
#datatime2string
stamp=datetime(2011,1,1,13,14,15)
time_str=stamp.strftime('%Y-%m-%d %H:%M:%S')
time_str
‘2011-01-01 13:14:15’
#string2datetime
time_str='2011-01-03 23:30:10'
time=datetime.strptime(time_str,'%Y-%m-%d %H:%M:%S')
time
datetime.datetime(2011, 1, 3, 23, 30, 10)
time_strs=['7/6/2011','8/6/2011']
for time_str in time_strs:
print(datetime.strptime(time_str,'%m/%d/%Y'))
2011-07-06 00:00:00
2011-08-06 00:00:00
时间序列基本
pandas中最基本的时间序列对象就是以时间戳为行索引的Series:
dates=[datetime(2011,1,2),
datetime(2011,1,5),
datetime(2011,1,7)]
ts=pd.Series(np.random.randn(3),index=dates)
ts
2011-01-02 -0.062302
2011-01-05 0.020653
2011-01-07 -0.296481
dtype: float64
pandas可以很方便地生成一串时间对象:
time_index=pd.date_range('1/1/2011',periods=1000)
time_index
索引、选择、子集
时间序列在索引与选择上与pandas对象基本一致,不过因为时间戳的特殊性,在按时间戳索引时可以有多种不同的格式:
ts.loc['2011-01-05']
0.020653080558353583
ts.loc['20110105']
0.020653080558353583
longer_ts=pd.Series(np.random.randn(1000),pd.date_range('2000-01-01',periods=1000))
#索引出某一月的数据,某年某日类似
longer_ts.loc['2001-02']
#切片索引
longer_ts.loc['20020924':]
2002-09-24 0.556197
2002-09-25 0.895196
2002-09-26 -0.190502
Freq: D, dtype: float64
此处还需要注意的是,同numpy切片索引,连续索引返回的是一个指针引用,只有非连续索引返回的才是一个副本。
日期范围、频率与变换
生成指定范围的日期
pd.date_range('20120401','20120405',freq='D') #以天为间隔
pd.date_range(start='20120401',periods=5,freq='W-MON') #以每周一为间隔
pd.date_range(end='20120405',periods=5,freq='W-SAT') #以每周六为间隔
pd.date_range('20180101',periods=12,freq='BM') #以每月的最后一个工作日为间隔
pd.date_range('20000101',periods=10,freq='90T') #以90分钟为间隔
平移数据
ts=pd.Series(np.abs(np.random.randn(4)),index=pd.date_range('20000101',periods=4,freq='M'))
ts.shift(2) #将数据沿时间轴往后移动两个单位
平移的一个常用用法为计算数据沿时间轴的变化率,如计算以上数据每月的变化率:
(ts.shift(-1)-ts)/ts
shift()方法还可用来更改时间轴:
new_ts=ts.shift(1,freq='D')
new_ts
根据偏移来平移数据
(待补充)
时区处理
时间序列中的时区可能是最令人头疼的工作了。
rng=pd.date_range('20120309 9:30',periods=6,freq='D')
ts=pd.Series(np.random.randn(len(rng)),index=rng)
ts_utc=ts.tz_localize('UTC') #为无时区的时间戳加上时区
ts_utc
rng_utc=pd.date_range('20120309 9:30',periods=6,freq='D',tz='UTC') #在生成时间索引时声明时区
ts_utc=pd.Series(np.random.randn(len(rng_utc)),index=rng_utc)
ts_utc
ts_utc.tz_convert('Asia/Shanghai') #转换时区
在不同时区下操作
如果两个Series拥有不同时区,合并后的结果会是UTC:
rng=pd.date_range('20120307 9:30',periods=5,freq='B')
ts=pd.Series(np.random.randn(len(rng)),index=rng)
ts1=ts.iloc[:3].tz_localize('Europe/London')
ts2=ts.iloc[3:].tz_localize('Europe/Moscow')
(ts1+ts2).index
时期与时期数学
时期代表着一段时间,Period类就代表这种数据结构。
p=pd.Period(2007,freq='A-DEC') #十二月的最后一天为时期的结束日
print(p+5,p-2)
2012 2005
如果两个Period拥有相同的频率,则其唯一的差异就在于单位的数字:
pd.Period(2014,freq='A-DEC')-p
7
类似于date_range(),可以使用period_range()方法生成时期区间:
rng=pd.period_range('20000101','20000630',freq='M')
pd.Series(np.random.randn(6),index=rng)
时期频率转换
如果我们需要将一个年度时期转换成月度时期,使用asfreq()方法进行时期的频率转换:
p=pd.Period('2007',freq='A-JUN') #2007年六月的最后一天为时期的结束日
p
Period(‘2007’, ‘A-JUN’)
p.asfreq('M',how='start') #上述时期的起始月份
Period(‘2006-07’, ‘M’)
p.asfreq('M',how='end') #上述时期的结束月份
Period(‘2007-06’, ‘M’)
季度时期频率
季度性数据是财政领域的标准时期,季度性数据跟财年结束日相关,财年结束日通常为一年中某月(不一定设在十二月)的最后一个工作日。因此,2012Q4时期会根据不同的财年结束日而指向不同的时期,具体看下图:
p=pd.Period('2012Q4',freq='Q-FEB') #财年结束日为FEB
print(p.asfreq('D',how='start'),p.asfreq('D',how='end')) #显示时期的起止日期
2011-12-01 2012-02-29
时间戳与时期的转换
(待补充)
由数组创建时期索引
有时数据集中在一列或多列中包含了时期信息的数据,可以通过这些已有数据来生成时期索引:
data=pd.read_csv('examples/macrodata.csv')
data.sample(5)
year_index=data.loc[:,'year']
year_index.unique()
quarter_index=data.loc[:,'quarter']
quarter_index.unique()
array([1., 2., 3., 4.])
period_index=pd.PeriodIndex(year=year_index,quarter=quarter_index,freq='Q-DEC')
data.index=period_index
data.sample(5)
重采样与频率转换
重采样指的是将一个时间序列的频率进行转换,高频转低频称为下采样,低频转高频称为上采样。resample()方法实际是对时间序列提供了一个类似于groupby()的方法用来按时间戳来聚合数据。
rng=pd.period_range('20000101',periods=100,freq='D')
ts=pd.Series(np.random.randn(100),index=rng)
ts.sample(5)
ts.resample('M',kind='period').mean() #相当于groupby().mean()
下采样
对时间序列进行下采样是最普遍的任务
rng=pd.date_range('20000101',periods=6,freq='T')
ts=pd.Series(np.arange(len(rng)),index=rng)
ts
重采样返回结果的时间戳为划分区间的左值,而区间是左开右闭还是左闭右开由closd=
参数决定。试比较下面两种不同分割方法的区别:
ts.resample('5min',closed='left',label='left').count()
ts.resample('5min',closed='right',label='left').count()
在closed='left'
的条件下,时间序列被分为[00:00:00,00:05:00)与[00:05:00,00:10:00)两个区间,而在closed='right
条件下,被分为(23:55:00,00:00:00]与(00:00:00,00:05:00]两个区间。
区间时间戳的显示值默认为左值,不过可以使用参数label=
来更改为右值:
ts.resample('5min',closed='left',label='right').count()
2000-01-01 00:05:00 5
2000-01-01 00:10:00 1
Freq: 5T, dtype: int64
open-high-low-close(OHLC) 重采样
在金融领域,一种聚合时间序列的常用方法是计算每个分组的四个值:第一个值(open),最后一个值(close),最大值(high),最小值(low):
ts.resample('5min').ohlc()
上采样与插值
当频率由低转高时,没有数据的聚合,而是需要插值。
rng=pd.date_range('20000101',periods=2,freq='W-WED')
frame=pd.DataFrame(np.random.randn(2,4),
index=rng,
columns=['Colorado','Texas','New York','Ohio'])
frame.resample('D').asfreq() #上采样时不插值的话会有缺失值
frame.resample('D').ffill() #前插法插值
可以利用resample()方法+ffill()来改变时间抽:
frame.resample('W-THU').ffill()
时期的重采样
rng=pd.period_range('20000101',periods=14,freq='M')
frame=pd.DataFrame(np.random.randn(len(rng),4),
index=rng,
columns=['Colorado','Texas','New York','Ohio'])
annual_frame=frame.resample('A-DEC').mean()
#上采样
annual_frame.resample('Q-DEC').ffill()
移动窗口函数
应用于时间序列的重要操作,很多都是基于一个滑动窗口或者权重指数衰减之上的,这有助于平滑噪声或带间隔的数据。
close_px_all=pd.read_csv('examples/stock_px_2.csv',parse_dates=True,index_col=0)
close_px=close_px_all.loc[:,['AAPL','MSFT','XOM']]
close_px=close_px.resample('B').ffill()
close_px.loc[:,'AAPL'].rolling(250).mean().plot() #以250天为滑动窗口的APPLE股票均价
plt.show()
因为滑动窗口尺寸为250天,所以在前249天是没有数据的,可以在rolling()方法中使用参数min_periods=
来指定前期允许的最小窗口数:
close_px.loc[:,'AAPL'].rolling(250,min_periods=10).mean().plot() #最小允许一个10天的滑动窗口
plt.show()
当对一个DataFrame直接应用rolling()方法时,会单独对DF中的每一列进行操作:
close_px.rolling(100).mean().plot(logy=True) #开启指数纵坐标
plt.show()
指数加权函数
在很多关于时间序列的应用场景下,我们会更看重最近的值,即最近的值得权重越大,而越久远的值权重越小。我们可以应用ewm()方法来实现与rolling()方法类似的功能,并且是带权重的:
close_px.loc[:,'AAPL'].rolling(30).mean().plot(style='k--',label='Simple MA')
close_px.loc[:,'AAPL'].ewm(30).mean().plot(style='k-',label='EW MA')
plt.show()
二进制移动窗口函数
有些统计学操作,如相关系数或协方差,需要在两个时间序列上操作。举个例子,金融分析师经常会对一只股票与S&P500的关系感兴趣。
spx_px=close_px_all.loc[:,'SPX']
spx_returns=spx_px.pct_change()
returns=close_px.pct_change()
returns.rolling(100,min_periods=50).corr(spx_returns).plot()
plt.show()
用户定义的移动窗口函数
(待补充)