Pandas 时间序列 - DateOffset 对象

呆鸟云:“书接上文《Pandas 时间序列 - 日期时间索引》,本篇讲的主要是日期偏移,就是 标题里的 DateOffset。这里呆鸟强调一下,文章光看不行,一定要敲代码,呆鸟就是左右分屏,一边放文章,一边放 Jupyter Notebook,一段一段代码敲出来,看每一段运行的结果,这样能更好理解代码的含义,才能学的更扎实。还有,官档精译系列是全面基础知识,百问百答系列是实战技巧,两个系列要配合着学才好。”

今天换个浅色的代码主题。

呆鸟又云:“感谢大家支持,上一篇《三分钟告诉你 1575119387982 是什么?》” 的在看突破了 50+。呆鸟辛苦翻译,写文,公号也没留言功能,看到在看,才知道有多少读者对呆鸟文章的认可,在看多, 就是读者对呆鸟的鼓励,就更有动力继续下去,所以继续求在看,希望这篇也能上50+。”

[TOC]

上例中,频率字符串(如,D)用于定义指定的频率:

频率字符串表示的是 DateOffset 对象及其子类。DateOffset 类似于时间差 Timedelta ,但遵循指定的日历日规则。例如,Timedelta 表示的每日时间差一直都是 24 小时,而 DateOffset 的每日偏移量则是与下一天相同的时间差,使用夏时制时,每日偏移时间有可能是 23 或 24 小时,甚至还有可能是 25 小时。不过,DateOffset 子类只能是等于或小于小时的时间单位(HourMinuteSecondMilliMicroNano),操作类似于 Timedelta 及对应的绝对时间。

DateOffset 基础操作类似于 dateutil.relativedeltarelativedelta 文档),可按指定的日历日时间段偏移日期时间。可用算数运算符(+)或 apply 方法执行日期偏移操作。

# 指定包含夏时制变迁的某天
In [141]: ts = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki')

# 对应的绝对时间
In [142]: ts + pd.Timedelta(days=1)
Out[142]: Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')

# 对应的日历时间
In [143]: ts + pd.DateOffset(days=1)
Out[143]: Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')

In [144]: friday = pd.Timestamp('2018-01-05')

In [145]: friday.day_name()
Out[145]: 'Friday'

# 与两个工作日相加(星期五 --> 星期二)
In [146]: two_business_days = 2 * pd.offsets.BDay()

In [147]: two_business_days.apply(friday)
Out[147]: Timestamp('2018-01-09 00:00:00')

In [148]: friday + two_business_days
Out[148]: Timestamp('2018-01-09 00:00:00')

In [149]: (friday + two_business_days).day_name()
Out[149]: 'Tuesday'

大多数 DateOffset 都支持频率字符串或偏移别名,可用作 freq 关键字参数。有效的日期偏移及频率字符串如下:

日期偏移量 频率字符串 说明
DateOffset 通用偏移类,默认为一个日历日
BDayBusinessDay 'B' 工作日
CDayCustomBusinessDay 'C' 自定义工作日
Week 'W' 一周,可选周内固定某日
WeekOfMonth 'WOM' 每月第几周的第几天
LastWeekOfMonth 'LWOM' 每月最后一周的第几天
MonthEnd 'M' 日历日月末
MonthBegin 'MS' 日历日月初
BMonthEndBusinessMonthEnd 'BM' 工作日月末
BMonthBeginBusinessMonthBegin 'BMS' 工作日月初
CBMonthEndCustomBusinessMonthEnd 'CBM' 自定义工作日月末
CBMonthBeginCustomBusinessMonthBegin 'CBMS' 自定义工作日月初
SemiMonthEnd 'SM' 某月第 15 天(或其它半数日期)与日历日月末
SemiMonthBegin 'SMS' 日历日月初与第 15 天(或其它半数日期)
QuarterEnd 'Q' 日历日季末
QuarterBegin 'QS' 日历日季初
BQuarterEnd 'BQ 工作日季末
BQuarterBegin 'BQS' 工作日季初
FY5253Quarter 'REQ' 零售季,又名 52-53 周
YearEnd 'A' 日历日年末
YearBegin 'AS''BYS' 日历日年初
BYearEnd 'BA' 工作日年末
BYearBegin 'BAS' 工作日年初
FY5253 'RE' 零售年(又名 52-53 周)
Easter 复活节假日
BusinessHour 'BH' 工作小时
CustomBusinessHour 'CBH' 自定义工作小时
Day 'D' 一天
Hour 'H' 一小时
Minute 'T''min' 一分钟
Second 'S' 一秒
Milli 'L''ms' 一毫秒
Micro 'U''us' 一微秒
Nano 'N' 一纳秒

DateOffset 还支持 rollforward()rollback() 方法,按偏移量把某一日期向前向后移动至有效偏移日期。例如,工作日偏移滚动日期时会跳过周末(即,星期六与星期日),直接到星期一,因为工作日偏移针对的是工作日。

In [150]: ts = pd.Timestamp('2018-01-06 00:00:00')

In [151]: ts.day_name()
Out[151]: 'Saturday'

# 工作时间的有效偏移日期为星期一至星期五
In [152]: offset = pd.offsets.BusinessHour(start='09:00')

# 向前偏移到最近的工作日,即星期一
In [153]: offset.rollforward(ts)
Out[153]: Timestamp('2018-01-08 09:00:00')

# 向前偏移至最近的工作日,同时,小时也相应增加了
In [154]: ts + offset
Out[154]: Timestamp('2018-01-08 10:00:00')

这些操作默认保存时间(小时、分钟等)信息。normalize() 可以把时间重置为午夜零点,是否应用此操作,取决于是否需要保留时间信息。

In [155]: ts = pd.Timestamp('2014-01-01 09:00')

In [156]: day = pd.offsets.Day()

In [157]: day.apply(ts)
Out[157]: Timestamp('2014-01-02 09:00:00')

In [158]: day.apply(ts).normalize()
Out[158]: Timestamp('2014-01-02 00:00:00')

In [159]: ts = pd.Timestamp('2014-01-01 22:00')

In [160]: hour = pd.offsets.Hour()

In [161]: hour.apply(ts)
Out[161]: Timestamp('2014-01-01 23:00:00')

In [162]: hour.apply(ts).normalize()
Out[162]: Timestamp('2014-01-01 00:00:00')

In [163]: hour.apply(pd.Timestamp("2014-01-01 23:30")).normalize()
Out[163]: Timestamp('2014-01-02 00:00:00')

参数偏移

偏移量支持参数,可以让不同操作生成不同结果。例如,Week 偏移生成每周数据时支持 weekday 参数,生成日期始终位于一周中的指定日期。

In [164]: d = datetime.datetime(2008, 8, 18, 9, 0)

In [165]: d
Out[165]: datetime.datetime(2008, 8, 18, 9, 0)

In [166]: d + pd.offsets.Week()
Out[166]: Timestamp('2008-08-25 09:00:00')

In [167]: d + pd.offsets.Week(weekday=4)
Out[167]: Timestamp('2008-08-22 09:00:00')

In [168]: (d + pd.offsets.Week(weekday=4)).weekday()
Out[168]: 4

In [169]: d - pd.offsets.Week()
Out[169]: Timestamp('2008-08-11 09:00:00')

加减法也支持 normalize 选项。

In [170]: d + pd.offsets.Week(normalize=True)
Out[170]: Timestamp('2008-08-25 00:00:00')

In [171]: d - pd.offsets.Week(normalize=True)
Out[171]: Timestamp('2008-08-11 00:00:00')

YearEnd 也支持参数,如 month 参数,用于指定月份 。

In [172]: d + pd.offsets.YearEnd()
Out[172]: Timestamp('2008-12-31 09:00:00')

In [173]: d + pd.offsets.YearEnd(month=6)
Out[173]: Timestamp('2009-06-30 09:00:00')

SeriesDatetimeIndex 偏移

可以为 SeriesDatetimeIndex 里的每个元素应用偏移。

In [174]: rng = pd.date_range('2012-01-01', '2012-01-03')

In [175]: s = pd.Series(rng)

In [176]: rng
Out[176]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[ns]', freq='D')

In [177]: rng + pd.DateOffset(months=2)
Out[177]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[ns]', freq='D')

In [178]: s + pd.DateOffset(months=2)
Out[178]: 
0   2012-03-01
1   2012-03-02
2   2012-03-03
dtype: datetime64[ns]

In [179]: s - pd.DateOffset(months=2)
Out[179]: 
0   2011-11-01
1   2011-11-02
2   2011-11-03
dtype: datetime64[ns]

如果偏移直接映射 TimedeltaDayHourMinuteSecondMicroMilliNano),则该偏移与 Timedelta 的使用方式完全一样。参阅时间差 - Timedelta,查看更多示例。

In [180]: s - pd.offsets.Day(2)
Out[180]: 
0   2011-12-30
1   2011-12-31
2   2012-01-01
dtype: datetime64[ns]

In [181]: td = s - pd.Series(pd.date_range('2011-12-29', '2011-12-31'))

In [182]: td
Out[182]: 
0   3 days
1   3 days
2   3 days
dtype: timedelta64[ns]

In [183]: td + pd.offsets.Minute(15)
Out[183]: 
0   3 days 00:15:00
1   3 days 00:15:00
2   3 days 00:15:00
dtype: timedelta64[ns]

注意,某些偏移量(如 BQuarterEnd)不支持矢量操作,即使可以执行运算,速度也非常慢,并可能显示 PerformanceWaring(性能警告)。

In [184]: rng + pd.offsets.BQuarterEnd()
Out[184]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq='D')

自定义工作日

CdayCustomBusinessDay 类可以参数化 BusinessDay 类,用于创建支持本地周末与传统节假日的自定义工作日历。

下面这个例子就很有意思,知道吗?埃及的周末是星期五与星期六。

In [185]: weekmask_egypt = 'Sun Mon Tue Wed Thu'


# 下面是 2012 - 2014 年的五一劳动节
In [186]: holidays = ['2012-05-01',
   .....:             datetime.datetime(2013, 5, 1),
   .....:             np.datetime64('2014-05-01')]
   .....: 

In [187]: bday_egypt = pd.offsets.CustomBusinessDay(holidays=holidays,
   .....:                                           weekmask=weekmask_egypt)
   .....: 

In [188]: dt = datetime.datetime(2013, 4, 30)

In [189]: dt + 2 * bday_egypt
Out[189]: Timestamp('2013-05-05 00:00:00')

下列代码实现了日期与工作日之间的映射关系。

In [190]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)

In [191]: pd.Series(dts.weekday, dts).map(
   .....:     pd.Series('Mon Tue Wed Thu Fri Sat Sun'.split()))
   .....: 
Out[191]: 
2013-04-30    Tue
2013-05-02    Thu
2013-05-05    Sun
2013-05-06    Mon
2013-05-07    Tue
Freq: C, dtype: object

节日日历支持节假日列表。更多信息,请参阅节日日历文档。

In [192]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [193]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())

# 马丁路德金纪念日前的星期五
In [194]: dt = datetime.datetime(2014, 1, 17)

# 马丁路德金纪念日后的星期二,因为星期一放假,所以跳过了
In [195]: dt + bday_us
Out[195]: Timestamp('2014-01-21 00:00:00')

遵循节日日历规则的月偏移可以用正常方式定义。

In [196]: bmth_us = pd.offsets.CustomBusinessMonthBegin(
   .....:     calendar=USFederalHolidayCalendar())
   .....: 

# 跳过新年
In [197]: dt = datetime.datetime(2013, 12, 17)

In [198]: dt + bmth_us
Out[198]: Timestamp('2014-01-02 00:00:00')

# 定义带自定义偏移的日期索引
In [199]: pd.date_range(start='20100101', end='20120101', freq=bmth_us)
Out[199]: 
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
               '2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
               '2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
               '2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
               '2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
               '2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
              dtype='datetime64[ns]', freq='CBMS')

注意:频率字符串 'C' 验证 CustomBusinessDay 日期偏移 调用,注意,CustomBusinessDay 可实现参数化,CustomBusinessDay 实例会各不相同,且频率字符串 'C' 无法识别这个问题。用户应确保应用里调用的频率字符串 'C' 的一致性 。

工作时间

BusinessHour 表示 BusinessDay 基础上的工作时间,用于指定开始与结束工作时间。

BusinessHour 默认的工作时间是 9:00 - 17:00。BusinessHour 加法以小时频率增加 Timestamp 。如果目标 Timestamp 超出了一小时,则要先移动到下一个工作小时,再行增加。如果超过了当日工作时间的范围,剩下的时间则添加到下一个工作日。

In [200]: bh = pd.offsets.BusinessHour()

In [201]: bh
Out[201]: <BusinessHour: BH=09:00-17:00>

# 2014 年 8 月 1 日是星期五
In [202]: pd.Timestamp('2014-08-01 10:00').weekday()
Out[202]: 4

