pandas中的时间序列一般被认为是不规则的,也就是说,没有固定的频率。但pandas有一整套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。例如将一个时间序列转换为固定频率的时间序列,只需要调用resample即可。
频率的转换(或重采样)是一个大的主题,这里先介绍如何使用基本的频率。
1.1 生成日期范围
pandas.date_range()可用于生成指定长度的DatetimeIndex。
1 >>> index = pd.date_range('1/1/2020', '1/10/2020') 2 >>> index 3 DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', 4 '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08', 5 '2020-01-09', '2020-01-10'], 6 dtype='datetime64[ns]', freq='D') 7 >>>
默认情况下,date_range会产生按天计算的时间点,如果只传入起始或结束日期。那就还得传入一个表示一段时间的数字。
1 >>> pd.date_range(start='1/1/2020', periods=20) 2 DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', 3 '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08', 4 '2020-01-09', '2020-01-10', '2020-01-11', '2020-01-12', 5 '2020-01-13', '2020-01-14', '2020-01-15', '2020-01-16', 6 '2020-01-17', '2020-01-18', '2020-01-19', '2020-01-20'], 7 dtype='datetime64[ns]', freq='D') 8 >>> pd.date_range(end='1/20/2020', periods=20) 9 DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', 10 '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08', 11 '2020-01-09', '2020-01-10', '2020-01-11', '2020-01-12', 12 '2020-01-13', '2020-01-14', '2020-01-15', '2020-01-16', 13 '2020-01-17', '2020-01-18', '2020-01-19', '2020-01-20'], 14 dtype='datetime64[ns]', freq='D') 15 >>>
起始和结束日期定义了日期索引的严格边界。例如,如果你想要生成由每月最后一个工作日组成的日期索引,可以传入“BM”频率(表示business end of month),这样只会包含时间间隔内(或刚好在边界上)符合频率要求的日期。
1 >>> pd.date_range('1/1/2000', '12/1/2000', freq='BM') 2 DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', '2000-04-28', 3 '2000-05-31', '2000-06-30', '2000-07-31', '2000-08-31', 4 '2000-09-29', '2000-10-31', '2000-11-30'], 5 dtype='datetime64[ns]', freq='BM') 6 >>>
date_range默认会保留起始和结束时间戳的时间信息。
1 >>> pd.date_range('5/2/2012 12:56:31', periods=5) 2 DatetimeIndex(['2012-05-02 12:56:31', '2012-05-03 12:56:31', 3 '2012-05-04 12:56:31', '2012-05-05 12:56:31', 4 '2012-05-06 12:56:31'], 5 dtype='datetime64[ns]', freq='D')
有时,虽然起始和结束日期都带有时间信息,但希望产生一组被规范化(normalize)到午夜的时间戳,normalize选项即可实现该功能。
1 >>> pd.date_range('5/2/2012 12:56:31', periods=5, normalize=True) 2 DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05', 3 '2012-05-06'], 4 dtype='datetime64[ns]', freq='D')
1.2 频率与日期偏移量
pandas的频率由一个基础频率(base frequency)和一个乘数组成的。基础频率通常以一个字符串别名表示,比如“M”表示每月,“H”表示每小时。对于每个基础频率,都有一个被称为日期偏移量(date offset)的对象与之对应。如,按小时计算的频率可以用Hour类表示。
1 >>> from pandas.tseries.offsets import Hour, Minute 2 >>> hour = Hour() 3 >>> hour 4 <Hour> 5 >>> four_hours = Hour(4) #定义偏移量的倍数 6 >>> four_hours 7 <4 * Hours>
一般来说,无需显示创建这样的对象,只需要使用诸如“H”或“4H”这样的字符串别名即可。在基础频率前放一个整数即可创建倍数。
1 >>> pd.date_range('1/1/2020', '1/3/2020 23:59', freq='4H') 2 DatetimeIndex(['2020-01-01 00:00:00', '2020-01-01 04:00:00', 3 '2020-01-01 08:00:00', '2020-01-01 12:00:00', 4 '2020-01-01 16:00:00', '2020-01-01 20:00:00', 5 '2020-01-02 00:00:00', '2020-01-02 04:00:00', 6 '2020-01-02 08:00:00', '2020-01-02 12:00:00', 7 '2020-01-02 16:00:00', '2020-01-02 20:00:00', 8 '2020-01-03 00:00:00', '2020-01-03 04:00:00', 9 '2020-01-03 08:00:00', '2020-01-03 12:00:00', 10 '2020-01-03 16:00:00', '2020-01-03 20:00:00'], 11 dtype='datetime64[ns]', freq='4H')
大部分偏移量对象可通过加法进行连接。
1 >>> Hour(2) + Minute(30) 2 <150 * Minutes>
同理,也可以传入频率字符串,这种字符串可以被高效的解析为等效的表达式。
1 >>> pd.date_range('1/1/2020', periods=10, freq='1h30min') 2 DatetimeIndex(['2020-01-01 00:00:00', '2020-01-01 01:30:00', 3 '2020-01-01 03:00:00', '2020-01-01 04:30:00', 4 '2020-01-01 06:00:00', '2020-01-01 07:30:00', 5 '2020-01-01 09:00:00', '2020-01-01 10:30:00', 6 '2020-01-01 12:00:00', '2020-01-01 13:30:00'], 7 dtype='datetime64[ns]', freq='90T')
有些频率说描述的时间点并不是均匀分隔的,例如:“M”(日历月末)和“BM”(每月最后一个工作日)就取决于每月的天数。对于后者,还要考虑月末是不是周末。我们将这些称为瞄点偏移量(anchored offset)。下表是pandas中频率代码和日期偏移量类。
别名 |
偏移量类型 |
说明 |
D |
Day |
每日历日 |
B |
BusinessDay |
每工作日 |
H |
Hour |
每小时 |
T或min |
Minute |
每分 |
S |
Second |
每秒 |
L或ms |
Milli |
每毫秒(即每千分之一秒) |
Y |
Micro |
每微妙(即每百万分之一秒) |
M |
MonthEnd |
每月最后一个日历日 |
BM |
BusinessMonthEnd |
每月最后一个工作日 |
MS |
MonthBegin |
每月第一个日历日 |
BMS |
BusinessMonthBegin |
每月第一个工作日 |
W-MON、W-TUE… |
Week |
从指定的星期几(MON、TUE、WED、THU、FRI、SAT、SUM)开始算起,每周 |
WOM-1MON、WOM-2MON… |
WeekOfMonth |
产生每月第一、第二、第三或第四周的星期几。例如,WOM-3FRI表示每月第三个星期五 |
Q-JAN、Q-FEB… |
QuarterEnd |
对于以指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、DEC)结束的年度,每季度最后一月的最后一个日历日 |
BQ-JAN、BQ-FEB… |
BusinessQuarterEnd |
对于以指定月份结束的年度,每季度最后一月的最后一个工作日 |
QS-JAN、QS-FEB… |
QuarterBegin |
对于以指定月份结束的年度,么季度最后一月的第一个日历日 |
BQS-JAN、BQS-FEB… |
BusinessQuarterBegin |
对于以指定月份结束的年度。每季度最后一月的第一个工作日 |
A-JAN、A-FEB… |
YearEnd |
每年指定月份的最后一个日历日 |
BA-JAN、BA-FEB… |
BusinessYearEnd |
每年指定月份的最后一个工作日 |
AS-JAN、AS-FEB… |
YearBegin |
每年指定月份的第一个日历日 |
BAS-JAN、BAS-FEB… |
BusinessYearBegin |
每年指定月份的第一个工作日 |
WOM日期:WOM(Week Of Month)是一种很实用的频率类。它使你能够获得诸如“每月第三个星期五”之类的日期:
1 >>> rng = pd.date_range('1/1/2020', '9/1/2020', freq='WOM-3FRI') 2 >>> list(rng) 3 [Timestamp('2020-01-17 00:00:00', freq='WOM-3FRI'), Timestamp('2020-02-21 00:00:00', freq='WOM-3FRI'), Timestamp('2020-03-20 00:00:00', freq='WOM-3FRI'), Timestamp('2020-04-17 4 00:00:00', freq='WOM-3FRI'), Timestamp('2020-05-15 00:00:00', freq='WOM-3FRI'), Timestamp('2020-06-19 00:00:00', freq='WOM-3FRI'), Timestamp('2020-07-17 00:00:00', freq='WOM-3FRI'), Timestamp('2020-08-21 00:00:00', freq='WOM-3FRI')] 5 >>>
1.3 移动(超前或滞后)数据
移动(shifting)指的是沿着时间轴将数据前移或者后移。Series和DataFrame都有一个shift方法用于执行单纯的前移或后移操作,保持索引不变。
1 >>> ts = pd.Series(np.random.randn(4), index=pd.date_range('1/1/2000', periods=4, freq='M')) 2 >>> ts 3 2000-01-31 -1.341160 4 2000-02-29 1.146953 5 2000-03-31 -1.434801 6 2000-04-30 -1.175278 7 Freq: M, dtype: float64 8 >>> ts.shift(2) 9 2000-01-31 NaN 10 2000-02-29 NaN 11 2000-03-31 -1.341160 12 2000-04-30 1.146953 13 Freq: M, dtype: float64 14 >>> ts.shift(-2) 15 2000-01-31 -1.434801 16 2000-02-29 -1.175278 17 2000-03-31 NaN 18 2000-04-30 NaN 19 Freq: M, dtype: float64 20 >>>
shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。可以这样表达:ts/ts.shift(1) – 1。
由于单纯的移位操作不会修改索引,所以部分数据会被丢弃。因此,如果频率已知,则可以将其传给shift以便实现对时间戳进行移位而不是对数据进行简单移位。
1 >>> ts 2 2000-01-31 -1.341160 3 2000-02-29 1.146953 4 2000-03-31 -1.434801 5 2000-04-30 -1.175278 6 Freq: M, dtype: float64 7 >>> ts.shift(2, freq='M') 8 2000-03-31 -1.341160 9 2000-04-30 1.146953 10 2000-05-31 -1.434801 11 2000-06-30 -1.175278 12 Freq: M, dtype: float64 13 >>> ts.shift(3, freq='D') 14 2000-02-03 -1.341160 15 2000-03-03 1.146953 16 2000-04-03 -1.434801 17 2000-05-03 -1.175278 18 dtype: float64 19 >>> ts.shift(1, freq='3D') 20 2000-02-03 -1.341160 21 2000-03-03 1.146953 22 2000-04-03 -1.434801 23 2000-05-03 -1.175278 24 dtype: float64 25 >>> ts.shift(1, freq='90T') 26 2000-01-31 01:30:00 -1.341160 27 2000-02-29 01:30:00 1.146953 28 2000-03-31 01:30:00 -1.434801 29 2000-04-30 01:30:00 -1.175278 30 Freq: M, dtype: float64 31 >>>
1.4 通过偏移量对日期进行位移
pandas的日期偏移量还可以对用在datetime或timestamp对象上。
如果加的是锚点偏移量(比如MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期。
通过锚点偏移量的rollforward和rollback方法,可以显示的将日期向前或先后“滚动”。
1 >>> from pandas.tseries.offsets import Day, MonthEnd 2 >>> now = datetime(2011, 11, 17) 3 >>> now + 3*Day() #增加锚点偏移量 4 Timestamp('2011-11-20 00:00:00') 5 >>> now + MonthEnd() 6 Timestamp('2011-11-30 00:00:00') 7 >>> now + MonthEnd(2) 8 Timestamp('2011-12-31 00:00:00') 9 >>> offset = MonthEnd() 10 >>> offset.rollforward(now) #前滚 11 Timestamp('2011-11-30 00:00:00') 12 >>> offset.rollback(now) #后滚 13 Timestamp('2011-10-31 00:00:00')
日期偏移量还有一个巧妙地用法,即结合groupby使用这两个“滚动”方法:
1 >>> ts = pd.Series(np.random.randn(20), index=pd.date_range('1/15/2000', periods=20, freq='4d')) 2 >>> ts.groupby(offset.rollforward).mean() 3 2000-01-31 -0.472525 4 2000-02-29 -0.328598 5 2000-03-31 -0.620547 6 dtype: float64 7 >>> ts 8 2000-01-15 1.318127 9 2000-01-19 -1.589067 10 2000-01-23 -1.157066 11 2000-01-27 -0.621659 12 2000-01-31 -0.312961 13 2000-02-04 -0.839835 14 2000-02-08 0.886033 15 2000-02-12 -0.478009 16 2000-02-16 -0.383263 17 2000-02-20 1.837059 18 2000-02-24 -1.343584 19 2000-02-28 -1.978589 20 2000-03-03 -1.372423 21 2000-03-07 -0.837760 22 2000-03-11 -0.329621 23 2000-03-15 -0.237106 24 2000-03-19 -0.596162 25 2000-03-23 -1.020840 26 2000-03-27 -0.037938 27 2000-03-31 -0.532527 28 Freq: 4D, dtype: float64