pandas笔记:ch10:时间序列
ch10
时间序列¶
Time series
from __future__ import division
from pandas import Series, DataFrame
import pandas as pd
from numpy.random import randn
import numpy as np
pd.options.display.max_rows = 12
np.set_printoptions(precision=4, suppress=True)
import matplotlib.pyplot as plt
plt.rc('figure', figsize=(12, 4))
%matplotlib inline
日期和时间数据类型及工具¶
Date and Time Data Types and Tools
Python标准库包含用于日期(date)和时间(time)数据的数据类型,而且还有日历方面的功能。我们主要会用到datetime,time以及calendar模块。datetime.datetime(也可以简写成datetime)是用的最多的数据类型:
'datetime.now(): 返回现在的时间'
from datetime import datetime
now = datetime.now()
now
now.year, now.month, now.day
'datetime以毫秒形式存储日期和时间。datetime.timedelta表示两个datetime对象之间的时间差 '
delta = datetime(2011, 1, 7) - datetime(2008, 6, 24, 8, 15)
delta
delta.days
delta.seconds
'datetime + or - timedelta'
from datetime import timedelta
start = datetime(2011, 1, 7)
start + timedelta(12)
start - 2 * timedelta(12)
datetime模块中的数据类型¶
date 以公立形式存储日历日期(年,月,日) time 将时间存储为时,分,秒,毫秒 datetime 存储日期和时间 timedelta 表示两个datetime值之间的差(日,秒,毫秒)
字符串和datetime的相互转换¶
Converting between string and datetime
'str 或 strftime 方法(传入一个格式化字符串): datetime对象和pandas的Timestamp对象 -> 格式化为字符串'
stamp = datetime(2011, 1, 3)
str(stamp)
'strftime: 实例化方法,传入格式化字符串'
stamp.strftime('%Y-%m-%d')
'strptime:用这些格式化编码将字符串转化为日期 (strftime的逆运算) '
value = '2011-01-03'
datetime.strptime(value, '%Y-%m-%d')
datestrs = ['7/6/2011', '8/6/2011']
[datetime.strptime(x, '%m/%d/%Y') for x in datestrs]
datetime.strptime是通过已知格式进行日期解析的最佳方式。但是每次都要编写格式定义是很麻烦的事情,尤其是对于一些常见的格式。
这种情况下,你可以用dateutil这个第三方包中的parser.parse方法
'dateutil.parser.parse方法:处理常见数据格式很方便'
from dateutil.parser import parse
parse('2011-01-03')
'parse()可以解析非常多的人类能够理解的日期表示形式(中文不行)'
parse('Jan 31, 1997 10:45 PM')
'dayfirst=True: 日 出现在 月 的前面(更符合国际通用的格式)'
parse('6/12/2011', dayfirst=True)
pandas通常是用于处理成组日期的,不管这些日期是DataFrame的轴索引还是列。to_datetime方法可以解析多种不同的日期表示形式。对标准日期格式(ISO8601)的解析非常快。
datestrs
pd.to_datetime(datestrs)
# note: output changed (no '00:00:00' anymore)
'它还可以处理缺失值(None,空字符串等):'
idx = pd.to_datetime(datestrs + [None])
idx
'Nat(Not a Time)是pandas中时间戳数据的NA值。'
idx[2]
pd.isnull(idx)
表10-2:datetime格式定义(兼容ISO C89)¶
%Y 4位数的年 %y 2位数的年 %m 2位数的月[01,12] %d 2位数的日[01,31] %H 时(24小时制)[00,23] %I 时(12小时制)[01,12] %M 2位数的分[00,59] %S 秒[00,61](秒60和61用于闰秒) %w 用整数表示的星期几[0(星期天),6] %U 每年的第几周[00,53]。星期天被认为是每周的第一天,每年第一个星期一之前的那几天被认为是‘第0周’ %W 每年的第几周[00,53]。星期一被认为是每周的第一天,每年第一个星期一之前的那几天被认为是‘第0周’ %z 以+HHMM或-HHMM表示的UTC时区偏移量,如果时区为naive,则返回空字符串 %F %Y-%m-%d简写形式,例如2012-04-18 %D %m/%d/%y简写形式,例如04/18/12
datetime对象还有一些特定于当前环境(位于不同国家或使用不同语言的系统)的格式化选项。例如,德语或法语系统所用的月份简写就与英语系统所用的不同。
表10-3:特定于当前环境的日期格式¶
%a 星期几的简写 %A 星期几的全称 %b 月份的简写 %B 月份的全称 %c 完整的日期格式,例如‘Tue 01 May 2012 04:20:57 PM’ %p 不同环境中的AM或PM %x 适合于当前环境的日期格式,例如,在美国,‘May 1,2012’会产生‘05/01/2012’ %X 适合于当前环境的时间格式,例如‘04:24:12 PM’
时间序列基础¶
Time Series Basics
'pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datatime对象表示)为索引的Series'
from datetime import datetime
dates = [datetime(2011, 1, 2), datetime(2011, 1, 5), datetime(2011, 1, 7),
datetime(2011, 1, 8), datetime(2011, 1, 10), datetime(2011, 1, 12)]
ts = Series(np.random.randn(6), index=dates)
ts
'datetime,Series,DatetimeIndex,"<M8[ns]",datetime64[ns],Timestamp'
type(ts)
#书本上的类型是TimeSeries,这里是 Series note: output changed to "pandas.core.series.Series"
ts.index
ts + ts[::2]
'pandas用Numpy的datetime64数据类型以纳秒形式存储时间戳:'
ts.index.dtype
# note: output changed from dtype('datetime64[ns]') to dtype('<M8[ns]')
stamp = ts.index[0]
stamp
# note: output changed from <Timestamp: 2011-01-02 00:00:00> to Timestamp('2011-01-02 00:00:00')
只要有需要,TimeStamp可以随时自动转换为datetime对象。此外,它还可以存储频率信息(如果有的话),且知道如何执行时区转换以及其他操作。稍后进行讲解。
索引,选取,子集构造¶
Indexing, selection, subsetting
索引传入 字符串日期,datetime,Timestamp,等价的实例化方法(after= )
'传入索引取值'
stamp = ts.index[2]
ts[stamp]
stamp
'传入可以表示为日期的字符串取值'
ts['1/10/2011']
ts['20110110']
'传入 年,年月 即可以对数据切片'
longer_ts = Series(np.random.randn(1000),
index=pd.date_range('1/1/2000', periods=1000))
longer_ts
longer_ts['2001']
longer_ts['2001-05']
ts
'日期切片的方式只对规则Series有效'
ts[datetime(2011, 1, 7):]
'传入指定范围日期进行切片,这个范围可以大于索引的时间范围'
ts['1/6/2011':'1/11/2011']
'x.truncate(after= ): 时间的终值值'
ts.truncate(after='1/9/2011')
'上面这些操作对DataFrame也有效'
dates = pd.date_range('1/1/2000', periods=100, freq='W-WED')
long_df = DataFrame(np.random.randn(100, 4),
index=dates,
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
long_df.ix['5-2001']
带有重复索引的时间序列¶
Time series with duplicate indices
dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000', '1/2/2000',
'1/3/2000'])
dup_ts = Series(np.arange(5), index=dates)
dup_ts
'x.index.is_unique: 检查索引值是否都是唯一的。'
dup_ts.index.is_unique
dup_ts['1/3/2000'] # 不重复 not duplicated
dup_ts['1/2/2000'] # 重复 duplicated
'对有重复索引的数据进行聚合:使用groupby,传入(level=0)(索引的唯一一层)'
grouped = dup_ts.groupby(level=0)
grouped.mean()
grouped.count()
日期的范围,频率以及移动¶
Date ranges, Frequencies, and Shifting
ts
'x.resample("D"):将时间序列 x 转换成一个具有固定频率(每日)的时间序列'
ts.resample('D')
生成日期范围¶
Generating date ranges
'pd.date_range():用于生成指定长度的DatetimeIndex:'
index = pd.date_range('4/1/2012', '6/1/2012')
index
'默认下,date_range会产生一个按天计算的时间点。如果只传入start= 或 end= (两者只取其中一个),那还得传入 periods= (表示时间段)'
pd.date_range(start='4/1/2012', periods=20)
pd.date_range(end='6/1/2012', periods=20)
'freq="BM" 传入频率 “BM”(表示business end of month),即每月最后一个工作日'
pd.date_range('1/1/2000', '12/1/2000', freq='BM')
'pd.date_range(): 默认保留起始和结束时间戳的时间信息(如果有的话)'
pd.date_range('5/2/2012 12:56:31', periods=5)
'normalize=True 产生一组被规范化到午夜的时间戳'
pd.date_range('5/2/2012 12:56:31', periods=5, normalize=True)
频率和日期偏移量¶
Frequencies and Date Offsets
pandas中的频率是有一个基础频率(base frequency)和一个乘数组成的。基础频率通常以一个字符串别名表示,比如"M"表示每月,"H"表示每小时。对于每个基础频率,都有一个被称为日期偏移量(date offset)的对象与之对应。 如 "H" 对应 Hour() 。
"导入 日期偏移量(date offset)对象"
from pandas.tseries.offsets import Hour, Minute
hour = Hour()
hour
four_hours = Hour(4)
four_hours
'对 freq= 使用基础频率 "4h"'
pd.date_range('1/1/2000', '1/3/2000 23:59', freq='4h')
'偏移量对象的 + - 运算'
Hour(2) + Minute(30)
'freq= 传入字符串的等效表达式'
pd.date_range('1/1/2000', periods=10, freq='1h30min')
WOM日期¶
Week of month dates
WOM是一种非常实用的频率类,使用它,你可以获得诸如‘每月第3个星期五’之类的日期
rng = pd.date_range('1/1/2012', '9/1/2012', freq='WOM-3FRI')
list(rng)
表10-4:时间序列的基础频率¶
别名 偏移量类型 说明` D Day 每日历日 B BusinessDay 每工作日 H Hour 每小时 T或min Minute 每分 S Second 每秒 L或ms Milli 每毫秒(即每千分之一秒) U Micro 每微妙(即每百万分之一秒) M MonthEnd 每月最后一个日历日 BM BusinessMonthEnd 每月最后一个工作日 MS MonthBegin 每月第一个日历日 BMS BusinessMonthBegin 每月第一个工作日 W-MON,W-TUE... Week 从每周指定的星期几(MON,TUE,WED,THU,FRI,SAT,SUN)。 WOM-1MON,WOM-2TUE... WeekOfMonth 产生每月第一,第二,第三或第四周的星期几。例如,WOM-3FRI表示每月第三个星期五 Q-JAN,Q-FEB... QuarterEnd 对于以指定月份(JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC)结束的年度,每季度最后一月的最后一个日历日 BQ-JAN,BQ-FEB... BusinessQuarterEnd 对于以指定月份结束的年度,每季度最后一月的最后一个工作日。 QS-JAN,QS-FEB... QuarterBegin 对于以指定月份结束的年度,每季度最后一月的第一个日历日 BQS-JAN,BQS-FEB... BusinessQuarterBegin 对于以指定月份结束的年度,每季度最后一月的第一个工作日 A-JAN,A-FEB... YearEnd 每年指定月份(JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC)的最后一个日历日 BA-JAN,BA-FEB... BusinessYearEnd 每年指定月份的最后一个工作日 AS-JAN,AS-FEB... YearBgein 每年指定月份的第一个日历日 BAS-JAN,BAS-FEB... BusinessYearBegin 每年指定月份的第一个工作日
移动(超前和滞后)数据 shift 方法¶
Shifting (leading and lagging) data
ts = Series(np.random.randn(4),
index=pd.date_range('1/1/2000', periods=4, freq='M'))
ts
'默认是针对数据的移动(不影响索引)。这里(传入正值)是向后移动'
ts.shift(2)
ts.shift(-2)
'shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。可以这样表达:'
ts / ts.shift(1) - 1
'由于单纯的移位操作不会修改索引,所以部分数据会被丢弃。因此如果频率已知,可以通过如下方式保留完整数据。'
'freq="M",传递指定的频率可以对时间戳进行位移,而不是对数据进行简单的移动。'
ts.shift(2, freq='M')
'下面是传递其他的 频率, 这样你就可以对数据进行非常灵活的超前喝滞后处理了。'
ts.shift(3, freq='D')
ts.shift(1, freq='3D')
ts.shift(1, freq='90T')
通过偏移量对日期进行位移¶
Shifting dates with offsets
'pandas 的日期偏移量还可以用在datetime或Timestamp对象上'
from pandas.tseries.offsets import Day, MonthEnd
now = datetime(2011, 11, 17)
now + 3 * Day()
'瞄点偏移量的用法:第一次增量会到达本月(或本年,本周)的对应日期'
now + MonthEnd()
'瞄点偏移量的用法:第二次增量则按正常的增量来。'
now + MonthEnd(2)
'通过瞄点偏移量的rollforward和rollback方法,可以显式地将日期向前或向后“滚动”'
offset = MonthEnd()
offset.rollforward(now)
offset.rollback(now)
'日期偏移量还有一个巧妙的用法,即结合groupby使用这两个“滚动”方法'
ts = Series(np.random.randn(20),
index=pd.date_range('1/15/2000', periods=20, freq='4d'))
ts.groupby(offset.rollforward).mean()
'更简单,更快速的实现该功能的办法是使用resample(稍后将对此进行详细分析)'
ts.resample('M', how='mean')
时区处理¶
Time Zone Handling
Python的时区信息来自第三方库pytz,由于pandas包装了pytz的功能,因此你可以不用记忆其API,只要记得时区的名称即可。
import pytz
pytz.common_timezones[-5:]
tz = pytz.timezone('US/Eastern')
tz
'pandas中的方法既可以接受时区名称,也可以接受这种对象。我建议只用时区名。'
本地化和转换¶
Localization and Conversion
'默认下,pandas中的时间序列是单纯的(naive)时区。'
rng = pd.date_range('3/9/2012 9:30', periods=6, freq='D')
ts = Series(np.random.randn(len(rng)), index=rng)
print(ts.index.tz)
'pd.date_range(tz= ):在生成日趋范围的时候还可以加上一个时区集。'
pd.date_range('3/9/2012 9:30', periods=10, freq='D', tz='UTC')
'x.tz_localize(): 从 单纯 到 本地化 的转换。'
ts_utc = ts.tz_localize('UTC')
ts_utc
ts_utc.index
'x.tz_convert(): 一旦时间序列被转化到某个特定时区,就可以用 tz_convert 将其转换到别的时区了。'
ts_utc.tz_convert('US/Eastern')
'对于上面的这种时间序列(他跨越了美国东部时区的夏令时转变期),我们可以将其本地化到EST,然后转化为UTC或柏林时间:'
ts_eastern = ts.tz_localize('US/Eastern')
ts_eastern.tz_convert('UTC')
ts_eastern.tz_convert('Europe/Berlin')
'tz_localize,tz_convert 也是 DatetimeIndex的实例方法'
ts.index.tz_localize('Asia/Shanghai')
操作时区意识型Timestamp对象¶
Operations with time zone-aware Timestamp objects
和时间序列和日期范围差不多,Timestamp对象也能被从单纯性(naive)本地化为时区意识型(time zone-aware),并从一个时区转换到另一个时区:
stamp = pd.Timestamp('2011-03-12 04:00')
stamp_utc = stamp.tz_localize('utc')
stamp_utc.tz_convert('US/Eastern')
'创建Timestamp时,还可以传入一个时区信息:'
stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow')
stamp_moscow
'时区意识型Timestamp对象在内部保存了一个UTC时间戳值(自UNIX纪元(1970年1月1日)算起的纳秒数)。这个UTC值在时区转换过程中是不会发生变化的。'
stamp_utc.value
stamp_utc.tz_convert('US/Eastern').value
'使用pandas的DateOffset对象执行时间算术运算时,运算过程会自动关注是否存在夏令时转变期:'
'这里的 30 minutes 我认为应该是 90 minutes '
# 30 minutes before DST transition
from pandas.tseries.offsets import Hour
stamp = pd.Timestamp('2012-03-12 01:30', tz='US/Eastern')
stamp
stamp + Hour()
# 90 minutes before DST transition
stamp = pd.Timestamp('2012-11-04 00:30', tz='US/Eastern')
stamp
stamp + 2 * Hour()
不同时区之间的运算¶
Operations between different time zones
'如果两个时间序列的时区不同,在将他们合并到一起时,最终结果就会是UTC。由于时间戳其实是UTC存储的,所以这是一个很简单的运算,并不需要发生任何转换'
rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B')
ts = Series(np.random.randn(len(rng)), index=rng)
ts
ts1 = ts[:7].tz_localize('Europe/London')
ts2 = ts1[2:].tz_convert('Europe/Moscow')
result = ts1 + ts2
result.index
result
时期及其算术运算¶
Periods and Period Arithmetic
时期(period)表示的是时间区间,比如数日,数月,数季,数年等。period类所表示的就是这种数据类型,其构造函数需要用到一个字符串或整数,以及表10-4中的频率:
'用Period对象表示 2007年1月 - 2007年 12月 31日 之间的整段时间。'
p = pd.Period(2007, freq='A-DEC')
p
'对period 加上或减去一个整数即可达到根据其频率进行位移的效果。'
p + 5
p - 2
'如果两个period对象有相同的频率,那么他们的差就是它们之间的单位数量'
pd.Period('2014', freq='A-DEC') - p
'pd.period_range()函数可用于创建规则的时期范围。'
rng = pd.period_range('1/1/2000', '6/30/2000', freq='M')
rng
'注意与 pd.date_range() 的区别:'
'一个是 PeriodIndex 一个是 DatetimeIndex'
'一个的 dtype 类型是 int64, 一个的 dtype 类型是 datetime64[ns]'
pd.date_range('1/1/2000', '6/30/2000', freq='M')
'PeriodIndex 类保存了一组Period,他可以在任何pandas数据结构中被用作轴索引:'
Series(np.random.randn(6), index=rng)
'PeriodIndex 类的构造函数还允许直接使用一组字符串:'
values = ['2001Q3', '2002Q2', '2003Q1']
index = pd.PeriodIndex(values, freq='Q-DEC')
index
时期的频率转换¶
Period Frequency Conversion
asfreq: period和PeriodIndex对象都可以通过其asfreq方法被转换成别的频率。
'把年度时期转换为当年年初或年末的一个月度时期。'
p = pd.Period('2007', freq='A-DEC')
p.asfreq('M', how='start')
p.asfreq('M', how='end')
你可以将Period('2007','A-DEC')看做一个被划分为多个月度时期的时间段中的游标。
但是对于一个不以12月结束的财政年度,月度子时期的归属情况就不一样了。
p = pd.Period('2007', freq='A-JUN')
p.asfreq('M', 'start')
p.asfreq('M', 'end')
'再将高频率转换为低频率时,超时期(superperiod)是由子时期(subperiod)所属的位置决定的。'
'例如,在A-JUN频率中,月份‘2007年8月’实际上是属于周期‘2008年’的'
p = pd.Period('Aug-2007', 'M')
p.asfreq('A-JUN')
'Period或Series的频率转换方式也是如此:'
rng = pd.period_range('2006', '2009', freq='A-DEC')
ts = Series(np.random.randn(len(rng)), index=rng)
ts
ts.asfreq('M', how='start')
ts.asfreq('B', how='end')
按季度计算的时期频率¶
Quarterly period frequencies
季度性数据在会计、金融等领域中很常见。许多季度性数据都会涉及‘财年末’的概念,通常是一年12个月中某月的最后一个日历日或工作日。就这一点来说,时期‘2012Q4’根据财年末的不同会有不同的含义。pandas支持12种可能的季度性频率,即Q-JAN 到 Q-DEC:
'再以1月结束的财年中,2012Q4是从11月到1月'
p = pd.Period('2012Q4', freq='Q-JAN')
p
p.asfreq('D', 'start')
p.asfreq('D', 'end')
'因此,Period之间的算术运算会非常简单。例如,要获取该季度倒数第二个工作日下午4点的时间戳,你可以这样:'
p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
p4pm
p4pm.to_timestamp()
'pd.period_range(): 还可以用于生成季度型范围。'
rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
ts = Series(np.arange(len(rng)), index=rng)
ts
'季度型范围的算术运算也跟上面是一样的:'
new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60
ts.index = new_rng.to_timestamp()
ts
将Timestamp转换为Period(及其反向过程)¶
Converting Timestamps to Periods (and back)
'x.to_period(): 可以将由时间戳索引的Series和DataFrame对象转换为以时期索引。'
rng = pd.date_range('1/1/2000', periods=3, freq='M')
ts = Series(randn(3), index=rng)
pts = ts.to_period()
ts
pts
由于时期指的是非重叠时间区间,因此对于给定的频率,一个时间戳只能属于一个时期。新PeriodIndex的频率默认是从时间戳推断而来的,你也可以指定任何特别的频率,在结果中允许存在重复时期:
rng = pd.date_range('1/29/2000', periods=6, freq='D')
ts2 = Series(randn(6), index=rng)
ts2.to_period('M')
ts2.to_period()
pts = ts.to_period()
pts
'x.to_timestamp(): 转换为时间戳'
pts.to_timestamp(how='end')
通过数组创建PeriodIndex¶
Creating a PeriodIndex from arrays
'固定频率的数据集通常会将时间信息分开存放在多个列中。例如,在下面这个宏观经济数据集中,年度和季度就分别存放在不同的列中。'
data = pd.read_csv('ch08/macrodata.csv')
data.year
data.quarter
'将这两个数组以及一个频率传入PeriodIndex,就可以将它们合并成DataFrame的一个索引。'
index = pd.PeriodIndex(year=data.year, quarter=data.quarter, freq='Q-DEC')
index
data.index = index
data.infl
重采样及频率转换¶
Resampling and Frequency Conversion
重采样(resampling)指的是将时间序列从一个频率转换到另一个频率的处理过程。将高频率数据聚合到低频率称为降采样(downsampling),而将低频率数据转换到高频率则称为升采样(upsampling)。并不是所有的重采样都能被划分到这两大类中。例如将W-WED(每周三)转换为W-FRI既不是降采样也不是升采样。
'pandas对象都带有一个resample方法,它是各种频率转换工作的主力函数:'
rng = pd.date_range('1/1/2000', periods=100, freq='D')
ts = Series(randn(len(rng)), index=rng)
ts.resample('M', how='mean')
ts.resample('M', how='mean', kind='period')
表10-5:resample方法的参数¶
参数 说明 freq 表示重采样频率的字符串或DateOffset,例如'M','5min'或'Second(15)' how='mean' 用于产生聚合值的函数名或数组函数,例如’mean',‘ohic',np.max等。默认为'mean'。其他常用的值有:'first','last','median','ohic','max','min' axis=0 重采样的轴,默认为axis=0 fill_method=None 升采样时如何插值,比如'ffill'或'bfill'。默认不插值 closed='left' 再降采样中,各时间段的哪一端是闭合(即包含)的,'right'或'left'。默认为'left' label='left' 再降采样中,如何设置聚合值的标签,'right'或'left'(面元的右边界或左边界)。例如,9:30到9:35之间的这5分钟会被标记为9:30或9:35.默认为'left'(本例就是9:30) loffset=None 面元标签的世间校正值,比如'-ls'/Second(-1)用于将聚合标签调早1秒 limit=None 在向前或向后填充时,允许填充的最大是期数。 kind=None 聚合到期('period')或时间戳('timestamp'),默认聚合到时间序列的索引类型。 convention=None 当重采样时期时,将低频率转换到高频率所采用的约定('start'或'end')。默认为'end'。
降采样¶
Downsampling
在用resample对数据进行降采样时,需要考虑两样东西:
1、各区间哪边是闭合的(默认:closed='left')
2、如何标记各个聚合面元,用区间的开头还是末尾(默认:label='left')
rng = pd.date_range('1/1/2000', periods=12, freq='T')
ts = Series(np.arange(12), index=rng)
ts
'通过 sum 方式将数据聚合到 5分钟 块中'
'默认输出形式是: closed="left",label="left" '
ts.resample('5min', how='sum')
# note: output changed (as the default changed from closed='right', label='right' to closed='left', label='left'
'变为右边闭合(closed=right)。'
ts.resample('5min', how='sum', closed='right')
ts.resample('5min', how='sum', closed='right', label='right')
'loffset="-1s",对索引做位移,这里是减去一秒。'
ts.resample('5min', how='sum', loffset='-1s')
OHLC重采样¶
Open-High-Low-Close (OHLC) resampling
'how="ohlc"可以得到各面元的四个值:开盘价(open),收盘价(close),最大值(high),最小值(low)'
ts.resample('5min', how='ohlc')
# note: output changed because of changed defaults
通过groupby进行重采样¶
Resampling with GroupBy
'例如,你打算根据月份或星期几进行分组,只需要传入一个能够访问时间序列的索引上的这些字段的函数即可:'
rng = pd.date_range('1/1/2000', periods=100, freq='D')
ts = Series(np.arange(100), index=rng)
ts.groupby(lambda x: x.month).mean()
ts.groupby(lambda x: x.weekday).mean()
升采样和插值¶
Upsampling and interpolation
frame = DataFrame(np.random.randn(2, 4),
index=pd.date_range('1/1/2000', periods=2, freq='W-WED'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame
'低频到高频,默认会引入缺失值'
df_daily = frame.resample('D')
df_daily
'fill_method="ffill",resample的填充和插值方式跟fillna和reindex的一样。'
frame.resample('D', fill_method='ffill')
'limit=2,填充指定时期数。'
frame.resample('D', fill_method='ffill', limit=2)
'注意:新的日期索引完全没有必要跟旧的相交。'
frame.resample('W-THU', fill_method='ffill')
frame.resample('W-THU')
通过时期进行重采样¶
Resampling with periods
frame = DataFrame(np.random.randn(24, 4),
index=pd.period_range('1-2000', '12-2001', freq='M'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame[:5]
'降采样 月 -> 年'
annual_frame = frame.resample('A-DEC', how='mean')
annual_frame
'升采样要稍微麻烦一些,因为你必须决定在新频率中各区间的哪端用于放置原来的值,就像asfreq方法那样。--- '
'--- convention参数默认值为"start",可以设置为"end"'
# Q-DEC: 季度型(每年以12月结束)Quarterly, year ending in December
annual_frame.resample('Q-DEC', fill_method='ffill')
# note: output changed, default value changed from convention='end' to convention='start' + 'start' changed to span-like
# also the following cells
'convention="end" 可以理解为以 2000 年的最后一个 “Q-DEC” 作为起始点采样。'
annual_frame.resample('Q-DEC', fill_method='ffill', convention='end')
由于时期指的是时间区间,所以升采样和降采样的规则就比较严格:
1) 在降采样中,目标频率必须是源频率的子时期(subperiod)。
2) 在升采样中,目标频率必须是源频率的超时期(superperiod)。
如果不满足这些条件,就会发生异常。这主要影响的是按季,年,周计算的频率。例如,由Q-MAR定义的时间区间只能升采样为A-MAR,A-JUN,A-SEP,A-DEC等:
'####????这里不懂为什么会这样?'
annual_frame.resample('Q-MAR', fill_method='ffill')
时间序列绘图¶
Time series plotting
close_px_all = pd.read_csv('ch09/stock_px.csv', parse_dates=True, index_col=0)
close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]
close_px = close_px.resample('B', fill_method='ffill')
close_px.info()
'pandas的时间序列的绘图功能在日期格式化方面比matplotlib原生的要好。对其中任意一列调用plot即可生成一张简单的图表:'
close_px['AAPL'].plot()
'当对DataFrame调用plot时,所有时间序列都会被绘制在一个subplot上,并有一个图例说明哪个是哪个。'
close_px.ix['2009'].plot()
close_px['AAPL'].ix['01-2011':'03-2011'].plot()
'季度型频率的数据会用季度标记进行格式化,这种事情如果纯手工做的话那是很费精力的。'
appl_q = close_px['AAPL'].resample('Q-DEC', fill_method='ffill')
appl_q.ix['2009':].plot()
pandas时间序列在绘图方面还有一个特点:当右键点击并拖拉(放大、缩小)是,日期会动态展开或收缩,且绘图窗口中的时间区间会被重新格式化。当然,只有在交互模式下使用matplotlib才会有此效果。
移动窗口函数¶
Moving window functions
在移动窗口(可以带有指数衰减权数)上计算的各种统计函数也是一类常见于时间序列的数组变换。我将它们称为移动窗口函数(moving window function),其中还包括那些窗口不定长的函数(如指数加权移动平均)。跟其他统计函数一样,移动窗口函数也会自动排除缺失值。
close_px = close_px.asfreq('B').fillna(method='ffill')
结果如下图所示,默认情况下,诸如rolling_mean这样的函数需要指定数量(译注:这是针对窗口而言的,即一个窗口里面必须有多少个非NA值)的非NA观测值。可以修改该行为以解决缺失值的问题。其实,在时间序列开始处尚不足窗口期的那些数据就是个特例(见下下图):
'苹果公司股价的250日均线'
close_px.AAPL.plot()
pd.rolling_mean(close_px.AAPL, 250).plot()
plt.figure()
'min_periods=10 最小10天可以看到标准差数据,由于10<250,则第一个数据是10日标准差,第二个数据是11日标准差,以此类推一直到250日--- '
'--- 标准差然后停止递增,里250 指的是 window=250'
appl_std250 = pd.rolling_std(close_px.AAPL, 250, min_periods=10)
appl_std250[5:12]
test = close_px.AAPL[:10]
test.std()
test = close_px.AAPL[:12]
test.std()
appl_std250[248:251]
test = close_px.AAPL[:251]
print(test[0:249].std(), '\n', test[0:250].std(), '\n', test[1:251].std(), '\n', test[0:251].std()) #最后一个数是251日标准差。
'苹果公司250日每日回报标准差'
appl_std250.plot()
'????????不懂这里的意思。'
'要计算扩展矿口平均(expanding window mean),你可以将扩展窗口看做一个特殊的窗口,其长度与时间序列一样,但只需一期(或多期)--- '
'--- (译注:不设置就完全空,也不要太大,大了就无意义了。)即可计算一个值'
# Define expanding mean in terms of rolling_mean
expanding_mean = lambda x: rolling_mean(x, len(x), min_periods=1)
'对DataFrame调用rolling_mean(以及与之类似的函数)会将转换应用到所有的列上。'
pd.rolling_mean(close_px, 60).plot(logy=True)
plt.close('all')
表10-6:移动窗口和指数加权函数¶
rolling_count 返回各窗口非NA观测值的数量 rolling_sum 移动窗口的和 rolling_mean 移动窗口的平均值 rolling_median 移动窗口的中位数 rolling_var,rolling_std 移动窗口的方差和标准差,分母为n-1 rolling_skew,rolling_kurt 移动窗口的偏度(三阶矩)和峰度(四阶矩) rolling_min,rolling_max 移动窗口的最小值和最大值 rolling_quantile 移动窗口指定百分位数/样本分位数位置的值 rolling_corr,rolling_cov 移动窗口的相关系数和协方差 rolling_apply 对移动窗口应用普通数组函数 ewma 指数加权移动平均 ewmvar,ewmstd 指数加权移动方差和标准差 ewmcorr,ewmcov 指数加权移动相关系数和协方差注意:bottleneck(由Keith Goodman制作的Python库)提供了另一种对NaN友好的移动窗口函数集。值得一看,说不定能在你的工作中派上用场 。
指数加权函数¶
Exponentially-weighted functions
'简单移动平均与指数加权移动平均'
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True, sharey=True,
figsize=(12, 7))
aapl_px = close_px.AAPL['2005':'2009']
ma60 = pd.rolling_mean(aapl_px, 60, min_periods=50)
ewma60 = pd.ewma(aapl_px, span=60)
aapl_px.plot(style='k-', ax=axes[0])
ma60.plot(style='k--', ax=axes[0])
aapl_px.plot(style='k-', ax=axes[1])
ewma60.plot(style='k--', ax=axes[1])
axes[0].set_title('Simple MA')
axes[1].set_title('Exponentially-weighted MA')
二元移动窗口函数¶
Binary moving window functions
close_px
spx_px = close_px_all['SPX']
'计算AAPL对标准普尔500指数的相关系数:先计算百分比变化,再使用rolling_corr得到结果。'
spx_rets = spx_px / spx_px.shift(1) - 1
returns = close_px.pct_change()
corr = pd.rolling_corr(returns.AAPL, spx_rets, 125, min_periods=100)
corr.plot()
'AAPL6个月的回报与标准普尔500指数的相关系数'
'计算多只股票与标准普尔500指数的相关系数:不用编写循环,与上面类似,只需要传入一个Series和DataFrame。'
corr = pd.rolling_corr(returns, spx_rets, 125, min_periods=100)
corr.plot()
'3只股票6个月的回报与标准普尔500指数的相关系数'
用户定义的移动窗口函数¶
User-defined moving window functions
rolling_apply函数使你能够在移动窗口上应用自己设计的数组函数。唯一要求的就是:该函数要能从数组的各个片段中产生单个值(即简约)。比如说,当我们用rolling_quantile计算样本分位数时,可能对样本中特定值的百分等级感兴趣。scipy.stats.percentileofscore函数就能达到这个目的。
'????????不懂为什么'
from scipy.stats import percentileofscore
score_at_2percent = lambda x: percentileofscore(x, 0.02)
result = pd.rolling_apply(returns.AAPL, 250, score_at_2percent)
result.plot()
性能和内存使用方面的注意事项¶
Performance and Memory Usage Notes
rng = pd.date_range('1/1/2000', periods=10000000, freq='10ms')
ts = Series(np.random.randn(len(rng)), index=rng)
ts
ts.resample('15min', how='ohlc').info()
%timeit ts.resample('15min', how='ohlc')
rng = pd.date_range('1/1/2000', periods=10000000, freq='1s')
ts = Series(np.random.randn(len(rng)), index=rng)
%timeit ts.resample('15s', how='ohlc')

浙公网安备 33010602011771号