In [203]: pd.Timestamp('2014-08-01 10:00') + bh
Out[203]: Timestamp('2014-08-01 11:00:00')

# 下例等同于: pd.Timestamp('2014-08-01 09:00') + bh
In [204]: pd.Timestamp('2014-08-01 08:00') + bh
Out[204]: Timestamp('2014-08-01 10:00:00')

# 如果计算结果为当日下班时间,则转移到下一个工作日的上班时间
In [205]: pd.Timestamp('2014-08-01 16:00') + bh
Out[205]: Timestamp('2014-08-04 09:00:00')

# 剩下的时间也会添加到下一天
In [206]: pd.Timestamp('2014-08-01 16:30') + bh
Out[206]: Timestamp('2014-08-04 09:30:00')

# 添加 2 个工作小时
In [207]: pd.Timestamp('2014-08-01 10:00') + pd.offsets.BusinessHour(2)
Out[207]: Timestamp('2014-08-01 12:00:00')

# 减掉 3 个工作小时
In [208]: pd.Timestamp('2014-08-01 10:00') + pd.offsets.BusinessHour(-3)
Out[208]: Timestamp('2014-07-31 15:00:00')

还可以用关键字指定 startend 时间。参数必须是hour:minute 格式的字符串或 datetime.time 实例。把秒、微秒、纳秒设置为工作时间会导致 ValueError

In [209]: bh = pd.offsets.BusinessHour(start='11:00', end=datetime.time(20, 0))

In [210]: bh
Out[210]: <BusinessHour: BH=11:00-20:00>

In [211]: pd.Timestamp('2014-08-01 13:00') + bh
Out[211]: Timestamp('2014-08-01 14:00:00')

In [212]: pd.Timestamp('2014-08-01 09:00') + bh
Out[212]: Timestamp('2014-08-01 12:00:00')

In [213]: pd.Timestamp('2014-08-01 18:00') + bh
Out[213]: Timestamp('2014-08-01 19:00:00')

start 时间晚于 end 时间表示夜班工作时间。此时,工作时间将从午夜延至第二天。工作时间是否有效取决于该时间是否开始于有效的 BusinessDay

In [214]: bh = pd.offsets.BusinessHour(start='17:00', end='09:00')

In [215]: bh
Out[215]: <BusinessHour: BH=17:00-09:00>

In [216]: pd.Timestamp('2014-08-01 17:00') + bh
Out[216]: Timestamp('2014-08-01 18:00:00')

In [217]: pd.Timestamp('2014-08-01 23:00') + bh
Out[217]: Timestamp('2014-08-02 00:00:00')

# 虽然 2014 年 8 月 2 日是星期六,
# 但因为工作时间开始于星期五,因此,也是有效的
In [218]: pd.Timestamp('2014-08-02 04:00') + bh
Out[218]: Timestamp('2014-08-02 05:00:00')


# 虽然 2014 年 8 月 4 日是星期一,
# 但开始时间是星期日,因此,超出了工作时间
In [219]: pd.Timestamp('2014-08-04 04:00') + bh
Out[219]: Timestamp('2014-08-04 18:00:00')

BusinessHour.rollforwardrollback 操作将前滚至下一天的上班时间,或回滚至前一天的下班时间。与其它偏移量不同,BusinessHour.rollforward 输出与 apply 定义不同的结果。

这是因为一天工作时间的结束等同于第二天工作时间的开始。默认情况下,工作时间为 9:00 - 17:00,Pandas 认为 2014-08-01 17:002014-08-04 09:00 之间的时间间隔为 0 分钟。

# 把时间戳回滚到前一天的下班时间
In [220]: pd.offsets.BusinessHour().rollback(pd.Timestamp('2014-08-02 15:00'))
Out[220]: Timestamp('2014-08-01 17:00:00')

# 把时间戳前滚到下一个工作日的上班时间
In [221]: pd.offsets.BusinessHour().rollforward(pd.Timestamp('2014-08-02 15:00'))
Out[221]: Timestamp('2014-08-04 09:00:00')

# 等同于:BusinessHour().apply(pd.Timestamp('2014-08-01 17:00'))
# 与 BusinessHour().apply(pd.Timestamp('2014-08-04 09:00'))
In [222]: pd.offsets.BusinessHour().apply(pd.Timestamp('2014-08-02 15:00'))
Out[222]: Timestamp('2014-08-04 10:00:00')

