导读
pandas是Python数据分析最好用的第三方库,没有之一。——笛卡儿没说过这句话!
在进行时间相关的数据分析时,时间序列的处理是自然而然的事情,从创建、格式转换到筛选、重采样和聚合统计,pandas都提供了全套方法支持,用的熟练简直是异常丝滑。
01 创建
pandas时间序列创建最为常用的有以下2种方式:
- pd.date_range(),创建指定日期范围,start、end和periods三个参数任意指定2个即可,另有频率、开闭端点、时区等参数可选
- pd.Timestamp(),时间戳对象,从其首字母大写的命名方式可以看出这是pandas中的一个类,实际上相当于Python标准库中的datetime的定位,在创建时间对象时可接受日期字符串、时间戳数值或分别指定年月日时分秒等参数三类,仅能生成单一时间点。其优点是Timestamp类提供了丰富的时间处理接口,如日期加减、属性提取等
与二者类似,pandas还提供了pd.period和pd.period_range两个方法,分别用于创建单个时期和时期序列。这里时期是一段时间,而date或timestamp则是一个时间点。
02 转换
实际应用中,与时间格式相互转换最多的应该就是字符串格式了,这也是最为常用也最为经典的时间转换需求,pandas中自然也带有这一功能:
- pd.to_datetime:字符串转时间格式
- dt.astype(str):时间提取字符串
其中,pd.to_datetime可接受单个或多个日期数值,具体类型包括数值型、字符串、数组或pd.series等序列,其中字符串日期格式几乎包含了所有可能的组成形式,例如"年/月/日","月/日/年"和"月-日-年"等形式,字符串转换日期也是实际应用中最为常见的需求。反之,对于日期格式转换为相应的字符串形式,pandas则提供了时间格式的"dt"属性,类似于pandas为字符串类型提供了str属性及相应方法,时间格式的"dt"属性也支持大量丰富的接口。例如dt.date可提取日期,dt.time则可提取时间。
需要指出,时间序列在pandas.dataframe数据结构中,当该时间序列是索引时,则可直接调用相应的属性;若该时间序列是dataframe中的一列时,则需先调用dt属性再调用接口。举例如下:
1.首先创建数据结构如下,其中初始dataframe索引是时间序列,两列数据分别为数值型和字符串型
2.运用to_datetime将B列字符串格式转换为时间序列
3.分别访问索引序列中的时间和B列中的日期,并输出字符串格式
03 筛选
处理时间序列的另一个常用需求是筛选指定范围的数据,例如选取特定时段、特定日期等。实现这一目的,个人较为常用的有3种方法:
- 索引模糊匹配,这实际上算是pandas索引访问的一个通用策略,所以自然在时间筛选中也适用
- truncate,截断函数,通过接受before和after参数,实现筛选特定范围内的数据,其中两个参数中可有一个缺省,表示半开区间
- dt.between,也是借助时间序列的dt属性,接受起始和结束参数,实现特定范围筛选
以这一数据作为示例,其中索引时间序列,需求是筛选出上午7点-9点间的记录,则3种实现方式分别示例如下:
1.通过索引模糊匹配,由于是要查询7点-9点间的记录,这等价于通过行索引查询以07到08开头之间的数据,查询结果如下:
实际上,这是pandas行索引访问的通用策略,即模糊匹配。当然,虽然同样是执行的模糊匹配,但对于时间序列和字符串序列的匹配策略还是略有不同:时间序列执行的模糊匹配是"截断式",即只要当前匹配,则进行筛选保留;而字符串序列执行的模糊匹配是"比较式",也就是说在执行范围查询时实际上是将各索引逐一与查询范围进行比较字符串大小,若满足区间则筛选保留。这里补充一个将时间序列索引转化为字符串格式的普通索引后的模糊匹配例子,可自行体会下二者的区别:
2.truncate截断函数,实际上这也不是一个时间序列的专用方法,而仅仅是pandas中布尔索引的一种简略写法:通过逐一将索引与起始值比较得出布尔值,从而完成筛选。例如,仍然查询7点-9点间的记录,得到以下结果:
3.dt.between,这是一个真正意义上的时间序列筛选方法,通过访问dt属性,并指定起止时间,从而完成指定时间范围的记录筛选。其具体用法有些类似SQL中的between。需注意的是该方法主要用于数据列的时间筛选,其最大优势在于可指定时间属性比较,例如可以指定time字段根据时间筛选而不考虑日期范围,也可以指定日期范围而不考虑时间取值,这在有些场景下是非常实用的。
04 重采样
重采样是pandas时间序列中的一个特色操作,在有些连续时间记录需要按某一指定周期进行聚合统计时尤为有效,实现这一功能的函数主要是resample。这里resample意为重采样,具体又包括上采样和下采样:前者也叫升采样,意为着采样后频率升高,如从2小时一个周期变为1小时一个周期;而后者也叫降采样,采样后频率降低,如从1小时变为2小时采样。仍然以前述的时间索引记录为例,首先将其按4小时为周期进行采样,此时在每个4小时周期内的所有记录汇聚为一条结果,所以自然涉及到聚合函数的问题,包括计数、求均值、累和等等。
在完成4小时降采样的基础上,如果此时需要周期为2小时的采样结果,则就是上采样。直观来看,由于此时是将6条记录结果上升为12条记录结果,而这些数据不会凭空出现,所以如果说下采样需要聚合、上采样则需要空值填充,常用方法包括前向填充、后向填充等。这里我们结合业务实际,采取前向填充的方式,得到2小时采样结果如下:
关于pandas时间序列的重采样,再补充两点:1.重采样函数可以和groupby分组聚合函数组合使用,可实现更为精细的功能,具体可参考Pandas中groupby的这些用法你都知道吗一文;2.重采样过程中,无论是上采样还是下采样,其采样结果范围是输入记录中的最小值和最大值覆盖的范围,所以当输入序列中为两段不连续的时间序列记录时,可能会出现中间大量不需要的结果(笔者亲历天坑),同时在上图中也可发现从4小时上采样为2小时后时间最大范围是20:00,而非22:00,也是这个原因。
05 滑动窗口
理解pandas中时间序列滑动窗口的最好方式是类比SQL中的窗口函数。实际上,其与分组聚合函数的联系和SQL中的窗口函数与分组聚合联系是一致的。常用的滑动窗口函数主要有3个:
- shift,向前或向后取值
- diff,向前或向后去差值
- rolling,一段滑动窗口内聚合取值
仍以前述时间序列数据为例,为了便于比较,首先再次给出数据序列
1.shift完成向前或向后滑动取值,periods参数设置滑动长度,freq设置滑动参考周期,默认为空,此时仅仅是向后读取一条记录
设置freq=10T,向后滑动10分钟后取值。
值得指出,这里的滑动取值可以这样理解:periods参数为正数时,可以想象成索引列不动,数据列向后滑动;反之,periods参数为负数时,索引列不动,数据列向前滑动。进一步的,当freq参数为None时,则仅仅是滑动指定数目的记录,而不管索引实际取值;而当freq设置有效参数时,此时要求索引列必须为时间序列,并根据时间序列滑动到指定周期处,并从此处开始取值(在上图中,体现为10T之前的记录不再保留)。
2.在理解shift操作的基础上,diff函数用于取差值就容易得多,且比其更为简单的是diff操作只支持记录间的差值,而不支持指定周期。接受参数主要是periods:当其为正数时,表示当前值与前面的值相减的结果;反之,当其未负数时,表示当前值与后面的值相减。
以差值窗口长度=1为例,实际上此时只是简单的执行当前值与其前一个值的差,其应用shift的等价形式即为:
3.rolling,这是一个原原本本的滑动窗口,适用场景是连续求解一段时间内的某一指标。例如,求解连续3条记录的均值,则可简单实现如下:
注意到由于窗口长度设置为3,前两条记录因为"向前凑不齐"3条,所以结果为空值。当然,就这一特定需求而言,也可由shift函数实现: