时间序列与日期用法 依托 NumPy 的 datetime64
、timedelta64
等数据类型,Pandas 可以处理各种时间序列数据,还能调用 scikits.timeseries
等 Python 支持库的时间序列功能。
Pandas 支持以下操作:
解析 时间字符串
、np.datetime64
、datetime.datetime
等多种时间序列数据。
1 2 3 4 5 6 7 8 In [1 ]: import datetime In [2 ]: dti = pd.to_datetime(['1/1/2018' , np.datetime64('2018-01-01' ), ...: datetime.datetime(2018 , 1 , 1 )]) ...: In [3 ]: dti Out[3 ]: DatetimeIndex(['2018-01-01' , '2018-01-01' , '2018-01-01' ], dtype='datetime64[ns]' , freq=None )
生成 DatetimeIndex
、TimedeltaIndex
、PeriodIndex
等定频日期与时间段序列。
1 2 3 4 5 6 7 In [4 ]: dti = pd.date_range('2018-01-01' , periods=3 , freq='H' ) In [5 ]: dti Out[5 ]: DatetimeIndex(['2018-01-01 00:00:00' , '2018-01-01 01:00:00' , '2018-01-01 02:00:00' ], dtype='datetime64[ns]' , freq='H' )
处理、转换带时区的日期时间数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 In [6 ]: dti = dti.tz_localize('UTC' ) In [7 ]: dti Out[7 ]: DatetimeIndex(['2018-01-01 00:00:00+00:00' , '2018-01-01 01:00:00+00:00' , '2018-01-01 02:00:00+00:00' ], dtype='datetime64[ns, UTC]' , freq='H' ) In [8 ]: dti.tz_convert('US/Pacific' ) Out[8 ]: DatetimeIndex(['2017-12-31 16:00:00-08:00' , '2017-12-31 17:00:00-08:00' , '2017-12-31 18:00:00-08:00' ], dtype='datetime64[ns, US/Pacific]' , freq='H' )
按指定频率重采样,并转换为时间序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 In [9 ]: idx = pd.date_range('2018-01-01' , periods=5 , freq='H' ) In [10 ]: ts = pd.Series(range (len (idx)), index=idx) In [11 ]: ts Out[11 ]: 2018 -01-01 00 :00 :00 0 2018 -01-01 01:00 :00 1 2018 -01-01 02:00 :00 2 2018 -01-01 03:00 :00 3 2018 -01-01 04:00 :00 4 Freq: H, dtype: int64 In [12 ]: ts.resample('2H' ).mean() Out[12 ]: 2018 -01-01 00 :00 :00 0.5 2018 -01-01 02:00 :00 2.5 2018 -01-01 04:00 :00 4.0 Freq: 2H, dtype: float64
用绝对或相对时间差计算日期与时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [13 ]: friday = pd.Timestamp('2018-01-05' ) In [14 ]: friday.day_name() Out[14 ]: 'Friday' In [15 ]: saturday = friday + pd.Timedelta('1 day' ) In [16 ]: saturday.day_name() Out[16 ]: 'Saturday' In [17 ]: monday = friday + pd.offsets.BDay() In [18 ]: monday.day_name() Out[18 ]: 'Monday'
Pandas 提供了一组精悍、实用的工具集以完成上述操作。
时间序列纵览 Pandas 支持 4 种常见时间概念:
日期时间(Datetime):带时区的日期时间,类似于标准库的 datetime.datetime
。
时间差(Timedelta):绝对时间周期,类似于标准库的 datetime.timedelta
。
时间段(Timespan):在某一时点以指定频率定义的时间跨度。
日期偏移(Dateoffset):与日历运算对应的时间段,类似于 dateutil
的 dateutil.relativedelta.relativedelta
。
时间概念 标量类 数组类 Pandas 数据类型 主要构建方法 Date times Timestamp
DatetimeIndex
datetime64[ns]
或 datetime64[ns,tz]
to_datetime
或 date_range
Time deltas Timedelta
TimedeltaIndex
timedelta64[ns]
to_timedelta
或 timedelta_range
Time spans Period
PeriodIndex
period[freq]
Period
或 period_range
Date offsets DateOffset
None
None
DateOffset
一般情况下,时间序列主要是 Series
或 DataFrame
的时间型索引,可以用时间元素进行操控。
1 2 3 4 5 6 In [19 ]: pd.Series(range (3 ), index=pd.date_range('2000' , freq='D' , periods=3 )) Out[19 ]: 2000 -01-01 0 2000 -01-02 1 2000 -01-03 2 Freq: D, dtype: int64
当然,Series
与 DataFrame
也可以直接把时间序列当成数据。
1 2 3 4 5 6 In [20 ]: pd.Series(pd.date_range('2000' , freq='D' , periods=3 )) Out[20 ]: 0 2000 -01-011 2000 -01-022 2000 -01-03dtype: datetime64[ns]
Series
与 DataFrame
提供了 datetime
、timedelta
、Period
扩展类型与专有用法,不过,Dateoffset
则保存为 object
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 In [21 ]: pd.Series(pd.period_range('1/1/2011' , freq='M' , periods=3 )) Out[21 ]: 0 2011 -011 2011 -022 2011 -03dtype: period[M] In [22 ]: pd.Series([pd.DateOffset(1 ), pd.DateOffset(2 )]) Out[22 ]: 0 <DateOffset>1 <2 * DateOffsets>dtype: object In [23 ]: pd.Series(pd.date_range('1/1/2011' , freq='M' , periods=3 )) Out[23 ]: 0 2011 -01-31 1 2011 -02-28 2 2011 -03-31 dtype: datetime64[ns]
Pandas 用 NaT
表示日期时间、时间差及时间段的空值,代表了缺失日期或空日期的值,类似于浮点数的 np.nan
。
1 2 3 4 5 6 7 8 9 10 11 12 In [24 ]: pd.Timestamp(pd.NaT) Out[24 ]: NaT In [25 ]: pd.Timedelta(pd.NaT) Out[25 ]: NaT In [26 ]: pd.Period(pd.NaT) Out[26 ]: NaT In [27 ]: pd.NaT == pd.NaT Out[27 ]: False
时间戳 vs. 时间段 时间戳是最基本的时间序列数据,用于把数值与时点关联在一起。Pandas 对象通过时间戳调用时点数据。
1 2 3 4 5 6 7 8 In [28 ]: pd.Timestamp(datetime.datetime(2012 , 5 , 1 )) Out[28 ]: Timestamp('2012-05-01 00:00:00' ) In [29 ]: pd.Timestamp('2012-05-01' ) Out[29 ]: Timestamp('2012-05-01 00:00:00' ) In [30 ]: pd.Timestamp(2012 , 5 , 1 ) Out[30 ]: Timestamp('2012-05-01 00:00:00' )
不过,大多数情况下,用时间段改变变量更自然。Period
表示的时间段更直观,还可以用日期时间格式的字符串进行推断。
示例如下:
1 2 3 4 5 In [31 ]: pd.Period('2011-01' ) Out[31 ]: Period('2011-01' , 'M' ) In [32 ]: pd.Period('2012-05' , freq='D' ) Out[32 ]: Period('2012-05-01' , 'D' )
Timestamp
与 Period
可以用作索引。作为索引的 Timestamp
与 Period
列表则被强制转换为对应的 DatetimeIndex
与 PeriodIndex
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 In [33 ]: dates = [pd.Timestamp('2012-05-01' ), ....: pd.Timestamp('2012-05-02' ), ....: pd.Timestamp('2012-05-03' )] ....: In [34 ]: ts = pd.Series(np.random.randn(3 ), dates) In [35 ]: type (ts.index) Out[35 ]: pandas.core.indexes.datetimes.DatetimeIndex In [36 ]: ts.index Out[36 ]: DatetimeIndex(['2012-05-01' , '2012-05-02' , '2012-05-03' ], dtype='datetime64[ns]' , freq=None ) In [37 ]: ts Out[37 ]: 2012 -05-01 0.469112 2012 -05-02 -0.282863 2012 -05-03 -1.509059 dtype: float64 In [38 ]: periods = [pd.Period('2012-01' ), pd.Period('2012-02' ), pd.Period('2012-03' )] In [39 ]: ts = pd.Series(np.random.randn(3 ), periods) In [40 ]: type (ts.index) Out[40 ]: pandas.core.indexes.period.PeriodIndex In [41 ]: ts.index Out[41 ]: PeriodIndex(['2012-01' , '2012-02' , '2012-03' ], dtype='period[M]' , freq='M' ) In [42 ]: ts Out[42 ]: 2012 -01 -1.135632 2012 -02 1.212112 2012 -03 -0.173215 Freq: M, dtype: float64
Pandas 可以识别这两种表现形式,并在两者之间进行转化。Pandas 后台用 Timestamp
实例代表时间戳,用 DatetimeIndex
实例代表时间戳序列。Pandas 用 Period
对象表示符合规律的时间段标量值,用 PeriodIndex
表示时间段序列。未来版本会支持用任意起止时间实现不规律时间间隔。
转换时间戳 to_datetime
函数用于转换字符串、纪元式及混合的日期 Series
或日期列表。转换 Series
,返回具有相同索引的 Series
,日期时间列表被转换为 DatetimeIndex
:
1 2 3 4 5 6 7 8 9 In [43 ]: pd.to_datetime(pd.Series(['Jul 31, 2009' , '2010-01-10' , None ])) Out[43 ]: 0 2009 -07-31 1 2010 -01-10 2 NaTdtype: datetime64[ns] In [44 ]: pd.to_datetime(['2005/11/23' , '2010.12.31' ]) Out[44 ]: DatetimeIndex(['2005-11-23' , '2010-12-31' ], dtype='datetime64[ns]' , freq=None )
解析欧式日期(日-月-年),要用 dayfirst
关键字参数:
1 2 3 4 5 In [45 ]: pd.to_datetime(['04-01-2012 10:00' ], dayfirst=True ) Out[45 ]: DatetimeIndex(['2012-01-04 10:00:00' ], dtype='datetime64[ns]' , freq=None ) In [46 ]: pd.to_datetime(['14-01-2012' , '01-14-2012' ], dayfirst=True ) Out[46 ]: DatetimeIndex(['2012-01-14' , '2012-01-14' ], dtype='datetime64[ns]' , freq=None )
::: danger 警告
从上例可以看出,dayfirst
并不严苛,如果不能把第一个数解析为日 ,就会以 dayfirst
为 False
进行解析。
:::
to_datetime
转换单个字符串时,返回单个 Timestamp
。Timestamp
仅支持字符串输入,不支持 dayfirst
、format
等字符串解析选项,如果要使用这些选项,就要用 to_datetime
。
1 2 3 4 5 In [47 ]: pd.to_datetime('2010/11/12' ) Out[47 ]: Timestamp('2010-11-12 00:00:00' ) In [48 ]: pd.Timestamp('2010/11/12' ) Out[48 ]: Timestamp('2010-11-12 00:00:00' )
Pandas 还支持直接使用 DatetimeIndex
构建器:
1 2 In [49 ]: pd.DatetimeIndex(['2018-01-01' , '2018-01-03' , '2018-01-05' ]) Out[49 ]: DatetimeIndex(['2018-01-01' , '2018-01-03' , '2018-01-05' ], dtype='datetime64[ns]' , freq=None )
创建 DatetimeIndex
时,传递字符串 infer
可推断索引的频率。
1 2 In [50 ]: pd.DatetimeIndex(['2018-01-01' , '2018-01-03' , '2018-01-05' ], freq='infer' ) Out[50 ]: DatetimeIndex(['2018-01-01' , '2018-01-03' , '2018-01-05' ], dtype='datetime64[ns]' , freq='2D' )
提供格式参数 要实现精准转换,除了传递 datetime
字符串,还要指定 format
参数,指定此参数还可以加速转换速度。
1 2 3 4 5 In [51 ]: pd.to_datetime('2010/11/12' , format ='%Y/%m/%d' ) Out[51 ]: Timestamp('2010-11-12 00:00:00' ) In [52 ]: pd.to_datetime('12-11-2010 00:00' , format ='%d-%m-%Y %H:%M' ) Out[52 ]: Timestamp('2010-11-12 00:00:00' )
要了解更多 format
选项,请参阅 Python 日期时间文档 。
用多列组合日期时间 0.18.1 版新增。
Pandas 还可以把 DataFrame
里的整数或字符串列组合成 Timestamp Series
。
1 2 3 4 5 6 7 8 9 10 11 In [53 ]: df = pd.DataFrame({'year' : [2015 , 2016 ], ....: 'month' : [2 , 3 ], ....: 'day' : [4 , 5 ], ....: 'hour' : [2 , 3 ]}) ....: In [54 ]: pd.to_datetime(df) Out[54 ]: 0 2015 -02-04 02:00 :00 1 2016 -03-05 03:00 :00 dtype: datetime64[ns]
只传递组合所需的列也可以。
1 2 3 4 5 In [55 ]: pd.to_datetime(df[['year' , 'month' , 'day' ]]) Out[55 ]: 0 2015 -02-041 2016 -03-05dtype: datetime64[ns]
pd.to_datetime
查找列名里日期时间组件的标准名称,包括:
必填:year
、month
、day
可选:hour
、minute
、second
、millisecond
、microsecond
、nanosecond
无效数据 不可解析时,默认值 errors='raise'
会触发错误:
1 2 In [2 ]: pd.to_datetime(['2009/07/31' , 'asd' ], errors='raise' ) ValueError: Unknown string format
errors='ignore'
返回原始输入:
1 2 In [56 ]: pd.to_datetime(['2009/07/31' , 'asd' ], errors='ignore' ) Out[56 ]: Index(['2009/07/31' , 'asd' ], dtype='object' )
errors='coerce'
把无法解析的数据转换为 NaT
,即不是时间(Not a Time):
1 2 In [57 ]: pd.to_datetime(['2009/07/31' , 'asd' ], errors='coerce' ) Out[57 ]: DatetimeIndex(['2009-07-31' , 'NaT' ], dtype='datetime64[ns]' , freq=None )
纪元时间戳 Pandas 支持把整数或浮点数纪元时间转换为 Timestamp
与 DatetimeIndex
。鉴于 Timestamp
对象内部存储方式,这种转换的默认单位是纳秒。不过,一般都会用指定其它时间单位 unit
来存储纪元数据,纪元时间从 origin
参数指定的时点开始计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 In [58 ]: pd.to_datetime([1349720105 , 1349806505 , 1349892905 , ....: 1349979305 , 1350065705 ], unit='s' ) ....: Out[58 ]: DatetimeIndex(['2012-10-08 18:15:05' , '2012-10-09 18:15:05' , '2012-10-10 18:15:05' , '2012-10-11 18:15:05' , '2012-10-12 18:15:05' ], dtype='datetime64[ns]' , freq=None ) In [59 ]: pd.to_datetime([1349720105100 , 1349720105200 , 1349720105300 , ....: 1349720105400 , 1349720105500 ], unit='ms' ) ....: Out[59 ]: DatetimeIndex(['2012-10-08 18:15:05.100000' , '2012-10-08 18:15:05.200000' , '2012-10-08 18:15:05.300000' , '2012-10-08 18:15:05.400000' , '2012-10-08 18:15:05.500000' ], dtype='datetime64[ns]' , freq=None )
用带 tz
参数的纪元时间戳创建 Timestamp
或 DatetimeIndex
时,要先把纪元时间戳转化为 UTC,然后再把结果转换为指定时区。不过这种操作方式现在已经废弃 了,对于其它时区 Wall Time 里的纪元时间戳,建议先把纪元时间戳转换为无时区时间戳,再本地化时区。
1 2 3 4 5 In [60 ]: pd.Timestamp(1262347200000000000 ).tz_localize('US/Pacific' ) Out[60 ]: Timestamp('2010-01-01 12:00:00-0800' , tz='US/Pacific' ) In [61 ]: pd.DatetimeIndex([1262347200000000000 ]).tz_localize('US/Pacific' ) Out[61 ]: DatetimeIndex(['2010-01-01 12:00:00-08:00' ], dtype='datetime64[ns, US/Pacific]' , freq=None )
::: tip 注意
纪元时间取整到最近的纳秒。
:::
::: danger 警告
Python 浮点数 只精确到 15 位小数,因此,转换浮点纪元时间可能会导致不精准或失控的结果。转换过程中,免不了对高精度 Timestamp
取整,只有用 int64
等定宽类型才有可能实现极其精准的效果。
1 2 3 4 5 In [62 ]: pd.to_datetime([1490195805.433 , 1490195805.433502912 ], unit='s' ) Out[62 ]: DatetimeIndex(['2017-03-22 15:16:45.433000088' , '2017-03-22 15:16:45.433502913' ], dtype='datetime64[ns]' , freq=None ) In [63 ]: pd.to_datetime(1490195805433502912 , unit='ns' ) Out[63 ]: Timestamp('2017-03-22 15:16:45.433502912' )
:::
::: tip 参阅
应用 origin
参数
:::
把时间戳转换为纪元 反转上述操作,把 Timestamp
转换为 unix
纪元:
1 2 3 4 5 6 7 In [64 ]: stamps = pd.date_range('2012-10-08 18:15:05' , periods=4 , freq='D' ) In [65 ]: stamps Out[65 ]: DatetimeIndex(['2012-10-08 18:15:05' , '2012-10-09 18:15:05' , '2012-10-10 18:15:05' , '2012-10-11 18:15:05' ], dtype='datetime64[ns]' , freq='D' )
首先与纪元开始时点(1970 年 1 月 1 日午夜,UTC)相减,然后以 1 秒为时间单位(unit='1s'
)取底整除。
1 2 In [66 ]: (stamps - pd.Timestamp("1970-01-01" )) // pd.Timedelta('1s' ) Out[66 ]: Int64Index([1349720105 , 1349806505 , 1349892905 , 1349979305 ], dtype='int64' )
应用 origin
参数 0.20.0 版新增。
origin
参数可以指定 DatetimeIndex
的备选开始时点。例如,把1960-01-01
作为开始日期:
1 2 In [67 ]: pd.to_datetime([1 , 2 , 3 ], unit='D' , origin=pd.Timestamp('1960-01-01' )) Out[67 ]: DatetimeIndex(['1960-01-02' , '1960-01-03' , '1960-01-04' ], dtype='datetime64[ns]' , freq=None )
默认值为 origin='unix'
,即 1970-01-01 00:00:00
,一般把这个时点称为 unix 纪元
或 POSIX
时间。
1 2 In [68 ]: pd.to_datetime([1 , 2 , 3 ], unit='D' ) Out[68 ]: DatetimeIndex(['1970-01-02' , '1970-01-03' , '1970-01-04' ], dtype='datetime64[ns]' , freq=None )
生成时间戳范围 DatetimeIndex
、Index
构建器可以生成时间戳索引,此处要提供 datetime
对象列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [69 ]: dates = [datetime.datetime(2012 , 5 , 1 ), ....: datetime.datetime(2012 , 5 , 2 ), ....: datetime.datetime(2012 , 5 , 3 )] ....: In [70 ]: index = pd.DatetimeIndex(dates) In [71 ]: index Out[71 ]: DatetimeIndex(['2012-05-01' , '2012-05-02' , '2012-05-03' ], dtype='datetime64[ns]' , freq=None ) In [72 ]: index = pd.Index(dates) In [73 ]: index Out[73 ]: DatetimeIndex(['2012-05-01' , '2012-05-02' , '2012-05-03' ], dtype='datetime64[ns]' , freq=None )
实际工作中,生成大量时间戳的超长索引时,一个个输入时间戳又枯燥,又低效。如果时间戳是定频的,用 date_range()
与 bdate_range()
函数即可创建 DatetimeIndex
。date_range
默认的频率是日历日 ,bdate_range
的默认频率是工作日 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 In [74 ]: start = datetime.datetime(2011 , 1 , 1 ) In [75 ]: end = datetime.datetime(2012 , 1 , 1 ) In [76 ]: index = pd.date_range(start, end) In [77 ]: index Out[77 ]: DatetimeIndex(['2011-01-01' , '2011-01-02' , '2011-01-03' , '2011-01-04' , '2011-01-05' , '2011-01-06' , '2011-01-07' , '2011-01-08' , '2011-01-09' , '2011-01-10' , ... '2011-12-23' , '2011-12-24' , '2011-12-25' , '2011-12-26' , '2011-12-27' , '2011-12-28' , '2011-12-29' , '2011-12-30' , '2011-12-31' , '2012-01-01' ], dtype='datetime64[ns]' , length=366 , freq='D' ) In [78 ]: index = pd.bdate_range(start, end) In [79 ]: index Out[79 ]: DatetimeIndex(['2011-01-03' , '2011-01-04' , '2011-01-05' , '2011-01-06' , '2011-01-07' , '2011-01-10' , '2011-01-11' , '2011-01-12' , '2011-01-13' , '2011-01-14' , ... '2011-12-19' , '2011-12-20' , '2011-12-21' , '2011-12-22' , '2011-12-23' , '2011-12-26' , '2011-12-27' , '2011-12-28' , '2011-12-29' , '2011-12-30' ], dtype='datetime64[ns]' , length=260 , freq='B' )
date_range
、bdate_range
等便捷函数可以调用各种频率别名 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 In [80 ]: pd.date_range(start, periods=1000 , freq='M' ) Out[80 ]: DatetimeIndex(['2011-01-31' , '2011-02-28' , '2011-03-31' , '2011-04-30' , '2011-05-31' , '2011-06-30' , '2011-07-31' , '2011-08-31' , '2011-09-30' , '2011-10-31' , ... '2093-07-31' , '2093-08-31' , '2093-09-30' , '2093-10-31' , '2093-11-30' , '2093-12-31' , '2094-01-31' , '2094-02-28' , '2094-03-31' , '2094-04-30' ], dtype='datetime64[ns]' , length=1000 , freq='M' ) In [81 ]: pd.bdate_range(start, periods=250 , freq='BQS' ) Out[81 ]: DatetimeIndex(['2011-01-03' , '2011-04-01' , '2011-07-01' , '2011-10-03' , '2012-01-02' , '2012-04-02' , '2012-07-02' , '2012-10-01' , '2013-01-01' , '2013-04-01' , ... '2071-01-01' , '2071-04-01' , '2071-07-01' , '2071-10-01' , '2072-01-01' , '2072-04-01' , '2072-07-01' , '2072-10-03' , '2073-01-02' , '2073-04-03' ], dtype='datetime64[ns]' , length=250 , freq='BQS-JAN' )
date_range
与 bdate_range
通过指定 start
、end
、period
与 freq
等参数,简化了生成日期范围这项工作。开始与结束日期是必填项,因此,不会生成指定范围之外的日期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 In [82 ]: pd.date_range(start, end, freq='BM' ) Out[82 ]: DatetimeIndex(['2011-01-31' , '2011-02-28' , '2011-03-31' , '2011-04-29' , '2011-05-31' , '2011-06-30' , '2011-07-29' , '2011-08-31' , '2011-09-30' , '2011-10-31' , '2011-11-30' , '2011-12-30' ], dtype='datetime64[ns]' , freq='BM' ) In [83 ]: pd.date_range(start, end, freq='W' ) Out[83 ]: DatetimeIndex(['2011-01-02' , '2011-01-09' , '2011-01-16' , '2011-01-23' , '2011-01-30' , '2011-02-06' , '2011-02-13' , '2011-02-20' , '2011-02-27' , '2011-03-06' , '2011-03-13' , '2011-03-20' , '2011-03-27' , '2011-04-03' , '2011-04-10' , '2011-04-17' , '2011-04-24' , '2011-05-01' , '2011-05-08' , '2011-05-15' , '2011-05-22' , '2011-05-29' , '2011-06-05' , '2011-06-12' , '2011-06-19' , '2011-06-26' , '2011-07-03' , '2011-07-10' , '2011-07-17' , '2011-07-24' , '2011-07-31' , '2011-08-07' , '2011-08-14' , '2011-08-21' , '2011-08-28' , '2011-09-04' , '2011-09-11' , '2011-09-18' , '2011-09-25' , '2011-10-02' , '2011-10-09' , '2011-10-16' , '2011-10-23' , '2011-10-30' , '2011-11-06' , '2011-11-13' , '2011-11-20' , '2011-11-27' , '2011-12-04' , '2011-12-11' , '2011-12-18' , '2011-12-25' , '2012-01-01' ], dtype='datetime64[ns]' , freq='W-SUN' ) In [84 ]: pd.bdate_range(end=end, periods=20 ) Out[84 ]: DatetimeIndex(['2011-12-05' , '2011-12-06' , '2011-12-07' , '2011-12-08' , '2011-12-09' , '2011-12-12' , '2011-12-13' , '2011-12-14' , '2011-12-15' , '2011-12-16' , '2011-12-19' , '2011-12-20' , '2011-12-21' , '2011-12-22' , '2011-12-23' , '2011-12-26' , '2011-12-27' , '2011-12-28' , '2011-12-29' , '2011-12-30' ], dtype='datetime64[ns]' , freq='B' ) In [85 ]: pd.bdate_range(start=start, periods=20 ) Out[85 ]: DatetimeIndex(['2011-01-03' , '2011-01-04' , '2011-01-05' , '2011-01-06' , '2011-01-07' , '2011-01-10' , '2011-01-11' , '2011-01-12' , '2011-01-13' , '2011-01-14' , '2011-01-17' , '2011-01-18' , '2011-01-19' , '2011-01-20' , '2011-01-21' , '2011-01-24' , '2011-01-25' , '2011-01-26' , '2011-01-27' , '2011-01-28' ], dtype='datetime64[ns]' , freq='B' )
0.23.0 版新增。
指定 start
、end
、periods
即可生成从 start
开始至 end
结束的等距日期范围,这个日期范围包含了 start
与 end
,生成的 DatetimeIndex
里的元素数量为 periods
的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [86 ]: pd.date_range('2018-01-01' , '2018-01-05' , periods=5 ) Out[86 ]: DatetimeIndex(['2018-01-01' , '2018-01-02' , '2018-01-03' , '2018-01-04' , '2018-01-05' ], dtype='datetime64[ns]' , freq=None ) In [87 ]: pd.date_range('2018-01-01' , '2018-01-05' , periods=10 ) Out[87 ]: DatetimeIndex(['2018-01-01 00:00:00' , '2018-01-01 10:40:00' , '2018-01-01 21:20:00' , '2018-01-02 08:00:00' , '2018-01-02 18:40:00' , '2018-01-03 05:20:00' , '2018-01-03 16:00:00' , '2018-01-04 02:40:00' , '2018-01-04 13:20:00' , '2018-01-05 00:00:00' ], dtype='datetime64[ns]' , freq=None )
自定义频率范围 设定 weekmask
与 holidays
参数,bdate_range
还可以生成自定义频率日期范围。这些参数只用于传递自定义字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 In [88 ]: weekmask = 'Mon Wed Fri' In [89 ]: holidays = [datetime.datetime(2011 , 1 , 5 ), datetime.datetime(2011 , 3 , 14 )] In [90 ]: pd.bdate_range(start, end, freq='C' , weekmask=weekmask, holidays=holidays) Out[90 ]: DatetimeIndex(['2011-01-03' , '2011-01-07' , '2011-01-10' , '2011-01-12' , '2011-01-14' , '2011-01-17' , '2011-01-19' , '2011-01-21' , '2011-01-24' , '2011-01-26' , ... '2011-12-09' , '2011-12-12' , '2011-12-14' , '2011-12-16' , '2011-12-19' , '2011-12-21' , '2011-12-23' , '2011-12-26' , '2011-12-28' , '2011-12-30' ], dtype='datetime64[ns]' , length=154 , freq='C' ) In [91 ]: pd.bdate_range(start, end, freq='CBMS' , weekmask=weekmask) Out[91 ]: DatetimeIndex(['2011-01-03' , '2011-02-02' , '2011-03-02' , '2011-04-01' , '2011-05-02' , '2011-06-01' , '2011-07-01' , '2011-08-01' , '2011-09-02' , '2011-10-03' , '2011-11-02' , '2011-12-02' ], dtype='datetime64[ns]' , freq='CBMS' )
::: tip 参阅
自定义工作日
:::
时间戳的界限 Pandas 时间戳的最低单位为纳秒,64 位整数显示的时间跨度约为 584 年,这就是 Timestamp
的界限:
1 2 3 4 5 In [92 ]: pd.Timestamp.min Out[92 ]: Timestamp('1677-09-21 00:12:43.145225' ) In [93 ]: pd.Timestamp.max Out[93 ]: Timestamp('2262-04-11 23:47:16.854775807' )
::: tip 参阅
时间段越界展示
:::
索引 DatetimeIndex
主要用于 Pandas 对象的索引,为时间序列做了很多优化:
预计算了各种偏移量的日期范围,并在后台缓存,让后台生成后续日期范围的速度非常快(仅需抓取切片)。
在 Pandas 对象上用 shift
与 tshift
方法进行快速偏移。
合并具有相同频率的重叠 DatetimeIndex
对象的速度非常快(这点对快速数据对齐非常重要)。
通过 year
、month
等属性快速访问日期字段。
snap
等正则函数与超快的 asof
逻辑。
DatetimeIndex
支持 Index
的所有基本用法,以及一些列简化频率处理的高级时间序列专有方法。
::: tip 参阅
重置索引
:::
::: tip 注意
Pandas 不强制排序日期索引,但如果日期未排序,可能会引发失控或错误操作。
:::
DatetimeIndex
可以当作常规索引,支持选择、切片等方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 In [94 ]: rng = pd.date_range(start, end, freq='BM' ) In [95 ]: ts = pd.Series(np.random.randn(len (rng)), index=rng) In [96 ]: ts.index Out[96 ]: DatetimeIndex(['2011-01-31' , '2011-02-28' , '2011-03-31' , '2011-04-29' , '2011-05-31' , '2011-06-30' , '2011-07-29' , '2011-08-31' , '2011-09-30' , '2011-10-31' , '2011-11-30' , '2011-12-30' ], dtype='datetime64[ns]' , freq='BM' ) In [97 ]: ts[:5 ].index Out[97 ]: DatetimeIndex(['2011-01-31' , '2011-02-28' , '2011-03-31' , '2011-04-29' , '2011-05-31' ], dtype='datetime64[ns]' , freq='BM' ) In [98 ]: ts[::2 ].index Out[98 ]: DatetimeIndex(['2011-01-31' , '2011-03-31' , '2011-05-31' , '2011-07-29' , '2011-09-30' , '2011-11-30' ], dtype='datetime64[ns]' , freq='2BM' )
局部字符串索引 能解析为时间戳的日期与字符串可以作为索引的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [99 ]: ts['1/31/2011' ] Out[99 ]: 0.11920871129693428 In [100 ]: ts[datetime.datetime(2011 , 12 , 25 ):] Out[100 ]: 2011 -12 -30 0.56702 Freq: BM, dtype: float64 In [101 ]: ts['10/31/2011' :'12/31/2011' ] Out[101 ]: 2011 -10 -31 0.271860 2011 -11 -30 -0.424972 2011 -12 -30 0.567020 Freq: BM, dtype: float64
Pandas 为访问较长的时间序列提供了便捷方法,年 、年月 字符串均可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 In [102 ]: ts['2011' ] Out[102 ]: 2011 -01-31 0.119209 2011 -02-28 -1.044236 2011 -03-31 -0.861849 2011 -04-29 -2.104569 2011 -05-31 -0.494929 2011 -06-30 1.071804 2011 -07-29 0.721555 2011 -08-31 -0.706771 2011 -09-30 -1.039575 2011 -10 -31 0.271860 2011 -11 -30 -0.424972 2011 -12 -30 0.567020 Freq: BM, dtype: float64 In [103 ]: ts['2011-6' ] Out[103 ]: 2011 -06-30 1.071804 Freq: BM, dtype: float64
带 DatetimeIndex
的 DateFrame
也支持这种切片方式。局部字符串是标签切片的一种形式,这种切片也包含 截止时点,即,与日期匹配的时间也会包含在内:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 In [104 ]: dft = pd.DataFrame(np.random.randn(100000 , 1 ), columns=['A' ], .....: index=pd.date_range('20130101' , periods=100000 , freq='T' )) .....: In [105 ]: dft Out[105 ]: A 2013 -01-01 00 :00 :00 0.276232 2013 -01-01 00 :01:00 -1.087401 2013 -01-01 00 :02:00 -0.673690 2013 -01-01 00 :03:00 0.113648 2013 -01-01 00 :04:00 -1.478427 ... ...2013 -03-11 10 :35 :00 -0.747967 2013 -03-11 10 :36 :00 -0.034523 2013 -03-11 10 :37 :00 -0.201754 2013 -03-11 10 :38 :00 -1.509067 2013 -03-11 10 :39 :00 -1.693043 [100000 rows x 1 columns] In [106 ]: dft['2013' ] Out[106 ]: A 2013 -01-01 00 :00 :00 0.276232 2013 -01-01 00 :01:00 -1.087401 2013 -01-01 00 :02:00 -0.673690 2013 -01-01 00 :03:00 0.113648 2013 -01-01 00 :04:00 -1.478427 ... ...2013 -03-11 10 :35 :00 -0.747967 2013 -03-11 10 :36 :00 -0.034523 2013 -03-11 10 :37 :00 -0.201754 2013 -03-11 10 :38 :00 -1.509067 2013 -03-11 10 :39 :00 -1.693043 [100000 rows x 1 columns]
下列代码截取了自 1 月 1 日凌晨起,至 2 月 28 日午夜的日期与时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [107 ]: dft['2013-1' :'2013-2' ] Out[107 ]: A 2013 -01-01 00 :00 :00 0.276232 2013 -01-01 00 :01:00 -1.087401 2013 -01-01 00 :02:00 -0.673690 2013 -01-01 00 :03:00 0.113648 2013 -01-01 00 :04:00 -1.478427 ... ...2013 -02-28 23 :55 :00 0.850929 2013 -02-28 23 :56 :00 0.976712 2013 -02-28 23 :57 :00 -2.693884 2013 -02-28 23 :58 :00 -1.575535 2013 -02-28 23 :59 :00 -1.573517 [84960 rows x 1 columns]
下列代码截取了包含截止日期及其时间在内 的日期与时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [108 ]: dft['2013-1' :'2013-2-28' ] Out[108 ]: A 2013 -01-01 00 :00 :00 0.276232 2013 -01-01 00 :01:00 -1.087401 2013 -01-01 00 :02:00 -0.673690 2013 -01-01 00 :03:00 0.113648 2013 -01-01 00 :04:00 -1.478427 ... ...2013 -02-28 23 :55 :00 0.850929 2013 -02-28 23 :56 :00 0.976712 2013 -02-28 23 :57 :00 -2.693884 2013 -02-28 23 :58 :00 -1.575535 2013 -02-28 23 :59 :00 -1.573517 [84960 rows x 1 columns]
下列代码指定了精准的截止时间,注意此处的结果与上述截取结果的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [109 ]: dft['2013-1' :'2013-2-28 00:00:00' ] Out[109 ]: A 2013 -01-01 00 :00 :00 0.276232 2013 -01-01 00 :01:00 -1.087401 2013 -01-01 00 :02:00 -0.673690 2013 -01-01 00 :03:00 0.113648 2013 -01-01 00 :04:00 -1.478427 ... ...2013 -02-27 23 :56 :00 1.197749 2013 -02-27 23 :57 :00 0.720521 2013 -02-27 23 :58 :00 -0.072718 2013 -02-27 23 :59 :00 -0.681192 2013 -02-28 00 :00 :00 -0.557501 [83521 rows x 1 columns]
截止时间是索引的一部分,包含在截取的内容之内:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [110 ]: dft['2013-1-15' :'2013-1-15 12:30:00' ] Out[110 ]: A 2013 -01-15 00 :00 :00 -0.984810 2013 -01-15 00 :01:00 0.941451 2013 -01-15 00 :02:00 1.559365 2013 -01-15 00 :03:00 1.034374 2013 -01-15 00 :04:00 -1.480656 ... ...2013 -01-15 12 :26 :00 0.371454 2013 -01-15 12 :27 :00 -0.930806 2013 -01-15 12 :28 :00 -0.069177 2013 -01-15 12 :29 :00 0.066510 2013 -01-15 12 :30 :00 -0.003945 [751 rows x 1 columns]
0.18.0 版新增 。
DatetimeIndex
局部字符串索引还支持多层索引 DataFrame
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 In [111 ]: dft2 = pd.DataFrame(np.random.randn(20 , 1 ), .....: columns=['A' ], .....: index=pd.MultiIndex.from_product( .....: [pd.date_range('20130101' , periods=10 , freq='12H' ), .....: ['a' , 'b' ]])) .....: In [112 ]: dft2 Out[112 ]: A 2013 -01-01 00 :00 :00 a -0.298694 b 0.823553 2013 -01-01 12 :00 :00 a 0.943285 b -1.479399 2013 -01-02 00 :00 :00 a -1.643342 ... ...2013 -01-04 12 :00 :00 b 0.069036 2013 -01-05 00 :00 :00 a 0.122297 b 1.422060 2013 -01-05 12 :00 :00 a 0.370079 b 1.016331 [20 rows x 1 columns] In [113 ]: dft2.loc['2013-01-05' ] Out[113 ]: A 2013 -01-05 00 :00 :00 a 0.122297 b 1.422060 2013 -01-05 12 :00 :00 a 0.370079 b 1.016331 In [114 ]: idx = pd.IndexSlice In [115 ]: dft2 = dft2.swaplevel(0 , 1 ).sort_index() In [116 ]: dft2.loc[idx[:, '2013-01-05' ], :] Out[116 ]: A a 2013 -01-05 00 :00 :00 0.122297 2013 -01-05 12 :00 :00 0.370079 b 2013 -01-05 00 :00 :00 1.422060 2013 -01-05 12 :00 :00 1.016331
0.25.0 版新增 。
字符串索引切片支持 UTC 偏移。
1 2 3 4 5 6 7 8 9 10 11 In [117 ]: df = pd.DataFrame([0 ], index=pd.DatetimeIndex(['2019-01-01' ], tz='US/Pacific' )) In [118 ]: df Out[118 ]: 0 2019 -01-01 00 :00 :00 -08:00 0 In [119 ]: df['2019-01-01 12:00:00+04:00' :'2019-01-01 13:00:00+04:00' ] Out[119 ]: 0 2019 -01-01 00 :00 :00 -08:00 0
切片 vs. 精准匹配 0.20.0 版新增。
基于索引的精度,字符串既可用于切片,也可用于精准匹配。字符串精度比索引精度低,就是切片,比索引精度高,则是精准匹配。
1 2 3 4 5 6 7 8 In [120 ]: series_minute = pd.Series([1 , 2 , 3 ], .....: pd.DatetimeIndex(['2011-12-31 23:59:00' , .....: '2012-01-01 00:00:00' , .....: '2012-01-01 00:02:00' ])) .....: In [121 ]: series_minute.index.resolution Out[121 ]: 'minute'
下例中的时间戳字符串没有 Series
对象的精度高。series_minute
到秒
,时间戳字符串只到分
。
1 2 3 4 In [122 ]: series_minute['2011-12-31 23' ] Out[122 ]: 2011 -12 -31 23 :59 :00 1 dtype: int64
精度为分钟(或更高精度)的时间戳字符串,给出的是标量,不会被当作切片。
1 2 3 4 5 In [123 ]: series_minute['2011-12-31 23:59' ] Out[123 ]: 1 In [124 ]: series_minute['2011-12-31 23:59:00' ] Out[124 ]: 1
索引的精度为秒时,精度为分钟的时间戳返回的是 Series
。
1 2 3 4 5 6 7 8 9 10 11 12 13 In [125 ]: series_second = pd.Series([1 , 2 , 3 ], .....: pd.DatetimeIndex(['2011-12-31 23:59:59' , .....: '2012-01-01 00:00:00' , .....: '2012-01-01 00:00:01' ])) .....: In [126 ]: series_second.index.resolution Out[126 ]: 'second' In [127 ]: series_second['2011-12-31 23:59' ] Out[127 ]: 2011 -12 -31 23 :59 :59 1 dtype: int64
用时间戳字符串切片时,还可以用 []
索引 DataFrame
。
1 2 3 4 5 6 7 8 In [128 ]: dft_minute = pd.DataFrame({'a' : [1 , 2 , 3 ], 'b' : [4 , 5 , 6 ]}, .....: index=series_minute.index) .....: In [129 ]: dft_minute['2011-12-31 23' ] Out[129 ]: a b 2011 -12 -31 23 :59 :00 1 4
::: danger 警告
字符串执行精确匹配时,用 []
按列,而不是按行截取 DateFrame
,参阅索引基础 。如,dft_minute ['2011-12-31 23:59']
会触发 KeyError
,这是因为 2012-12-31 23:59
与索引的精度一样,但没有叫这个名字的列。
为了实现精准切片,要用 .loc
对行进行切片或选择。
1 2 3 4 5 In [130 ]: dft_minute.loc['2011-12-31 23:59' ] Out[130 ]: a 1 b 4 Name: 2011 -12 -31 23 :59 :00 , dtype: int64
:::
注意:DatetimeIndex
精度不能低于日。
1 2 3 4 5 6 7 8 9 10 11 In [131 ]: series_monthly = pd.Series([1 , 2 , 3 ], .....: pd.DatetimeIndex(['2011-12' , '2012-01' , '2012-02' ])) .....: In [132 ]: series_monthly.index.resolution Out[132 ]: 'day' In [133 ]: series_monthly['2011-12' ] Out[133 ]: 2011 -12 -01 1 dtype: int64
精确索引 正如上节所述,局部字符串依靠时间段的精度 索引 DatetimeIndex
,即时间间隔与索引精度相关。反之,用 Timestamp
或 datetime
索引更精准,这些对象指定的时间更精确。注意,精确索引包含了起始时点。
就算没有显式指定,Timestamp
与datetime
也支持 hours
、minutes
、seconds
,默认值为 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [134 ]: dft[datetime.datetime(2013 , 1 , 1 ):datetime.datetime(2013 , 2 , 28 )] Out[134 ]: A 2013 -01-01 00 :00 :00 0.276232 2013 -01-01 00 :01:00 -1.087401 2013 -01-01 00 :02:00 -0.673690 2013 -01-01 00 :03:00 0.113648 2013 -01-01 00 :04:00 -1.478427 ... ...2013 -02-27 23 :56 :00 1.197749 2013 -02-27 23 :57 :00 0.720521 2013 -02-27 23 :58 :00 -0.072718 2013 -02-27 23 :59 :00 -0.681192 2013 -02-28 00 :00 :00 -0.557501 [83521 rows x 1 columns]
不用默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 In [135 ]: dft[datetime.datetime(2013 , 1 , 1 , 10 , 12 , 0 ): .....: datetime.datetime(2013 , 2 , 28 , 10 , 12 , 0 )] .....: Out[135 ]: A 2013 -01-01 10 :12 :00 0.565375 2013 -01-01 10 :13 :00 0.068184 2013 -01-01 10 :14 :00 0.788871 2013 -01-01 10 :15 :00 -0.280343 2013 -01-01 10 :16 :00 0.931536 ... ...2013 -02-28 10 :08:00 0.148098 2013 -02-28 10 :09:00 -0.388138 2013 -02-28 10 :10 :00 0.139348 2013 -02-28 10 :11 :00 0.085288 2013 -02-28 10 :12 :00 0.950146 [83521 rows x 1 columns]
截断与花式索引 truncate()
便捷函数与切片类似。注意,与切片返回的是部分匹配日期不同, truncate
假设 DatetimeIndex
里未标明时间组件的值为 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 In [136 ]: rng2 = pd.date_range('2011-01-01' , '2012-01-01' , freq='W' ) In [137 ]: ts2 = pd.Series(np.random.randn(len (rng2)), index=rng2) In [138 ]: ts2.truncate(before='2011-11' , after='2011-12' ) Out[138 ]: 2011 -11 -06 0.437823 2011 -11 -13 -0.293083 2011 -11 -20 -0.059881 2011 -11 -27 1.252450 Freq: W-SUN, dtype: float64 In [139 ]: ts2['2011-11' :'2011-12' ] Out[139 ]: 2011 -11 -06 0.437823 2011 -11 -13 -0.293083 2011 -11 -20 -0.059881 2011 -11 -27 1.252450 2011 -12 -04 0.046611 2011 -12 -11 0.059478 2011 -12 -18 -0.286539 2011 -12 -25 0.841669 Freq: W-SUN, dtype: float64
花式索引返回 DatetimeIndex
, 但因为打乱了 DatetimeIndex
频率,丢弃了频率信息,见 freq=None
:
1 2 In [140 ]: ts2[[0 , 2 , 6 ]].index Out[140 ]: DatetimeIndex(['2011-01-02' , '2011-01-16' , '2011-02-13' ], dtype='datetime64[ns]' , freq=None )
日期/时间组件 以下日期/时间属性可以访问 Timestamp
或 DatetimeIndex
。
属性 说明 year datetime 的年 month datetime 的月 day datetime 的日 hour datetime 的小时 minute datetime 的分钟 second datetime 的秒 microsecond datetime 的微秒 nanosecond datetime 的纳秒 date 返回 datetime.date(不包含时区信息) time 返回 datetime.time(不包含时区信息) timetz 返回带本地时区信息的 datetime.time dayofyear 一年里的第几天 weekofyear 一年里的第几周 week 一年里的第几周 dayofweek 一周里的第几天,Monday=0, Sunday=6 weekday 一周里的第几天,Monday=0, Sunday=6 weekday_name 这一天是星期几 (如,Friday) quarter 日期所处的季节:Jan-Mar = 1,Apr-Jun = 2 等 days_in_month 日期所在的月有多少天 is_month_start 逻辑判断是不是月初(由频率定义) is_month_end 逻辑判断是不是月末(由频率定义) is_quarter_start 逻辑判断是不是季初(由频率定义) is_quarter_end 逻辑判断是不是季末(由频率定义) is_year_start 逻辑判断是不是年初(由频率定义) is_year_end 逻辑判断是不是年末(由频率定义) is_leap_year 逻辑判断是不是日期所在年是不是闰年
参照 .dt 访问器 一节介绍的知识点,Series
的值为 datetime
时,还可以用 .dt
访问这些属性。
DateOffset 对象 上例中,频率字符串(如,D
)用于定义指定的频率:
频率字符串表示的是 DateOffset
对象及其子类。DateOffset
类似于时间差 Timedelta
,但遵循指定的日历日规则。例如,Timedelta
表示的每日时间差一直都是 24 小时,而 DateOffset
的每日偏移量则是与下一天相同的时间差,使用夏时制时,每日偏移时间有可能是 23 或 24 小时,甚至还有可能是 25 小时。不过,DateOffset
子类只能是等于或小于小时 的时间单位(Hour
、Minute
、Second
、Milli
、Micro
、Nano
),操作类似于 Timedelta
及对应的绝对时间。
DateOffset
基础操作类似于 dateutil.relativedelta
(relativedelta 文档 ),可按指定的日历日时间段偏移日期时间。可用算数运算符(+)或 apply
方法执行日期偏移操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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
还支持 rollforward()
与 rollback()
方法,按偏移量把某一日期向前 或向后 移动至有效偏移日期。例如,工作日偏移滚动日期时会跳过周末(即,星期六与星期日),直接到星期一,因为工作日偏移针对的是工作日。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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()
可以把时间重置为午夜零点,是否应用此操作,取决于是否需要保留时间信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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
参数,生成日期始终位于一周中的指定日期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
选项。
1 2 3 4 5 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
参数,用于指定月份 。
1 2 3 4 5 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' )
Series
与 DatetimeIndex
偏移可以为 Series
或 DatetimeIndex
里的每个元素应用偏移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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-011 2012 -03-022 2012 -03-03dtype: datetime64[ns] In [179 ]: s - pd.DateOffset(months=2 ) Out[179 ]: 0 2011 -11 -011 2011 -11 -022 2011 -11 -03dtype: datetime64[ns]
如果偏移直接映射 Timedelta
(Day
、Hour
、Minute
、Second
、Micro
、Milli
、Nano
),则该偏移与 Timedelta
的使用方式完全一样。参阅时间差 - Timedelta ,查看更多示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 In [180 ]: s - pd.offsets.Day(2 ) Out[180 ]: 0 2011 -12 -30 1 2011 -12 -31 2 2012 -01-01dtype: datetime64[ns] In [181 ]: td = s - pd.Series(pd.date_range('2011-12-29' , '2011-12-31' )) In [182 ]: td Out[182 ]: 0 3 days1 3 days2 3 daysdtype: 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
(性能警告)。
1 2 In [184 ]: rng + pd.offsets.BQuarterEnd() Out[184 ]: DatetimeIndex(['2012-03-30' , '2012-03-30' , '2012-03-30' ], dtype='datetime64[ns]' , freq='D' )
自定义工作日 Cday
或 CustomBusinessDay
类可以参数化 BusinessDay
类,用于创建支持本地周末与传统节假日的自定义工作日历。
下面这个例子就很有意思,知道吗?埃及的周末是星期五与星期六。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 In [185 ]: weekmask_egypt = 'Sun Mon Tue Wed Thu' 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' )
下列代码实现了日期与工作日之间的映射关系。
1 2 3 4 5 6 7 8 9 10 11 12 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 Tue2013 -05-02 Thu2013 -05-05 Sun2013 -05-06 Mon2013 -05-07 TueFreq: C, dtype: object
节日日历支持节假日列表。更多信息,请参阅节日日历 文档。
1 2 3 4 5 6 7 8 9 10 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' )
遵循节日日历规则的月偏移可以用正常方式定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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' )
::: tip 注意
频率字符串 ‘C’ 验证 CustomBusinessDay
日期偏移 调用,注意,CustomBusinessDay
可实现参数化,CustomBusinessDay
实例会各不相同,且频率字符串 ‘C’ 无法识别这个问题。用户应确保应用里调用的频率字符串 ‘C’ 的一致性 。
工作时间 BusinessHour
表示 BusinessDay
基础上的工作时间,用于指定开始与结束工作时间。
BusinessHour
默认的工作时间是 9:00 - 17:00。BusinessHour
加法以小时频率增加 Timestamp
。如果目标 Timestamp
超出了一小时,则要先移动到下一个工作小时,再行增加。如果超过了当日工作时间的范围,剩下的时间则添加到下一个工作日。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 In [200 ]: bh = pd.offsets.BusinessHour() In [201 ]: bh Out[201 ]: <BusinessHour: BH=09:00 -17 :00 > 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' ) 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' ) In [207 ]: pd.Timestamp('2014-08-01 10:00' ) + pd.offsets.BusinessHour(2 ) Out[207 ]: Timestamp('2014-08-01 12:00:00' ) In [208 ]: pd.Timestamp('2014-08-01 10:00' ) + pd.offsets.BusinessHour(-3 ) Out[208 ]: Timestamp('2014-07-31 15:00:00' )
还可以用关键字指定 start
与 end
时间。参数必须是hour:minute
格式的字符串或 datetime.time
实例。把秒、微秒、纳秒设置为工作时间会导致 ValueError
。
1 2 3 4 5 6 7 8 9 10 11 12 13 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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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' ) In [218 ]: pd.Timestamp('2014-08-02 04:00' ) + bh Out[218 ]: Timestamp('2014-08-02 05:00:00' ) In [219 ]: pd.Timestamp('2014-08-04 04:00' ) + bh Out[219 ]: Timestamp('2014-08-04 18:00:00' )
BusinessHour.rollforward
与 rollback
操作将前滚至下一天的上班时间,或回滚至前一天的下班时间。与其它偏移量不同,BusinessHour.rollforward
输出与 apply
定义不同的结果。
这是因为一天工作时间的结束等同于第二天工作时间的开始。默认情况下,工作时间为 9:00 - 17:00,Pandas 认为 2014-08-01 17:00
与 2014-08-04 09:00
之间的时间间隔为 0 分钟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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' ) 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' ) 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 版新增 。
CustomBusinessHour
是 BusinessHour
和 CustomBusinessDay
的混合体,可以指定任意节假日。除了跳过自定义节假日之外,CustomBusinessHour
的运作方式与 BusinessHour
一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 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
一样的关键字参数。
1 2 3 4 5 6 7 In [230 ]: bhour_mon = pd.offsets.CustomBusinessHour(start='10:00' , .....: weekmask='Tue Wed Thu Fri' ) .....: 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 纳秒
别名组合 如前说述,别名与偏移量实例在绝大多数函数里可以互换:
1 2 3 4 5 6 7 8 9 10 11 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' )
可以组合日与当日偏移量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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_range
、bdate_range
、DatetimeIndex
及其它时间序列函数的参数。
锚定偏移量的含义 对于偏移量锚定于开始或结束指定频率(MonthEnd
、MonthBegin
、WeekEnd
等)下列规则应用于前滚与后滚。
n
不为 0 时,如果给定日期不是锚定日期,将寻找下一个或上一个锚点,并向前或向后移动 |n|-1
步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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|
个点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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
时,如果日期在锚点,则不移动,否则将前滚至下一个锚点。
1 2 3 4 5 6 7 8 9 10 11 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
类支持所有返回假日列表的方法,并且仅需在指定假日日历类里定义 rules
。start_date
与 end_date
类属性决定了假日的范围。该操作会覆盖 AbstractHolidayCalendar
类,适用于所有日历子类。USFederalHolidayCalendar
是仅有的假日日历,主要用作开发其它日历的示例。
固定日期的假日,如美国阵亡将士纪念日或美国国庆日(7 月 4 日),取决于该假日是否是在周末,可以使用以下规则:
规则 说明 nearest_workday 把星期六移至星期五,星期日移至星期一 sunday_to_monday 星期六紧接着星期一 next_monday_or_tuesday 把星期六移至星期一,并把星期日/星期一移至星期二 previous_friday 把星期六与星期日移至上一个星期五 next_monday 把星期六与星期日移至下一个星期一
下例展示如何定义假日与假日日历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 )
::: tip 提示
weekday=MO(2)
与 2 * Week(weekday=2)
相同。
:::
用这个日历创建索引,或计算偏移量,将跳过周末与假日(如,纪念日与国庆节)。下列代码用 ExampleCalendar
设定自定义工作日偏移量。至于其它偏移量,可以用于创建 DatetimeIndex
或添加到 datetime
与 Timestamp
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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_date
与 end_date
定义日期范围。默认值如下:
1 2 3 4 5 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' )
这两个日期可以用 datetime
、Timestamp
、字符串
修改。
1 2 3 4 5 6 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
还提供了一个创建日历组合或含附加规则日历的简易接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 )>)]
时间序列实例方法 移位与延迟 有时,需要整体向前或向后移动时间序列里的值,这就是移位与延迟。实现这一操作的方法是 shift()
,该方法适用于所有 Pandas 对象。
1 2 3 4 5 6 7 8 9 10 In [272 ]: ts = pd.Series(range (len (rng)), index=rng) In [273 ]: ts = ts[:5 ] In [274 ]: ts.shift(1 ) Out[274 ]: 2012 -01-01 NaN2012 -01-02 0.0 2012 -01-03 1.0 Freq: D, dtype: float64
shift
方法支持 freq
参数,可以把 DateOffset
、timedelta
对象、偏移量别名
作为参数值:
1 2 3 4 5 6 7 8 9 10 11 12 13 In [275 ]: ts.shift(5 , freq=pd.offsets.BDay()) Out[275 ]: 2012 -01-06 0 2012 -01-09 1 2012 -01-10 2 Freq: B, dtype: int64 In [276 ]: ts.shift(5 , freq='BM' ) Out[276 ]: 2012 -05-31 0 2012 -05-31 1 2012 -05-31 2 Freq: D, dtype: int64
除更改数据与索引的对齐方式外,DataFrame
与 Series
对象还提供了 tshift()
便捷方法,可以指定偏移量修改索引日期。
1 2 3 4 5 6 In [277 ]: ts.tshift(5 , freq='D' ) Out[277 ]: 2012 -01-06 0 2012 -01-07 1 2012 -01-08 2 Freq: D, dtype: int64
注意,使用 tshift()
时,因为数据没有重对齐,NaN
不会排在前面。
频率转换 改变频率的函数主要是 asfreq()
。对于 DatetimeIndex
,这就是一个调用 reindex()
,并生成 date_range
的便捷打包器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 In [278 ]: dr = pd.date_range('1/1/2010' , periods=3 , freq=3 * pd.offsets.BDay()) In [279 ]: ts = pd.Series(np.random.randn(3 ), index=dr) In [280 ]: ts Out[280 ]: 2010 -01-01 1.494522 2010 -01-06 -0.778425 2010 -01-11 -0.253355 Freq: 3B, dtype: float64 In [281 ]: ts.asfreq(pd.offsets.BDay()) Out[281 ]: 2010 -01-01 1.494522 2010 -01-04 NaN2010 -01-05 NaN2010 -01-06 -0.778425 2010 -01-07 NaN2010 -01-08 NaN2010 -01-11 -0.253355 Freq: B, dtype: float64
asfreq
用起来很方便,可以为频率转化后出现的任意间隔指定插值方法。
1 2 3 4 5 6 7 8 9 10 In [282 ]: ts.asfreq(pd.offsets.BDay(), method='pad' ) Out[282 ]: 2010 -01-01 1.494522 2010 -01-04 1.494522 2010 -01-05 1.494522 2010 -01-06 -0.778425 2010 -01-07 -0.778425 2010 -01-08 -0.778425 2010 -01-11 -0.253355 Freq: B, dtype: float64
向前与向后填充 与 asfreq
与 reindex
相关的是 fillna()
,有关文档请参阅缺失值 。
转换 Python 日期与时间 用 to_datetime
方法可以把DatetimeIndex
转换为 Python 原生 datetime.datetime
对象数组。
重采样 ::: danger 警告
0.18.0 版修改了 .resample
接口,现在的 .resample
更灵活,更像 groupby。参阅更新文档 ,对比新旧版本操作的区别。
:::
Pandas 有一个虽然简单,但却强大、高效的功能,可在频率转换时执行重采样,如,将秒数据转换为 5 分钟数据,这种操作在金融等领域里的应用非常广泛。
resample()
是基于时间的分组操作,每个组都遵循归纳方法。参阅 Cookbook 示例 了解高级应用。
从 0.18.0 版开始,resample()
可以直接用于 DataFrameGroupBy
对象,参阅 groupby 文档 。
::: tip 注意
.resample()
类似于基于时间偏移量的 rolling()
操作,请参阅这里 的讨论。
:::
基础知识 1 2 3 4 5 6 7 8 In [283 ]: rng = pd.date_range('1/1/2012' , periods=100 , freq='S' ) In [284 ]: ts = pd.Series(np.random.randint(0 , 500 , len (rng)), index=rng) In [285 ]: ts.resample('5Min' ).sum () Out[285 ]: 2012 -01-01 25103 Freq: 5T, dtype: int64
resample
函数非常灵活,可以指定多种频率转换与重采样参数。
任何支持派送(dispatch) 的函数都可用于 resample
返回对象,包括 sum
、mean
、std
、sem
、max
、min
、mid
、median
、first
、last
、ohlc
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [286 ]: ts.resample('5Min' ).mean() Out[286 ]: 2012 -01-01 251.03 Freq: 5T, dtype: float64 In [287 ]: ts.resample('5Min' ).ohlc() Out[287 ]: open high low close 2012 -01-01 308 460 9 205 In [288 ]: ts.resample('5Min' ).max () Out[288 ]: 2012 -01-01 460 Freq: 5T, dtype: int64
对于下采样,closed
可以设置为left
或 right
,用于指定关闭哪一端间隔:
1 2 3 4 5 6 7 8 9 10 In [289 ]: ts.resample('5Min' , closed='right' ).mean() Out[289 ]: 2011 -12 -31 23 :55 :00 308.000000 2012 -01-01 00 :00 :00 250.454545 Freq: 5T, dtype: float64 In [290 ]: ts.resample('5Min' , closed='left' ).mean() Out[290 ]: 2012 -01-01 251.03 Freq: 5T, dtype: float64
label
、loffset
等参数用于生成标签。label
指定生成的结果是否要为间隔标注起始时间。loffset
调整输出标签的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [291 ]: ts.resample('5Min' ).mean() Out[291 ]: 2012 -01-01 251.03 Freq: 5T, dtype: float64 In [292 ]: ts.resample('5Min' , label='left' ).mean() Out[292 ]: 2012 -01-01 251.03 Freq: 5T, dtype: float64 In [293 ]: ts.resample('5Min' , label='left' , loffset='1s' ).mean() Out[293 ]: 2012 -01-01 00 :00 :01 251.03 dtype: float64
::: danger 警告
除了 M
、A
、Q
、BM
、BA
、BQ
、W
的默认值是 right
外,其它频率偏移量的 label
与 closed
默认值都是 left
。
这种操作可能会导致时间回溯,即后面的时间会被拉回到前面的时间,如下例的 BusinessDay
频率所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 In [294 ]: s = pd.date_range('2000-01-01' , '2000-01-05' ).to_series() In [295 ]: s.iloc[2 ] = pd.NaT In [296 ]: s.dt.weekday_name Out[296 ]: 2000 -01-01 Saturday2000 -01-02 Sunday2000 -01-03 NaN2000 -01-04 Tuesday2000 -01-05 WednesdayFreq: D, dtype: object In [297 ]: s.resample('B' ).last().dt.weekday_name Out[297 ]: 1999 -12 -31 Sunday2000 -01-03 NaN2000 -01-04 Tuesday2000 -01-05 WednesdayFreq: B, dtype: object
看到了吗?星期日被拉回到了上一个星期五。要想把星期日移至星期一,改用以下代码:
1 2 3 4 5 6 In [298 ]: s.resample('B' , label='right' , closed='right' ).last().dt.weekday_name Out[298 ]: 2000 -01-03 Sunday2000 -01-04 Tuesday2000 -01-05 WednesdayFreq: B, dtype: object
:::
axis
参数的值为 0
或 1
,并可指定 DataFrame
重采样的轴。
kind
参数可以是 timestamp
或 period
,转换为时间戳或时间段形式的索引。resample
默认保留输入的日期时间形式。
重采样 period
数据时(详情见下文),convention
可以设置为 start
或 end
。指定低频时间段如何转换为高频时间段。
上采样 上采样可以指定上采样的方式及插入时间间隔的 limit
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 In [299 ]: ts[:2 ].resample('250L' ).asfreq() Out[299 ]: 2012 -01-01 00 :00 :00.000 308.0 2012 -01-01 00 :00 :00.250 NaN2012 -01-01 00 :00 :00.500 NaN2012 -01-01 00 :00 :00.750 NaN2012 -01-01 00 :00 :01.000 204.0 Freq: 250L , dtype: float64 In [300 ]: ts[:2 ].resample('250L' ).ffill() Out[300 ]: 2012 -01-01 00 :00 :00.000 308 2012 -01-01 00 :00 :00.250 308 2012 -01-01 00 :00 :00.500 308 2012 -01-01 00 :00 :00.750 308 2012 -01-01 00 :00 :01.000 204 Freq: 250L , dtype: int64 In [301 ]: ts[:2 ].resample('250L' ).ffill(limit=2 ) Out[301 ]: 2012 -01-01 00 :00 :00.000 308.0 2012 -01-01 00 :00 :00.250 308.0 2012 -01-01 00 :00 :00.500 308.0 2012 -01-01 00 :00 :00.750 NaN2012 -01-01 00 :00 :01.000 204.0 Freq: 250L , dtype: float64
稀疏重采样 相对于时间点总量,稀疏时间序列重采样的点要少很多。单纯上采样稀疏系列可能会生成很多中间值。未指定填充值,即 fill_method
是 None
时,中间值将填充为 NaN
。
鉴于 resample
是基于时间的分组,下列这种方法可以有效重采样,只是分组不是都为 NaN
。
1 2 3 In [302 ]: rng = pd.date_range('2014-1-1' , periods=100 , freq='D' ) + pd.Timedelta('1s' ) In [303 ]: ts = pd.Series(range (100 ), index=rng)
对 Series
全范围重采样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [304 ]: ts.resample('3T' ).sum () Out[304 ]: 2014 -01-01 00 :00 :00 0 2014 -01-01 00 :03:00 0 2014 -01-01 00 :06:00 0 2014 -01-01 00 :09:00 0 2014 -01-01 00 :12 :00 0 .. 2014 -04-09 23 :48 :00 0 2014 -04-09 23 :51 :00 0 2014 -04-09 23 :54 :00 0 2014 -04-09 23 :57 :00 0 2014 -04-10 00 :00 :00 99 Freq: 3T, Length: 47521 , dtype: int64
对以下包含点的分组重采样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 In [305 ]: from functools import partial In [306 ]: from pandas.tseries.frequencies import to_offset In [307 ]: def round (t, freq ): .....: freq = to_offset(freq) .....: return pd.Timestamp((t.value // freq.delta.value) * freq.delta.value) .....: In [308 ]: ts.groupby(partial(round , freq='3T' )).sum () Out[308 ]: 2014 -01-01 0 2014 -01-02 1 2014 -01-03 2 2014 -01-04 3 2014 -01-05 4 .. 2014 -04-06 95 2014 -04-07 96 2014 -04-08 97 2014 -04-09 98 2014 -04-10 99 Length: 100 , dtype: int64
聚合 类似于聚合 API ,Groupby API 及窗口函数 API ,Resampler
可以有选择地重采样。
DataFrame
重采样,默认用相同函数操作所有列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [309 ]: df = pd.DataFrame(np.random.randn(1000 , 3 ), .....: index=pd.date_range('1/1/2012' , freq='S' , periods=1000 ), .....: columns=['A' , 'B' , 'C' ]) .....: In [310 ]: r = df.resample('3T' ) In [311 ]: r.mean() Out[311 ]: A B C 2012 -01-01 00 :00 :00 -0.033823 -0.121514 -0.081447 2012 -01-01 00 :03:00 0.056909 0.146731 -0.024320 2012 -01-01 00 :06:00 -0.058837 0.047046 -0.052021 2012 -01-01 00 :09:00 0.063123 -0.026158 -0.066533 2012 -01-01 00 :12 :00 0.186340 -0.003144 0.074752 2012 -01-01 00 :15 :00 -0.085954 -0.016287 -0.050046
标准 getitem
操作可以指定的一列或多列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 In [312 ]: r['A' ].mean() Out[312 ]: 2012 -01-01 00 :00 :00 -0.033823 2012 -01-01 00 :03:00 0.056909 2012 -01-01 00 :06:00 -0.058837 2012 -01-01 00 :09:00 0.063123 2012 -01-01 00 :12 :00 0.186340 2012 -01-01 00 :15 :00 -0.085954 Freq: 3T, Name: A, dtype: float64 In [313 ]: r[['A' , 'B' ]].mean() Out[313 ]: A B 2012 -01-01 00 :00 :00 -0.033823 -0.121514 2012 -01-01 00 :03:00 0.056909 0.146731 2012 -01-01 00 :06:00 -0.058837 0.047046 2012 -01-01 00 :09:00 0.063123 -0.026158 2012 -01-01 00 :12 :00 0.186340 -0.003144 2012 -01-01 00 :15 :00 -0.085954 -0.016287
聚合还支持函数列表与字典,输出的是 DataFrame
。
1 2 3 4 5 6 7 8 9 In [314 ]: r['A' ].agg([np.sum , np.mean, np.std]) Out[314 ]: sum mean std 2012 -01-01 00 :00 :00 -6.088060 -0.033823 1.043263 2012 -01-01 00 :03:00 10.243678 0.056909 1.058534 2012 -01-01 00 :06:00 -10.590584 -0.058837 0.949264 2012 -01-01 00 :09:00 11.362228 0.063123 1.028096 2012 -01-01 00 :12 :00 33.541257 0.186340 0.884586 2012 -01-01 00 :15 :00 -8.595393 -0.085954 1.035476
重采样后的 DataFrame
,可以为每列指定函数列表,生成结构化索引的聚合结果:
1 2 3 4 5 6 7 8 9 10 In [315 ]: r.agg([np.sum , np.mean]) Out[315 ]: A B C sum mean sum mean sum mean 2012 -01-01 00 :00 :00 -6.088060 -0.033823 -21.872530 -0.121514 -14.660515 -0.081447 2012 -01-01 00 :03:00 10.243678 0.056909 26.411633 0.146731 -4.377642 -0.024320 2012 -01-01 00 :06:00 -10.590584 -0.058837 8.468289 0.047046 -9.363825 -0.052021 2012 -01-01 00 :09:00 11.362228 0.063123 -4.708526 -0.026158 -11.975895 -0.066533 2012 -01-01 00 :12 :00 33.541257 0.186340 -0.565895 -0.003144 13.455299 0.074752 2012 -01-01 00 :15 :00 -8.595393 -0.085954 -1.628689 -0.016287 -5.004580 -0.050046
把字典传递给 aggregate
,可以为 DataFrame
里不同的列应用不同聚合函数。
1 2 3 4 5 6 7 8 9 10 11 In [316 ]: r.agg({'A' : np.sum , .....: 'B' : lambda x: np.std(x, ddof=1 )}) .....: Out[316 ]: A B 2012 -01-01 00 :00 :00 -6.088060 1.001294 2012 -01-01 00 :03:00 10.243678 1.074597 2012 -01-01 00 :06:00 -10.590584 0.987309 2012 -01-01 00 :09:00 11.362228 0.944953 2012 -01-01 00 :12 :00 33.541257 1.095025 2012 -01-01 00 :15 :00 -8.595393 1.035312
还可以用字符串代替函数名。为了让字符串有效,必须在重采样对象上操作:
1 2 3 4 5 6 7 8 9 In [317 ]: r.agg({'A' : 'sum' , 'B' : 'std' }) Out[317 ]: A B 2012 -01-01 00 :00 :00 -6.088060 1.001294 2012 -01-01 00 :03:00 10.243678 1.074597 2012 -01-01 00 :06:00 -10.590584 0.987309 2012 -01-01 00 :09:00 11.362228 0.944953 2012 -01-01 00 :12 :00 33.541257 1.095025 2012 -01-01 00 :15 :00 -8.595393 1.035312
甚至还可以为每列单独多个聚合函数。
1 2 3 4 5 6 7 8 9 10 In [318 ]: r.agg({'A' : ['sum' , 'std' ], 'B' : ['mean' , 'std' ]}) Out[318 ]: A B sum std mean std 2012 -01-01 00 :00 :00 -6.088060 1.043263 -0.121514 1.001294 2012 -01-01 00 :03:00 10.243678 1.058534 0.146731 1.074597 2012 -01-01 00 :06:00 -10.590584 0.949264 0.047046 0.987309 2012 -01-01 00 :09:00 11.362228 1.028096 -0.026158 0.944953 2012 -01-01 00 :12 :00 33.541257 0.884586 -0.003144 1.095025 2012 -01-01 00 :15 :00 -8.595393 1.035476 -0.016287 1.035312
如果 DataFrame
用的不是 datetime
型索引,则可以基于 datetime
数据列重采样,用关键字 on
控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 In [319 ]: df = pd.DataFrame({'date' : pd.date_range('2015-01-01' , freq='W' , periods=5 ), .....: 'a' : np.arange(5 )}, .....: index=pd.MultiIndex.from_arrays([ .....: [1 , 2 , 3 , 4 , 5 ], .....: pd.date_range('2015-01-01' , freq='W' , periods=5 )], .....: names=['v' , 'd' ])) .....: In [320 ]: df Out[320 ]: date a v d 1 2015 -01-04 2015 -01-04 0 2 2015 -01-11 2015 -01-11 1 3 2015 -01-18 2015 -01-18 2 4 2015 -01-25 2015 -01-25 3 5 2015 -02-01 2015 -02-01 4 In [321 ]: df.resample('M' , on='date' ).sum () Out[321 ]: a date 2015 -01-31 6 2015 -02-28 4
同样,还可以对 datetime MultiIndex
重采样,通过关键字 level
传递名字与位置。
1 2 3 4 5 6 In [322 ]: df.resample('M' , level='d' ).sum () Out[322 ]: a d 2015 -01-31 6 2015 -02-28 4
分组迭代 Resampler
对象迭代分组数据的操作非常自然,类似于 itertools.groupby()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 In [323 ]: small = pd.Series( .....: range (6 ), .....: index=pd.to_datetime(['2017-01-01T00:00:00' , .....: '2017-01-01T00:30:00' , .....: '2017-01-01T00:31:00' , .....: '2017-01-01T01:00:00' , .....: '2017-01-01T03:00:00' , .....: '2017-01-01T03:05:00' ]) .....: ) .....: In [324 ]: resampled = small.resample('H' ) In [325 ]: for name, group in resampled: .....: print ("Group: " , name) .....: print ("-" * 27 ) .....: print (group, end="\n\n" ) .....: Group: 2017 -01-01 00 :00 :00 --------------------------- 2017 -01-01 00 :00 :00 0 2017 -01-01 00 :30 :00 1 2017 -01-01 00 :31 :00 2 dtype: int64 Group: 2017 -01-01 01:00 :00 --------------------------- 2017 -01-01 01:00 :00 3 dtype: int64 Group: 2017 -01-01 02:00 :00 --------------------------- Series([], dtype: int64) Group: 2017 -01-01 03:00 :00 --------------------------- 2017 -01-01 03:00 :00 4 2017 -01-01 03:05:00 5 dtype: int64
了解更多详情,请参阅分组迭代 或 itertools.groupby()
。
时间跨度表示 规律时间间隔可以用 Pandas 的 Peirod
对象表示,Period
对象序列叫做 PeriodIndex
,用便捷函数 period_range
创建。
Period Period
表示时间跨度,即时间段,如年、季、月、日等。关键字 freq
与频率别名可以指定时间段。freq
表示的是 Period
的时间跨度,不能为负,如,-3D
。
1 2 3 4 5 6 7 8 9 10 11 In [326 ]: pd.Period('2012' , freq='A-DEC' ) Out[326 ]: Period('2012' , 'A-DEC' ) In [327 ]: pd.Period('2012-1-1' , freq='D' ) Out[327 ]: Period('2012-01-01' , 'D' ) In [328 ]: pd.Period('2012-1-1 19:00' , freq='H' ) Out[328 ]: Period('2012-01-01 19:00' , 'H' ) In [329 ]: pd.Period('2012-1-1 19:00' , freq='5H' ) Out[329 ]: Period('2012-01-01 19:00' , '5H' )
时间段加减法按自身频率位移。 不同频率的时间段不可进行算术运算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 In [330 ]: p = pd.Period('2012' , freq='A-DEC' ) In [331 ]: p + 1 Out[331 ]: Period('2013' , 'A-DEC' ) In [332 ]: p - 3 Out[332 ]: Period('2009' , 'A-DEC' ) In [333 ]: p = pd.Period('2012-01' , freq='2M' ) In [334 ]: p + 2 Out[334 ]: Period('2012-05' , '2M' ) In [335 ]: p - 1 Out[335 ]: Period('2011-11' , '2M' ) In [336 ]: p == pd.Period('2012-01' , freq='3M' ) --------------------------------------------------------------------------- IncompatibleFrequency Traceback (most recent call last) <ipython-input -336 -4b67dc0b596c> in <module> ----> 1 p == pd.Period('2012-01' , freq='3M' ) /pandas/pandas/_libs/tslibs/period.pyx in pandas._libs.tslibs.period._Period.__richcmp__() IncompatibleFrequency: Input has different freq=3M from Period(freq=2M)
freq
的频率为日或更高频率时,如 D
、H
、T
、S
、L
、U
、N
,offsets
与 timedelta
可以用相同频率实现加法。否则,会触发 ValueError
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [337 ]: p = pd.Period('2014-07-01 09:00' , freq='H' ) In [338 ]: p + pd.offsets.Hour(2 ) Out[338 ]: Period('2014-07-01 11:00' , 'H' ) In [339 ]: p + datetime.timedelta(minutes=120 ) Out[339 ]: Period('2014-07-01 11:00' , 'H' ) In [340 ]: p + np.timedelta64(7200 , 's' ) Out[340 ]: Period('2014-07-01 11:00' , 'H' ) In [1 ]: p + pd.offsets.Minute(5 ) Traceback ... ValueError: Input has different freq from Period(freq=H)
如果 Period
为其它频率,只有相同频率的 offsets
可以相加。否则,会触发 ValueError
。
1 2 3 4 5 6 7 8 In [341 ]: p = pd.Period('2014-07' , freq='M' ) In [342 ]: p + pd.offsets.MonthEnd(3 ) Out[342 ]: Period('2014-10' , 'M' ) In [1 ]: p + pd.offsets.MonthBegin(3 ) Traceback ... ValueError: Input has different freq from Period(freq=M)
用相同频率计算不同时间段实例之间的区别,将返回这些实例之间的频率单元数量。
1 2 In [343 ]: pd.Period('2012' , freq='A-DEC' ) - pd.Period('2002' , freq='A-DEC' ) Out[343 ]: <10 * YearEnds: month=12 >
PeriodIndex 与 period_range period_range
便捷函数可以创建有规律的 Period
对象序列,即 PeriodIndex
。
1 2 3 4 5 6 7 8 In [344 ]: prng = pd.period_range('1/1/2011' , '1/1/2012' , freq='M' ) In [345 ]: prng Out[345 ]: PeriodIndex(['2011-01' , '2011-02' , '2011-03' , '2011-04' , '2011-05' , '2011-06' , '2011-07' , '2011-08' , '2011-09' , '2011-10' , '2011-11' , '2011-12' , '2012-01' ], dtype='period[M]' , freq='M' )
也可以直接用 PeriodIndex
创建:
1 2 In [346 ]: pd.PeriodIndex(['2011-1' , '2011-2' , '2011-3' ], freq='M' ) Out[346 ]: PeriodIndex(['2011-01' , '2011-02' , '2011-03' ], dtype='period[M]' , freq='M' )
频率为复数时,输出的 Period
序列为复数时间段。
1 2 In [347 ]: pd.period_range(start='2014-01' , freq='3M' , periods=4 ) Out[347 ]: PeriodIndex(['2014-01' , '2014-04' , '2014-07' , '2014-10' ], dtype='period[3M]' , freq='3M' )
Period
对象的 start
或 end
会被当作 PeriodIndex
的锚定终点,其频率与 PeriodIndex
的频率一样。
1 2 3 4 In [348 ]: pd.period_range(start=pd.Period('2017Q1' , freq='Q' ), .....: end=pd.Period('2017Q2' , freq='Q' ), freq='M' ) .....: Out[348 ]: PeriodIndex(['2017-03' , '2017-04' , '2017-05' , '2017-06' ], dtype='period[M]' , freq='M' )
和 DatetimeIndex
一样,PeriodIndex
也可以作为 Pandas 对象的索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 In [349 ]: ps = pd.Series(np.random.randn(len (prng)), prng) In [350 ]: ps Out[350 ]: 2011 -01 -2.916901 2011 -02 0.514474 2011 -03 1.346470 2011 -04 0.816397 2011 -05 2.258648 2011 -06 0.494789 2011 -07 0.301239 2011 -08 0.464776 2011 -09 -1.393581 2011 -10 0.056780 2011 -11 0.197035 2011 -12 2.261385 2012 -01 -0.329583 Freq: M, dtype: float64
PeriodIndex
的加减法与 Period
一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 In [351 ]: idx = pd.period_range('2014-07-01 09:00' , periods=5 , freq='H' ) In [352 ]: idx Out[352 ]: PeriodIndex(['2014-07-01 09:00' , '2014-07-01 10:00' , '2014-07-01 11:00' , '2014-07-01 12:00' , '2014-07-01 13:00' ], dtype='period[H]' , freq='H' ) In [353 ]: idx + pd.offsets.Hour(2 ) Out[353 ]: PeriodIndex(['2014-07-01 11:00' , '2014-07-01 12:00' , '2014-07-01 13:00' , '2014-07-01 14:00' , '2014-07-01 15:00' ], dtype='period[H]' , freq='H' ) In [354 ]: idx = pd.period_range('2014-07' , periods=5 , freq='M' ) In [355 ]: idx Out[355 ]: PeriodIndex(['2014-07' , '2014-08' , '2014-09' , '2014-10' , '2014-11' ], dtype='period[M]' , freq='M' ) In [356 ]: idx + pd.offsets.MonthEnd(3 ) Out[356 ]: PeriodIndex(['2014-10' , '2014-11' , '2014-12' , '2015-01' , '2015-02' ], dtype='period[M]' , freq='M' )
PeriodIndex
有自己的数据类型,即 period
,请参阅 Period 数据类型 。
Period 数据类型 0.19.0 版新增 。
PeriodIndex
的自定义数据类型是 period
,是 Pandas 扩展数据类型,类似于带时区信息的数据类型 (datetime64[ns, tz]
)。
Period
数据类型支持 freq
属性,还可以用 period[freq]
表示,如,period[D]
或 period[M]
,这里用的是频率字符串 。
1 2 3 4 5 6 7 In [357 ]: pi = pd.period_range('2016-01-01' , periods=3 , freq='M' ) In [358 ]: pi Out[358 ]: PeriodIndex(['2016-01' , '2016-02' , '2016-03' ], dtype='period[M]' , freq='M' ) In [359 ]: pi.dtype Out[359 ]: period[M]
period
数据类型在 .astype(...)
里使用。允许改变 PeriodIndex
的 freq
, 如 .asfreq()
,并用 to_period()
把 DatetimeIndex
转化为 PeriodIndex
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [360 ]: pi.astype('period[D]' ) Out[360 ]: PeriodIndex(['2016-01-31' , '2016-02-29' , '2016-03-31' ], dtype='period[D]' , freq='D' ) In [361 ]: pi.astype('datetime64[ns]' ) Out[361 ]: DatetimeIndex(['2016-01-01' , '2016-02-01' , '2016-03-01' ], dtype='datetime64[ns]' , freq='MS' ) In [362 ]: dti = pd.date_range('2011-01-01' , freq='M' , periods=3 ) In [363 ]: dti Out[363 ]: DatetimeIndex(['2011-01-31' , '2011-02-28' , '2011-03-31' ], dtype='datetime64[ns]' , freq='M' ) In [364 ]: dti.astype('period[M]' ) Out[364 ]: PeriodIndex(['2011-01' , '2011-02' , '2011-03' ], dtype='period[M]' , freq='M' )
PeriodIndex 局部字符串索引 与 DatetimeIndex
一样,PeriodIndex
可以把日期与字符串传递给 Series
与 DataFrame
。详情请参阅 DatetimeIndex 局部字符串索引 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In [365 ]: ps['2011-01' ] Out[365 ]: -2.9169013294054507 In [366 ]: ps[datetime.datetime(2011 , 12 , 25 ):] Out[366 ]: 2011 -12 2.261385 2012 -01 -0.329583 Freq: M, dtype: float64 In [367 ]: ps['10/31/2011' :'12/31/2011' ] Out[367 ]: 2011 -10 0.056780 2011 -11 0.197035 2011 -12 2.261385 Freq: M, dtype: float64
传递比 PeriodIndex
更低频率的字符串会返回局部切片数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 In [368 ]: ps['2011' ] Out[368 ]: 2011 -01 -2.916901 2011 -02 0.514474 2011 -03 1.346470 2011 -04 0.816397 2011 -05 2.258648 2011 -06 0.494789 2011 -07 0.301239 2011 -08 0.464776 2011 -09 -1.393581 2011 -10 0.056780 2011 -11 0.197035 2011 -12 2.261385 Freq: M, dtype: float64 In [369 ]: dfp = pd.DataFrame(np.random.randn(600 , 1 ), .....: columns=['A' ], .....: index=pd.period_range('2013-01-01 9:00' , .....: periods=600 , .....: freq='T' )) .....: In [370 ]: dfp Out[370 ]: A 2013 -01-01 09:00 -0.538468 2013 -01-01 09:01 -1.365819 2013 -01-01 09:02 -0.969051 2013 -01-01 09:03 -0.331152 2013 -01-01 09:04 -0.245334 ... ...2013 -01-01 18 :55 0.522460 2013 -01-01 18 :56 0.118710 2013 -01-01 18 :57 0.167517 2013 -01-01 18 :58 0.922883 2013 -01-01 18 :59 1.721104 [600 rows x 1 columns] In [371 ]: dfp['2013-01-01 10H' ] Out[371 ]: A 2013 -01-01 10 :00 -0.308975 2013 -01-01 10 :01 0.542520 2013 -01-01 10 :02 1.061068 2013 -01-01 10 :03 0.754005 2013 -01-01 10 :04 0.352933 ... ...2013 -01-01 10 :55 -0.865621 2013 -01-01 10 :56 -1.167818 2013 -01-01 10 :57 -2.081748 2013 -01-01 10 :58 -0.527146 2013 -01-01 10 :59 0.802298 [60 rows x 1 columns]
与 DatetimeIndex
一样,终点包含在结果范围之内。下例中的切片数据就是从 10:00 到 11:59。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [372 ]: dfp['2013-01-01 10H' :'2013-01-01 11H' ] Out[372 ]: A 2013 -01-01 10 :00 -0.308975 2013 -01-01 10 :01 0.542520 2013 -01-01 10 :02 1.061068 2013 -01-01 10 :03 0.754005 2013 -01-01 10 :04 0.352933 ... ...2013 -01-01 11 :55 -0.590204 2013 -01-01 11 :56 1.539990 2013 -01-01 11 :57 -1.224826 2013 -01-01 11 :58 0.578798 2013 -01-01 11 :59 -0.685496 [120 rows x 1 columns]
频率转换与 PeriodIndex
重采样 Period
与 PeriodIndex
的频率可以用 asfreq
转换。下列代码开始于 2011 财年,结束时间为十二月:
1 2 3 4 In [373 ]: p = pd.Period('2011' , freq='A-DEC' ) In [374 ]: p Out[374 ]: Period('2011' , 'A-DEC' )
可以把它转换为月频。使用 how
参数,指定是否返回开始或结束月份。
1 2 3 4 5 In [375 ]: p.asfreq('M' , how='start' ) Out[375 ]: Period('2011-01' , 'M' ) In [376 ]: p.asfreq('M' , how='end' ) Out[376 ]: Period('2011-12' , 'M' )
简称 s
与 e
用起来更方便:
1 2 3 4 5 In [377 ]: p.asfreq('M' , 's' ) Out[377 ]: Period('2011-01' , 'M' ) In [378 ]: p.asfreq('M' , 'e' ) Out[378 ]: Period('2011-12' , 'M' )
转换为“超级 period”,(如,年频就是季频的超级 period),自动返回包含输入时间段的超级 period:
1 2 3 4 In [379 ]: p = pd.Period('2011-12' , freq='M' ) In [380 ]: p.asfreq('A-NOV' ) Out[380 ]: Period('2012' , 'A-NOV' )
注意,因为转换年频是在十一月结束的,2011 年 12 月的月时间段实际上是 2012 A-NOV
period。
用锚定频率转换时间段,对经济学、商业等领域里的各种季度数据特别有用。很多公司都依据其财年开始月与结束月定义季度。因此,2011 年第一个季度有可能 2010 年就开始了,也有可能 2011 年过了几个月才开始。通过锚定频率,Pandas 可以处理所有从 Q-JAN
至 Q-DEC
的季度频率。
Q-DEC
定义的是常规日历季度:
1 2 3 4 5 6 7 In [381 ]: p = pd.Period('2012Q1' , freq='Q-DEC' ) In [382 ]: p.asfreq('D' , 's' ) Out[382 ]: Period('2012-01-01' , 'D' ) In [383 ]: p.asfreq('D' , 'e' ) Out[383 ]: Period('2012-03-31' , 'D' )
Q-MAR
定义的是财年结束于三月:
1 2 3 4 5 6 7 In [384 ]: p = pd.Period('2011Q4' , freq='Q-MAR' ) In [385 ]: p.asfreq('D' , 's' ) Out[385 ]: Period('2011-01-01' , 'D' ) In [386 ]: p.asfreq('D' , 'e' ) Out[386 ]: Period('2011-03-31' , 'D' )
不同表现形式之间的转换 to_period
把时间戳转换为 PeriodIndex
,to_timestamp
则执行反向操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 In [387 ]: rng = pd.date_range('1/1/2012' , periods=5 , freq='M' ) In [388 ]: ts = pd.Series(np.random.randn(len (rng)), index=rng) In [389 ]: ts Out[389 ]: 2012 -01-31 1.931253 2012 -02-29 -0.184594 2012 -03-31 0.249656 2012 -04-30 -0.978151 2012 -05-31 -0.873389 Freq: M, dtype: float64 In [390 ]: ps = ts.to_period() In [391 ]: ps Out[391 ]: 2012 -01 1.931253 2012 -02 -0.184594 2012 -03 0.249656 2012 -04 -0.978151 2012 -05 -0.873389 Freq: M, dtype: float64 In [392 ]: ps.to_timestamp() Out[392 ]: 2012 -01-01 1.931253 2012 -02-01 -0.184594 2012 -03-01 0.249656 2012 -04-01 -0.978151 2012 -05-01 -0.873389 Freq: MS, dtype: float64
记住 s
与 e
返回 period
开始或结束的时间戳:
1 2 3 4 5 6 7 8 In [393 ]: ps.to_timestamp('D' , how='s' ) Out[393 ]: 2012 -01-01 1.931253 2012 -02-01 -0.184594 2012 -03-01 0.249656 2012 -04-01 -0.978151 2012 -05-01 -0.873389 Freq: MS, dtype: float64
用便捷算数函数可以转换时间段与时间戳。下例中,把以 11 月年度结束的季频转换为以下一个季度月末上午 9 点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In [394 ]: prng = pd.period_range('1990Q1' , '2000Q4' , freq='Q-NOV' ) In [395 ]: ts = pd.Series(np.random.randn(len (prng)), prng) In [396 ]: ts.index = (prng.asfreq('M' , 'e' ) + 1 ).asfreq('H' , 's' ) + 9 In [397 ]: ts.head() Out[397 ]: 1990 -03-01 09:00 -0.109291 1990 -06-01 09:00 -0.637235 1990 -09-01 09:00 -1.735925 1990 -12 -01 09:00 2.096946 1991 -03-01 09:00 -1.039926 Freq: H, dtype: float64
界外跨度表示 数据在 Timestamp
限定边界外时,参阅 Timestamp 限制 ,可以用 PeriodIndex
或 Periods
的 Series
执行计算。
1 2 3 4 5 6 7 8 9 10 11 12 In [398 ]: span = pd.period_range('1215-01-01' , '1381-01-01' , freq='D' ) In [399 ]: span Out[399 ]: PeriodIndex(['1215-01-01' , '1215-01-02' , '1215-01-03' , '1215-01-04' , '1215-01-05' , '1215-01-06' , '1215-01-07' , '1215-01-08' , '1215-01-09' , '1215-01-10' , ... '1380-12-23' , '1380-12-24' , '1380-12-25' , '1380-12-26' , '1380-12-27' , '1380-12-28' , '1380-12-29' , '1380-12-30' , '1380-12-31' , '1381-01-01' ], dtype='period[D]' , length=60632 , freq='D' )
从基于 int64
的 YYYYMMDD
表示形式转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 In [400 ]: s = pd.Series([20121231 , 20141130 , 99991231 ]) In [401 ]: s Out[401 ]: 0 20121231 1 20141130 2 99991231 dtype: int64 In [402 ]: def conv (x ): .....: return pd.Period(year=x // 10000 , month=x // 100 % 100 , .....: day=x % 100 , freq='D' ) .....: In [403 ]: s.apply(conv) Out[403 ]: 0 2012 -12 -31 1 2014 -11 -30 2 9999 -12 -31 dtype: period[D] In [404 ]: s.apply(conv)[2 ] Out[404 ]: Period('9999-12-31' , 'D' )
轻轻松松就可以这些数据转换成 PeriodIndex
:
1 2 3 4 In [405 ]: span = pd.PeriodIndex(s.apply(conv)) In [406 ]: span Out[406 ]: PeriodIndex(['2012-12-31' , '2014-11-30' , '9999-12-31' ], dtype='period[D]' , freq='D' )
时区控制 利用 pytz
与 datetuil
或标准库 datetime.timezone
对象,Pandas 能以多种方式处理不同时区的时间戳。
处理时区 Pandas 对象默认不支持时区信息:
1 2 3 4 In [407 ]: rng = pd.date_range('3/6/2012 00:00' , periods=15 , freq='D' ) In [408 ]: rng.tz is None Out[408 ]: True
用 date_range()
、Timestamp
、DatetimeIndex
的 tz_localize
方法或 tz
关键字参数,可以为这些日期加上本地时区,即,把指定时区分配给不带时区的日期。还可以传递 pytz
、 dateutil
时区对象或奥尔森时区数据库字符串。奥尔森时区字符串默认返回 pytz
时区对象。要返回 dateutil
时区对象,在字符串前加上 datetuil/
。
用 from pytz import common_timezones, all_timezones
在 pytz
里查找通用时区。
dateutil
使用操作系统时区,没有固定的列表,其通用时区名与 pytz
相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 In [409 ]: import dateutil In [410 ]: rng_pytz = pd.date_range('3/6/2012 00:00' , periods=3 , freq='D' , .....: tz='Europe/London' ) .....: In [411 ]: rng_pytz.tz Out[411 ]: <DstTzInfo 'Europe/London' LMT-1 day, 23 :59 :00 STD> In [412 ]: rng_dateutil = pd.date_range('3/6/2012 00:00' , periods=3 , freq='D' ) In [413 ]: rng_dateutil = rng_dateutil.tz_localize('dateutil/Europe/London' ) In [414 ]: rng_dateutil.tz Out[414 ]: tzfile('/usr/share/zoneinfo/Europe/London' ) In [415 ]: rng_utc = pd.date_range('3/6/2012 00:00' , periods=3 , freq='D' , .....: tz=dateutil.tz.tzutc()) .....: In [416 ]: rng_utc.tz Out[416 ]: tzutc()
0.25.0 版新增。
1 2 3 4 5 6 7 In [417 ]: rng_utc = pd.date_range('3/6/2012 00:00' , periods=3 , freq='D' , .....: tz=datetime.timezone.utc) .....: In [418 ]: rng_utc.tz Out[418 ]: datetime.timezone.utc
注意, dateutil
的 UTC
时区是个特例,要显式地创建 dateutil.tz.tzutc
实例。可以先创建其它时区对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 In [419 ]: import pytz In [420 ]: tz_pytz = pytz.timezone('Europe/London' ) In [421 ]: rng_pytz = pd.date_range('3/6/2012 00:00' , periods=3 , freq='D' ) In [422 ]: rng_pytz = rng_pytz.tz_localize(tz_pytz) In [423 ]: rng_pytz.tz == tz_pytz Out[423 ]: True In [424 ]: tz_dateutil = dateutil.tz.gettz('Europe/London' ) In [425 ]: rng_dateutil = pd.date_range('3/6/2012 00:00' , periods=3 , freq='D' , .....: tz=tz_dateutil) .....: In [426 ]: rng_dateutil.tz == tz_dateutil Out[426 ]: True
不同时区之间转换带时区的 Pandas 对象时,用 tz_convert
方法。
1 2 3 4 5 In [427 ]: rng_pytz.tz_convert('US/Eastern' ) Out[427 ]: DatetimeIndex(['2012-03-05 19:00:00-05:00' , '2012-03-06 19:00:00-05:00' , '2012-03-07 19:00:00-05:00' ], dtype='datetime64[ns, US/Eastern]' , freq='D' )
::: tip 注意
使用 pytz
时区时,对于相同的输入时区,DatetimeIndex
会构建一个与 Timestamp
不同的时区对象。DatetimeIndex
具有一组 Timestamp
对象,UTC 偏移量也不同,不能用一个 pytz
时区实例简洁地表示,Timestamp
则可以用来指定 UTC 偏移量表示一个时点。
1 2 3 4 5 6 7 8 9 In [428 ]: dti = pd.date_range('2019-01-01' , periods=3 , freq='D' , tz='US/Pacific' ) In [429 ]: dti.tz Out[429 ]: <DstTzInfo 'US/Pacific' LMT-1 day, 16 :07:00 STD> In [430 ]: ts = pd.Timestamp('2019-01-01' , tz='US/Pacific' ) In [431 ]: ts.tz Out[431 ]: <DstTzInfo 'US/Pacific' PST-1 day, 16 :00 :00 STD>
:::
::: danger 警告
注意不同支持库之间的转换。一些时区,pytz
与 datetuil
对时区的定义不一样。与 US/Eastern
等“标准”时区相比,那些更少见的时区的问题更严重。
:::
::: danger 警告
注意不同版本时区支持库对时区的定义并不一致。在处理本地存储数据时使用一种版本的支持库,在运算时使用另一种版本的支持库,可能会引起问题。参阅本文 了解如何处理这种问题。
:::
::: danger 警告
对于 pytz
时区,直接把时区对象传递给 datetime.datetime
构建器是不对的,如,datetime.datetime(2011, 1, 1, tz=pytz.timezone('US/Eastern'))
。反之,datetime 要在 pytz
时区对象上使用 localize
方法。
:::
在后台,所有 Timestamp 都存储为 UTC。含时区信息的 DatetimeIndex
或 Timestamp
的值有其自己的本地化时区字段(日、小时、分钟等)。不过,对于不同时区时间戳,如果其 UTC 值相同,将被视作是相等的时间。
1 2 3 4 5 6 7 8 9 10 11 12 In [432 ]: rng_eastern = rng_utc.tz_convert('US/Eastern' ) In [433 ]: rng_berlin = rng_utc.tz_convert('Europe/Berlin' ) In [434 ]: rng_eastern[2 ] Out[434 ]: Timestamp('2012-03-07 19:00:00-0500' , tz='US/Eastern' , freq='D' ) In [435 ]: rng_berlin[2 ] Out[435 ]: Timestamp('2012-03-08 01:00:00+0100' , tz='Europe/Berlin' , freq='D' ) In [436 ]: rng_eastern[2 ] == rng_berlin[2 ] Out[436 ]: True
不同时区 Series
之间的操作生成的是与 UTC 时间戳数据对齐的 UTC Series
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 In [437 ]: ts_utc = pd.Series(range (3 ), pd.date_range('20130101' , periods=3 , tz='UTC' )) In [438 ]: eastern = ts_utc.tz_convert('US/Eastern' ) In [439 ]: berlin = ts_utc.tz_convert('Europe/Berlin' ) In [440 ]: result = eastern + berlin In [441 ]: result Out[441 ]: 2013 -01-01 00 :00 :00 +00 :00 0 2013 -01-02 00 :00 :00 +00 :00 2 2013 -01-03 00 :00 :00 +00 :00 4 Freq: D, dtype: int64 In [442 ]: result.index Out[442 ]: DatetimeIndex(['2013-01-01 00:00:00+00:00' , '2013-01-02 00:00:00+00:00' , '2013-01-03 00:00:00+00:00' ], dtype='datetime64[ns, UTC]' , freq='D' )
用 tz_localize(None)
或 tz_convert(None)
去掉时区信息。tz_localize(None)
去掉带本地时间表示的时区信息。tz_convert(None)
先把时间戳转为 UTC 时间,再去掉时区信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 In [443 ]: didx = pd.date_range(start='2014-08-01 09:00' , freq='H' , .....: periods=3 , tz='US/Eastern' ) .....: In [444 ]: didx Out[444 ]: DatetimeIndex(['2014-08-01 09:00:00-04:00' , '2014-08-01 10:00:00-04:00' , '2014-08-01 11:00:00-04:00' ], dtype='datetime64[ns, US/Eastern]' , freq='H' ) In [445 ]: didx.tz_localize(None ) Out[445 ]: DatetimeIndex(['2014-08-01 09:00:00' , '2014-08-01 10:00:00' , '2014-08-01 11:00:00' ], dtype='datetime64[ns]' , freq='H' ) In [446 ]: didx.tz_convert(None ) Out[446 ]: DatetimeIndex(['2014-08-01 13:00:00' , '2014-08-01 14:00:00' , '2014-08-01 15:00:00' ], dtype='datetime64[ns]' , freq='H' ) In [447 ]: didx.tz_convert('UTC' ).tz_localize(None ) Out[447 ]: DatetimeIndex(['2014-08-01 13:00:00' , '2014-08-01 14:00:00' , '2014-08-01 15:00:00' ], dtype='datetime64[ns]' , freq='H' )
本地化导致的混淆时间 tz_localize
不能决定时间戳的 UTC偏移量,因为本地时区的夏时制(DST)会引起一些时间在一天内出现两次的问题(“时钟回调”)。下面的选项是有效的:
raise
:默认触发 pytz.AmbiguousTimeError
infer
:依据时间戳的单一性,尝试推断正确的偏移量NaT
:用 NaT
替换混淆时间bool
:True
代表夏时制(DST)时间,False
代表正常时间。数组型的 bool
值支持一组时间序列。1 2 3 In [448 ]: rng_hourly = pd.DatetimeIndex(['11/06/2011 00:00' , '11/06/2011 01:00' , .....: '11/06/2011 01:00' , '11/06/2011 02:00' ]) .....:
这种操作会引起混淆时间失败错误( ‘11/06/2011 01:00’)。
1 2 In [2 ]: rng_hourly.tz_localize('US/Eastern' ) AmbiguousTimeError: Cannot infer dst time from Timestamp('2011-11-06 01:00:00' ), try using the 'ambiguous' argument
用下列指定的关键字控制混淆时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 In [449 ]: rng_hourly.tz_localize('US/Eastern' , ambiguous='infer' ) Out[449 ]: DatetimeIndex(['2011-11-06 00:00:00-04:00' , '2011-11-06 01:00:00-04:00' , '2011-11-06 01:00:00-05:00' , '2011-11-06 02:00:00-05:00' ], dtype='datetime64[ns, US/Eastern]' , freq=None ) In [450 ]: rng_hourly.tz_localize('US/Eastern' , ambiguous='NaT' ) Out[450 ]: DatetimeIndex(['2011-11-06 00:00:00-04:00' , 'NaT' , 'NaT' , '2011-11-06 02:00:00-05:00' ], dtype='datetime64[ns, US/Eastern]' , freq=None ) In [451 ]: rng_hourly.tz_localize('US/Eastern' , ambiguous=[True , True , False , False ]) Out[451 ]: DatetimeIndex(['2011-11-06 00:00:00-04:00' , '2011-11-06 01:00:00-04:00' , '2011-11-06 01:00:00-05:00' , '2011-11-06 02:00:00-05:00' ], dtype='datetime64[ns, US/Eastern]' , freq=None )
本地化时不存在的时间 夏时制转换会移位本地时间一个小时,这样会创建一个不存在的本地时间(“时钟春季前滚”)。这种本地化操作会导致时间序列出现不存在的时间,此问题可以用 nonexistent
参数解决。下列都是有效的选项:
raise
:默认触发 pytz.NonExistentTimeError
NaT
:用 NaT
替换不存在的时间shift_forward
:把不存在的时间前移至最近的真实时间shift_backward
:把不存在的时间后滚至最近的真实时间Timedelta
对象:用 timedelta
移位不存在的时间1 2 3 In [452 ]: dti = pd.date_range(start='2015-03-29 02:30:00' , periods=3 , freq='H' )
对不存在的时间进行本地化操作默认会触发错误。
1 2 In [2 ]: dti.tz_localize('Europe/Warsaw' ) NonExistentTimeError: 2015 -03-29 02:30 :00
把不存在的时间转换为 NaT
或移位时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 In [453 ]: dti Out[453 ]: DatetimeIndex(['2015-03-29 02:30:00' , '2015-03-29 03:30:00' , '2015-03-29 04:30:00' ], dtype='datetime64[ns]' , freq='H' ) In [454 ]: dti.tz_localize('Europe/Warsaw' , nonexistent='shift_forward' ) Out[454 ]: DatetimeIndex(['2015-03-29 03:00:00+02:00' , '2015-03-29 03:30:00+02:00' , '2015-03-29 04:30:00+02:00' ], dtype='datetime64[ns, Europe/Warsaw]' , freq='H' ) In [455 ]: dti.tz_localize('Europe/Warsaw' , nonexistent='shift_backward' ) Out[455 ]: DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00' , '2015-03-29 03:30:00+02:00' , '2015-03-29 04:30:00+02:00' ], dtype='datetime64[ns, Europe/Warsaw]' , freq='H' ) In [456 ]: dti.tz_localize('Europe/Warsaw' , nonexistent=pd.Timedelta(1 , unit='H' )) Out[456 ]: DatetimeIndex(['2015-03-29 03:30:00+02:00' , '2015-03-29 03:30:00+02:00' , '2015-03-29 04:30:00+02:00' ], dtype='datetime64[ns, Europe/Warsaw]' , freq='H' ) In [457 ]: dti.tz_localize('Europe/Warsaw' , nonexistent='NaT' ) Out[457 ]: DatetimeIndex(['NaT' , '2015-03-29 03:30:00+02:00' , '2015-03-29 04:30:00+02:00' ], dtype='datetime64[ns, Europe/Warsaw]' , freq='H' )
时区序列操作 无时区 Series
值的数据类型是 datetime64[ns]。
1 2 3 4 5 6 7 8 In [458 ]: s_naive = pd.Series(pd.date_range('20130101' , periods=3 )) In [459 ]: s_naive Out[459 ]: 0 2013 -01-011 2013 -01-022 2013 -01-03dtype: datetime64[ns]
有时区 Series
值的数据类型是 datetime64[ns, tz],tz
指的是时区。
1 2 3 4 5 6 7 8 In [460 ]: s_aware = pd.Series(pd.date_range('20130101' , periods=3 , tz='US/Eastern' )) In [461 ]: s_aware Out[461 ]: 0 2013 -01-01 00 :00 :00 -05:00 1 2013 -01-02 00 :00 :00 -05:00 2 2013 -01-03 00 :00 :00 -05:00 dtype: datetime64[ns, US/Eastern]
这两种 Series
的时区信息都可以用 .dt
访问器操控,参阅 dt 访问器 。
例如,本地化与把无时区时间戳转换为有时区时间戳。
1 2 3 4 5 6 In [462 ]: s_naive.dt.tz_localize('UTC' ).dt.tz_convert('US/Eastern' ) Out[462 ]: 0 2012 -12 -31 19 :00 :00 -05:00 1 2013 -01-01 19 :00 :00 -05:00 2 2013 -01-02 19 :00 :00 -05:00 dtype: datetime64[ns, US/Eastern]
时区信息还可以用 astype
操控。这种方法可以本地化并转换无时区时间戳或转换有时区时间戳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 In [463 ]: s_naive.astype('datetime64[ns, US/Eastern]' ) Out[463 ]: 0 2012 -12 -31 19 :00 :00 -05:00 1 2013 -01-01 19 :00 :00 -05:00 2 2013 -01-02 19 :00 :00 -05:00 dtype: datetime64[ns, US/Eastern] In [464 ]: s_aware.astype('datetime64[ns]' ) Out[464 ]: 0 2013 -01-01 05:00 :00 1 2013 -01-02 05:00 :00 2 2013 -01-03 05:00 :00 dtype: datetime64[ns] In [465 ]: s_aware.astype('datetime64[ns, CET]' ) Out[465 ]: 0 2013 -01-01 06:00 :00 +01:00 1 2013 -01-02 06:00 :00 +01:00 2 2013 -01-03 06:00 :00 +01:00 dtype: datetime64[ns, CET]
::: tip 注意
在 Series
上应用 Series.to_numpy()
,返回数据的 NumPy 数组。虽然 NumPy 可以输出 本地时区!但其实它当前并不支持时区,因此,有时区时间戳数据返回的是时间戳对象数组:
1 2 3 4 5 6 7 8 9 10 11 In [466 ]: s_naive.to_numpy() Out[466 ]: array(['2013-01-01T00:00:00.000000000' , '2013-01-02T00:00:00.000000000' , '2013-01-03T00:00:00.000000000' ], dtype='datetime64[ns]' ) In [467 ]: s_aware.to_numpy() Out[467 ]: array([Timestamp('2013-01-01 00:00:00-0500' , tz='US/Eastern' , freq='D' ), Timestamp('2013-01-02 00:00:00-0500' , tz='US/Eastern' , freq='D' ), Timestamp('2013-01-03 00:00:00-0500' , tz='US/Eastern' , freq='D' )], dtype=object )
通过转换时间戳数组,保留时区信息。例如,转换回 Series
时:
1 2 3 4 5 6 In [468 ]: pd.Series(s_aware.to_numpy()) Out[468 ]: 0 2013 -01-01 00 :00 :00 -05:00 1 2013 -01-02 00 :00 :00 -05:00 2 2013 -01-03 00 :00 :00 -05:00 dtype: datetime64[ns, US/Eastern]
如果需要 NumPy datetime64[ns]
数组(带已转为 UTC 的值)而不是对象数组,可以指定 dtype
参数:
1 2 3 4 In [469 ]: s_aware.to_numpy(dtype='datetime64[ns]' ) Out[469 ]: array(['2013-01-01T05:00:00.000000000' , '2013-01-02T05:00:00.000000000' , '2013-01-03T05:00:00.000000000' ], dtype='datetime64[ns]' )
:::