# 工作日的结果(仅供参考)
In [223]: pd.offsets.BusinessHour().rollforward(pd.Timestamp('2014-08-02'))
Out[223]: Timestamp('2014-08-04 09:00:00')

# 等同于 BusinessDay().apply(pd.Timestamp('2014-08-01'))
# 等同于 rollforward 因为工作日不会重叠
In [224]: pd.offsets.BusinessHour().apply(pd.Timestamp('2014-08-02'))
Out[224]: Timestamp('2014-08-04 10:00:00')

BusinessHour 把星期六与星期日当成假日。CustomBusinessHour 可以把节假日设为工作时间,详见下文。

自定义工作时间

0.18.1 版新增

CustomBusinessHourBusinessHourCustomBusinessDay 的混合体,可以指定任意节假日。除了跳过自定义节假日之外,CustomBusinessHour 的运作方式与 BusinessHour 一样。

In [225]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [226]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())

# 马丁路德金纪念日之前的星期五
In [227]: dt = datetime.datetime(2014, 1, 17, 15)

In [228]: dt + bhour_us
Out[228]: Timestamp('2014-01-17 16:00:00')

# 跳至马丁路德金纪念日之后的星期二,星期一过节,所以跳过了
In [229]: dt + bhour_us * 2
Out[229]: Timestamp('2014-01-21 09:00:00')

BusinessHour 支持与 CustomBusinessDay 一样的关键字参数。

In [230]: bhour_mon = pd.offsets.CustomBusinessHour(start='10:00',
   .....:                                           weekmask='Tue Wed Thu Fri')
   .....: 

# 跳过了星期一,因为星期一过节,工作时间从 10 点开始
In [231]: dt + bhour_mon * 2
Out[231]: Timestamp('2014-01-21 10:00:00')

偏移量别名

时间序列频率的字符串别名在这里叫偏移量别名

别名 说明
B 工作日频率
C 自定义工作日频率
D 日历日频率
W 周频率
M 月末频率
SM 半月末频率(15 号与月末)
BM 工作日月末频率
CBM 自定义工作日月末频率
MS 月初频率
SMS 半月初频率(1 号与 15 号)
BMS 工作日月初频率
CBMS 自定义工作日月初频率
Q 季末频率
BQ 工作日季末频率
QS 季初频率
BQS 工作日季初频率
A, Y 年末频率
BA, BY 工作日年末频率
AS, YS 年初频率
BAS, BYS 工作日年初频率
BH 工作时间频率
H 小时频率
T, min 分钟频率
S 秒频率
L, ms 毫秒
U, us 微秒
N 纳秒

别名组合

如前说述,别名与偏移量实例在绝大多数函数里可以互换:

In [232]: pd.date_range(start, periods=5, freq='B')
Out[232]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[ns]', freq='B')

In [233]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[233]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[ns]', freq='B')

可以组合日与当日偏移量。

In [234]: pd.date_range(start, periods=10, freq='2h20min')
Out[234]: 
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
               '2011-01-01 04:40:00', '2011-01-01 07:00:00',
               '2011-01-01 09:20:00', '2011-01-01 11:40:00',
               '2011-01-01 14:00:00', '2011-01-01 16:20:00',
               '2011-01-01 18:40:00', '2011-01-01 21:00:00'],
              dtype='datetime64[ns]', freq='140T')

In [235]: pd.date_range(start, periods=10, freq='1D10U')
Out[235]: 
DatetimeIndex([       '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
               '2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
               '2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
               '2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
               '2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
              dtype='datetime64[ns]', freq='86400000010U')

锚定偏移量

可以指定某些频率的锚定后缀:

别名 说明
W-SUN 周频率(星期日),与 “W” 相同
W-MON 周频率(星期一)
W-TUE 周频率(星期二)
W-WED 周频率(星期三)
W-THU 周频率(星期四)
W-FRI 周频率(星期五)
W-SAT 周频率(星期六)
(B)Q(S)-DEC 季频率,该年结束于十二月,与 “Q” 相同
(B)Q(S)-JAN 季频率,该年结束于一月
(B)Q(S)-FEB 季频率,该年结束于二月
(B)Q(S)-MAR 季频率,该年结束于三月
(B)Q(S)-APR 季频率,该年结束于四月
(B)Q(S)-MAY 季频率,该年结束于五月
(B)Q(S)-JUN 季频率,该年结束于六月
(B)Q(S)-JUL 季频率,该年结束于七月
(B)Q(S)-AUG 季频率,该年结束于八月
(B)Q(S)-SEP 季频率,该年结束于九月
(B)Q(S)-OCT 季频率,该年结束于十月
(B)Q(S)-NOV 季频率,该年结束于十一月
(B)A(S)-DEC 年频率,锚定结束于十二月,与 “A” 相同
(B)A(S)-JAN 年频率,锚定结束于一月
(B)A(S)-FEB 年频率,锚定结束于二月
(B)A(S)-MAR 年频率,锚定结束于三月
(B)A(S)-APR 年频率,锚定结束于四月
(B)A(S)-MAY 年频率,锚定结束于五月
(B)A(S)-JUN 年频率,锚定结束于六月
(B)A(S)-JUL 年频率,锚定结束于七月
(B)A(S)-AUG 年频率,锚定结束于八月
(B)A(S)-SEP 年频率,锚定结束于九月
(B)A(S)-OCT 年频率,锚定结束于十月
(B)A(S)-NOV 年频率,锚定结束于十一月

这些别名可以用作 date_rangebdate_rangeDatetimeIndex 及其它时间序列函数的参数。

锚定偏移量的含义

对于偏移量锚定于开始或结束指定频率(MonthEndMonthBeginWeekEnd 等)下列规则应用于前滚与后滚。

n 不为 0 时,如果给定日期不是锚定日期,将寻找下一个或上一个锚点,并向前或向后移动 |n|-1 步。

In [236]: pd.Timestamp('2014-01-02') + pd.offsets.MonthBegin(n=1)
Out[236]: Timestamp('2014-02-01 00:00:00')

In [237]: pd.Timestamp('2014-01-02') + pd.offsets.MonthEnd(n=1)
Out[237]: Timestamp('2014-01-31 00:00:00')

In [238]: pd.Timestamp('2014-01-02') - pd.offsets.MonthBegin(n=1)
Out[238]: Timestamp('2014-01-01 00:00:00')

In [239]: pd.Timestamp('2014-01-02') - pd.offsets.MonthEnd(n=1)
Out[239]: Timestamp('2013-12-31 00:00:00')

In [240]: pd.Timestamp('2014-01-02') + pd.offsets.MonthBegin(n=4)
Out[240]: Timestamp('2014-05-01 00:00:00')

In [241]: pd.Timestamp('2014-01-02') - pd.offsets.MonthBegin(n=4)
Out[241]: Timestamp('2013-10-01 00:00:00')

如果给定日期是锚定日期,则向前(或向后)移动 |n| 个点。

In [242]: pd.Timestamp('2014-01-01') + pd.offsets.MonthBegin(n=1)
Out[242]: Timestamp('2014-02-01 00:00:00')

In [243]: pd.Timestamp('2014-01-31') + pd.offsets.MonthEnd(n=1)
Out[243]: Timestamp('2014-02-28 00:00:00')

In [244]: pd.Timestamp('2014-01-01') - pd.offsets.MonthBegin(n=1)
Out[244]: Timestamp('2013-12-01 00:00:00')

In [245]: pd.Timestamp('2014-01-31') - pd.offsets.MonthEnd(n=1)
Out[245]: Timestamp('2013-12-31 00:00:00')

In [246]: pd.Timestamp('2014-01-01') + pd.offsets.MonthBegin(n=4)
Out[246]: Timestamp('2014-05-01 00:00:00')

In [247]: pd.Timestamp('2014-01-31') - pd.offsets.MonthBegin(n=4)
Out[247]: Timestamp('2013-10-01 00:00:00')

n=0 时,如果日期在锚点,则不移动,否则将前滚至下一个锚点。

In [248]: pd.Timestamp('2014-01-02') + pd.offsets.MonthBegin(n=0)
Out[248]: Timestamp('2014-02-01 00:00:00')

In [249]: pd.Timestamp('2014-01-02') + pd.offsets.MonthEnd(n=0)
Out[249]: Timestamp('2014-01-31 00:00:00')

In [250]: pd.Timestamp('2014-01-01') + pd.offsets.MonthBegin(n=0)
Out[250]: Timestamp('2014-01-01 00:00:00')

In [251]: pd.Timestamp('2014-01-31') + pd.offsets.MonthEnd(n=0)
Out[251]: Timestamp('2014-01-31 00:00:00')

假日与节日日历

用假日与日历可以轻松定义 CustomBusinessDay 假日规则,或其它分析所需的预设假日。AbstractHolidayCalendar 类支持所有返回假日列表的方法,并且仅需在指定假日日历类里定义 rulesstart_dateend_date 类属性决定了假日的范围。该操作会覆盖 AbstractHolidayCalendar 类,适用于所有日历子类。USFederalHolidayCalendar 是仅有的假日日历,主要用作开发其它日历的示例。

固定日期的假日,如美国阵亡将士纪念日或美国国庆日(7 月 4 日),取决于该假日是否是在周末,可以使用以下规则:

规则 说明
nearest_workday 把星期六移至星期五,星期日移至星期一
sunday_to_monday 星期六紧接着星期一
next_monday_or_tuesday 把星期六移至星期一,并把星期日/星期一移至星期二
previous_friday 把星期六与星期日移至上一个星期五
next_monday 把星期六与星期日移至下一个星期一

下例展示如何定义假日与假日日历:

In [252]: from pandas.tseries.holiday import Holiday, USMemorialDay,\
   .....:     AbstractHolidayCalendar, nearest_workday, MO
   .....: 

In [253]: class ExampleCalendar(AbstractHolidayCalendar):
   .....:     rules = [
   .....:         USMemorialDay,
   .....:         Holiday('July 4th', month=7, day=4, observance=nearest_workday),
   .....:         Holiday('Columbus Day', month=10, day=1,
   .....:                 offset=pd.DateOffset(weekday=MO(2)))]
   .....: 

In [254]: cal = ExampleCalendar()

In [255]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[255]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)

注意: weekday=MO(2)2 * Week(weekday=2) 相同。

用这个日历创建索引,或计算偏移量,将跳过周末与假日(如,纪念日与国庆节)。下列代码用 ExampleCalendar 设定自定义工作日偏移量。至于其它偏移量,可以用于创建 DatetimeIndex 或添加到 datetimeTimestamp 对象。

In [256]: pd.date_range(start='7/1/2012', end='7/10/2012',
   .....:               freq=pd.offsets.CDay(calendar=cal)).to_pydatetime()
   .....: 
Out[256]: 
array([datetime.datetime(2012, 7, 2, 0, 0),
       datetime.datetime(2012, 7, 3, 0, 0),
       datetime.datetime(2012, 7, 5, 0, 0),
       datetime.datetime(2012, 7, 6, 0, 0),
       datetime.datetime(2012, 7, 9, 0, 0),
       datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)

In [257]: offset = pd.offsets.CustomBusinessDay(calendar=cal)

In [258]: datetime.datetime(2012, 5, 25) + offset
Out[258]: Timestamp('2012-05-29 00:00:00')

In [259]: datetime.datetime(2012, 7, 3) + offset
Out[259]: Timestamp('2012-07-05 00:00:00')

In [260]: datetime.datetime(2012, 7, 3) + 2 * offset
Out[260]: Timestamp('2012-07-06 00:00:00')

In [261]: datetime.datetime(2012, 7, 6) + offset
Out[261]: Timestamp('2012-07-09 00:00:00')

AbstractHolidayCalendar 的类属性 start_dateend_date 定义日期范围。默认值如下:

In [262]: AbstractHolidayCalendar.start_date
Out[262]: Timestamp('1970-01-01 00:00:00')

In [263]: AbstractHolidayCalendar.end_date
Out[263]: Timestamp('2030-12-31 00:00:00')

这两个日期可以用 datetimeTimestamp字符串 修改。

In [264]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)

In [265]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)

In [266]: cal.holidays()
Out[266]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)

get_calender 函数通过日历名称访问日历,返回的是日历实例。任意导入的日历都自动适用于此函数。同时,HolidayCalendarFactory 还提供了一个创建日历组合或含附加规则日历的简易接口。

In [267]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory,\
   .....:     USLaborDay
   .....: 

In [268]: cal = get_calendar('ExampleCalendar')

In [269]: cal.rules
Out[269]: 
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7f2460862c20>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]

In [270]: new_cal = HolidayCalendarFactory('NewExampleCalendar', cal, USLaborDay)

In [271]: new_cal.rules
Out[271]: 
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO(+1)>),
 Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7f2460862c20>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]

Pandas 百问百答系列

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