搜博主文章 利用Python进行数据分析——时间序列

最常见的时间序列具有如下特征:
- 时间戳,时间的实例
- 有固定的时间间隔,一天,一小时或一分钟
- 有起止点

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()

用户定义的移动窗口函数

(待补充)

猜你喜欢

转载自blog.csdn.net/qq_31823267/article/details/79980893