一只码畜 2020-01-28
Pandas 是非常著名的开源数据处理库,我们可以通过它完成对数据集进行快速读取、转换、过滤、分析等一系列操作。同样,Pandas 已经被证明为是非常强大的用于处理时间序列数据的工具。本节将介绍所有 Pandas 在时间序列数据上的处理方法。
在 Pandas 中关于时间序列的常见对象有 6 种,分别是 Timestamp(时间戳)、DatetimeIndex(时间戳索引)、Period(时间段)、PeriodIndex(时间段索引)、以时间为元素的 Series 和以及以时间索引的 DataFrame。本小节学习如何创建以上对象。
Timestamp 时间戳表示时间轴上的某一点,以下不同代码都可以生成相同时间戳。
创建时间为 2018 年 10 月 1 日的时间戳。
import pandas as pd pd.Timestamp(2018, 10, 1)
也可以使创建的时间精确到时分秒。
pd.Timestamp("2018-10-1 10:00:1")
from datetime import datetime pd.Timestamp(datetime(2018, 10, 1))
Period 时间段表示时间轴上的某一区间,以下代码都可以生成相同时间段。
pd.Period('2018-10')
Period()
函数后面通常有两个参数,第二个 freq
参数决定时间段的分割长度。
创建频率为日的时间段。
pd.Period('2018-10', freq='D')
Pandas 中常用 to_datetime()
函数可以创建以时间为元素的 Series。
创建一个 Series,以三个时间的字符串作为元素。
df = ['2018-08-01', '2018-09-01', '2018-10-01'] pd.to_datetime(df)
可以使用多种方法创建时间元素的 Series。
df = pd.Series(['Sep 30, 2018', '2018-10-1', None]) pd.to_datetime(df)
df = pd.DataFrame({'year': [2017, 2018], 'month': [9, 10], 'day': [30, 1], 'hour': [23, 0]}) pd.to_datetime(df)
要生成带有时间戳的索引,可以使用 DatetimeIndex()
构造函数,并传入列表或 Series 对象:
dates = ['2018-08-01', '2018-09-01', '2018-10-01'] index = pd.DatetimeIndex(dates) index
实际运用中我们经常需要大量的的时间戳的索引。可以使用 date_range()
和 bdate_range()
来批量创建相同时间间隔的时间戳索引。
创建以 2018 年 9 月 30 日为开始的 250 条时间索引,相邻索引间隔时间长度为一个月。
index = pd.date_range('2018-9-30', periods=250, freq='M') index
创建以 2018 年 10 月 1 日为开始的 111 条时间索引,相邻索引间隔时间长度为一个工作日。
index = pd.bdate_range('2018-10-1', periods=111) index
在 date_range()
和 bdate_range()
中可以巧妙使用 start,end, periods,freq 等参数的各种组合轻松批量创建时间索引。
在 2017 年 10 月 1 日到 2018 年 10 月 1 日间,每隔一周创建一条索引。
start = datetime(2017, 10, 1) end = datetime(2018, 10, 1) rng = pd.date_range(start, end, freq='W') rng
从 2018 年 10 月 1 日向前每隔一个工作日创建一条索引,共 250 条。
pd.bdate_range(end=end, periods=250)
同理,时间段也能作为索引使用,需要用到 period_range()
。
从 2018 年 9 月 30 日向后创建 666 条索引,相邻索引间隔时间长度为一天。
pi = pd.period_range('2018-9-30', periods=666) pi
以时间为索引的 Series 对象指的是在该 Series 中,元素的索引不再是 1、2、3、4、5……这样的序号,而是有序的日期和时间。
import numpy as np dates = [pd.Timestamp('2018-08-01'), pd.Timestamp('2018-09-01'), pd.Timestamp('2018-10-01')] # 创建三个时间元素。 ts = pd.Series(np.random.randn(3), dates) # 创建索引值为随机数的 Series 对象。 ts
同样,时间段也能作为索引。
periods = [pd.Period('2018-08'), pd.Period('2018-09'), pd.Period('2018-10')] ts = pd.Series(np.random.randn(3), periods) ts
我们可以批量创建索引后再创建以时间为索引的 Series 对象。创建索引值为随机数的 Series 对象,长度与 rng 长度相同。
ts = pd.Series(np.random.randn(len(rng)), index=rng) ts
时间段也能作为索引创建 DataFrame 对象。在 2017 年第一季度和 2018 年第四季度之间每隔一个季度创建一条索引。
prng = pd.period_range('2017Q1', '2018Q4', freq='Q-NOV') # 行索引为时间段索引,列索引为 A。 ps = pd.DataFrame(np.random.rand(len(prng)), columns=[ 'A'], index=prng) ps
以时间戳为索引的 Series、DataFrame 对象具有与普通列表近乎相同的操作,且更具智能化。
简单查找。
ts
查找前 10 条索引记录。
ts[:10]
每隔 1 条记录查找 1 条索引记录。
ts[::2]
查找第 0、2、6 条索引记录。
ts[[0, 2, 6]]
基于时间索引的精确查找。查找索引为 2018 年 9 月 30 日的值。
ts["09/30/2018"]
ts[datetime(2018, 9, 30)]
基于索引的范围查找。查找索引时间在 2017 年内的所有记录。
ts["2017"]
查找索引时间在 2018 年 9 月内的所有记录。
ts["2018-9"]
以时间段为索引的 DataFrame 对象的查找规则与以时间戳的相同。
ps
2018 年的第一个季度规定为 2017 年的 12 月初到 2018 年的 2 月末。
查找 2017 年内的所有季度的记录。
ps["2017"]
查找 2017 年 12 月 31 日前的所有季度的记录。
ps[:datetime(2017, 12, 31)]
查找 2018 年 6 月内的所有季度的记录。
ps["2018-06"]
使用 truncate()
切下 2017 年 11 月 26 日与 2018 年 4 月 29 日间的记录。
ts.truncate(before='11/26/2017', after='4/29/2018')
将时间索引 Series 中的值向后和向前移动。其方法是 shift()
。
ts = ts[:5] # 取前 5 条数据方便观察。 ts
将元素列向下移动一条。
ts.shift(1)
除了元素可以被移动,索引本身也能被移动,需要加上 freq
参数。将索引列向上移动一条:
ts.shift(1, freq='W')
重采样可以通俗得理解为改变时间索引的个数,通过增大或减小相邻索引的时间间隔以达到减小或增加索引数量的效果,在 Pandas 中使用 resample()
函数。
下采样:增大时间间隔,减少记录的数量。创建从 2018 年 10 月 1 日开始的日间隔索引的 Series 。
rng = pd.date_range('10/1/2018', periods=10, freq='D') ts = pd.Series(np.random.randint(0, 50, len(rng)), index=rng) ts
原先索引的日间隔被扩大为周间隔,并以周末为索引采样点,采样点的索引值为所有未被索引值的和。
ts.resample('W').sum()
同样也能使采样点的索引值为所有未被索引值的平均值。
ts.resample('W').mean()
使用 ohlc()
函数对所用未被采样值进行统计。
ts.resample('W').ohlc()
上采样:减小时间间隔频率,增加记录的数量。
原来间隔为日的索引列,间隔被缩小成 12 小时,增加采样点的值为空值。
ts.resample('12H').asfreq()
ffill()
函数可以将新增的索引值以相邻的前一条索引值进行填充。
ts.resample('12H').ffill()
下表是 Pandas 内建的一些时间类,常用于时间索引的位移。
首先要导入 pandas.tseries.offsets
模块,Pandas 所有常用时间类都在该模块中。
d = pd.Timestamp(2018, 10, 1, 10, 1, 1) d
使用 DateOffset()
实现时间戳位移。
向后移动一个月零两天。
from pandas.tseries.offsets import DateOffset d + DateOffset(months=1, days=2)
也可以用时间戳加减常用时间类以实现时间戳位移。向前移动 10 个工作日。
from pandas.tseries.offsets import BDay d - 10 * BDay()
向后移动一个月末。
from pandas.tseries.offsets import BMonthEnd d + BMonthEnd()
个性化定制日期。虽然日历规定年末是 12 月,加入参数后相当于人为规定 2 月是年末。
向后移动到上两个年末。
from pandas.tseries.offsets import YearEnd d + YearEnd(month=2)
向前移动到上一个周四。
from pandas.tseries.offsets import Week d - Week(weekday=4)
可以使用 rollforward()
将指定时间向前或向后移动到一个制定常用时间类的时间戳上。将时间移动到下一个月末:
offset = BMonthEnd() offset.rollforward(d)
将时间移动到上一个月末。
offset.rollback(d)
偏移也同样适用于时间索引
rng
所有的时间索引向后移动两日。
rng + DateOffset(days=2)
所有的时间索引向后移动两个工作日。
rng + 2*BDay()
所有的时间索引向后移动 15 分钟。
from pandas.tseries.offsets import Minute rng + Minute(15)
下列是常用时间系列频率参数,上面小节经常出现,现在以一个表格作详细说明。
参数名 | 说明 |
---|---|
B | 工作日频率 |
C | 定制工作日频率 |
D | 日历日频率 |
W | 每周频率 |
M | 月结束频率 |
SM | 半月结束频率(15 个月和月末) |
BM | 业务月末频率 |
CBM | 定制业务月末频率 |
MS | 月起始频率 |
sMs | 半月起始频率(第 1 和 15) |
BMS | 业务月开始频率 |
CBMS | 定制商业月份开始频率 |
Q | 四分频结束频率 |
BQ | 业务四分之一频率 |
QS | 四分频启动频率 |
BQS | 业务季开始频率 |
A | 年结束频率 |
BA | 业务年结束频率 |
AS | 年起始频率 |
BAS | 业务年开始频率 |
BH | 工作时间频率 |
H | 每小时频率 |
T, min | 分钟频率 |
S | 次频 |
L, ms | 毫秒 |
U, uS | 微秒 |
N | 纳秒 |
使用常用频率参数组合创建时间索引。
创建 10 条以 2018 年 10 月 1 日为开始,间隔为 1 天 1 小时 1 分钟 10 微秒的时间索引。
pd.date_range("2018-10-1", periods=10, freq='1D1H1min10U')
以下频率参数可以指定后缀以达到改变默认间隔点的效果。
创建 10 条以 2018 年 10 月 1 日为开始,间隔为每周三的时间索引。
pd.date_range("2018-10-1", periods=10, freq='W-WED')
在使用特定频率(MonthEnd,MonthBegin,WeekEnd 等)的参数时,如果起始时间是刚好在频率点上,使用 n
参数可以决定是否让该点参与计算。
n=1
时参与计算。
from pandas.tseries.offsets import MonthBegin pd.Timestamp('2018-10-1') + MonthBegin(n=1)
n=0
时不参与计算。
pd.Timestamp('2018-10-1') + MonthBegin(n=0)
下采样中的聚合是指下采样后,对未被采样到的点进行的一系列计算。
创建 100 个日历日为时间索引的 DataFrame,将其以月频率下采样。
df = pd.DataFrame(np.random.rand(100, 3), index=pd.date_range('10/1/2018', freq='D', periods=100), columns=['A', 'B', 'C']) r = df.resample('M') r
对未采样点求和,结果保存在采样点的值中。
r.sum()
在下采样后也能进行查找操作。选择 A、C 列后取均值计算。
r[['A', 'C']].mean()
使用 agg()
同时进行不同的计算。对采样结果进行取和与取均值计算。
r.agg([np.sum, np.mean])
选择 A 列,同时进行取和,取均值,取标准差计算。
r['A'].agg([np.sum, np.mean, np.std]
对 A 列求和与标准差,对 B 列求均值与标准差。
r.agg({'A': ['sum', 'std'], 'B': ['mean', 'std']})
本章节介绍了 Pandas 对时间序列数据的基本处理操作。重点演示了时间的创建、时间索引对象的处理、时间的相关计算。当然,文中对这些方法的介绍依然还不够详细。如果你需要在实际工作中进行更复杂的时间数据处理,还需要深刻理解文中的基本演示,改编或组合出更高级的功能,这样才能发挥出 Pandas 的强大作用。