3.4 日期时间处理
日期时间处理是Python编程的必备技能,如从计算某一段程序的运行时长,到统计建模中某一列日期或时间戳类型数据,再到时间序列问题中通过历史数据预测未来趋势,在经济、金融、医学等领域中被广泛使用。Python的datatime包含一系列的时间处理工具,Pandas库也自带时序工具,还支持将时间类型作为数据表的索引及作图使用。
3.4.1 Python日期时间处理
1.时间点
Python的标准库提供了datetime系列工具,date用于处理日期信息,time用于处理时间信息,datetime可同时处理时间信息和日期信息,也是最常用的工具。
本例中首先导入datetime工具,使用now方法取当前日期时间,然后通过datetime的属性year,month等获取其具体数据。另外,也可以使用指定具体年月日时分秒的方法构造datetime数据,省略的时分秒参数被默认为0。
2.时间段
时间段timedelta用于表示两个时间点的差值,可以通过datetime数据相减得到,也可以通过指定具体时间差的方式构造。本例中使用了上例创建的时间日期变量d1和d2。
3.时间戳
时间戳是指格林尼治时间自1970年1月1日零时至当前时间的总秒数。使用时间戳的好处在于节省存储空间,且不受不同系统之间的日期时间格式限制;缺点在于不够直观。一般使用Python的time工具处理时间戳。
使用time.time函数获取当前时间戳:
使用time.mktime函数将datetime类型数据转换成时间戳,或者从字符串格式转换。
使用datetime.fromtimestamp函数将时间戳转换成datetime类型。
使用time.strftime函数将时间戳按指定格式转换成字符串。
4.时间类型转换
字符串类型转换成时间类型可使用datetime自带的strptime函数,在使用时需要指定时间格式:
在事先不确定时间日期格式的情况下,可以使用dateutil.parser中的parse方法自动识别字符串的时间类型,这样使用更为方便,但相对消耗资源较大。
将日期转换成时间相对比较简单,如果对字符串的语法没有特殊要求,就用str方法直接转换即可。
如果想指定格式,就使用datetime的strftime方法。
3.4.2 Pandas日期时间处理
Pandas支持时间点Timestamp、时间间隔Timedelta和时间段Period三种时间类型,它们常被用于时间索引,有时也用于描述时间类型数据。
1.时间点
Pandas最基本的数据类型为时间点,它继承自datetime,可使用to_datetime方法从字符串格式或者datetime格式转换。
2.时间间隔
Pandas中的时间间隔类似于datetime工具中的时间段,可以通过两个时间点相减获得。它的属性days,seconds可以查看具体的天数以及一天以内的秒数。
时间间隔可以通过pd.Timedelta函数创建,并利用它与时间点的计算构造新的时间点。
3.时间段
时间段描述的也是时间区间,但与时间间隔不同的是,它包含起始时间和终止时间,一般以年、月、日、小时等为计算单位,通过时间点来构造它所在的时间段。下例中使用当前时间所在小时构造时间段,并显示了该时间段的起止时间。
4.批量转换
使用to_datetime方法也可以进行数据的批量转换,通常用它将字符串类型转换为时间类型。
3.4.3 时间序列操作
Pandas中日期时间最重要的应用是处理时间序列。时间序列是一系列相同格式的数据按时间排列后组成的数列,常用于通过历史数据预测未来趋势,有时也用于空缺值的插补。在时间序列数据的清洗和准备时,会用到大量的与Pandas日期相关的操作,如按时间筛选、切分、统计、聚合、采样、去重、偏移等。
1.时间日期类型索引
时间点、时间段和时间间隔都可以作为数据表的索引,用以构造时间序列,其中最常用的是时间点索引,其类型为DatetimeIndex。
另外,也常用date_range方法指定时间范围来构造一组时间数据。下例中创建了从2017-12-30到2019-01-05,每日一条数据,共372条数据。用set_index方法将date列设置为索引列,然后将该日是星期几设置为其val字段的值,本小节的后续例程中也用到在此创建的数据。
date_range方法不仅支持设置起止时间start/end,还支持用起始时间和段数periods构造数据start/periods,其间隔freq支持年'y'、季度'q'、月'm'、小时'h'、分钟't'、秒's'等,并支持设置较为复杂的时间,如freq='1h30min'为间隔一个半小时。具体格式支持请参见Pandas源码中的pandas/tseries/frequencies.py。
2.时间段类型索引
时间段也是一种重要的索引,常用它存储整个时间段的统计数据,如全年总销量,其类型为PeriodIndex。与时间日期类型索引不同的是,它保存了起始和终止两个时间点。使用to_period方法和to_timestamp方法可以在时间段类型索引和时间点索引之间相互转换。
下例中,将时间日期类型索引改为其所在月的时间段类型索引。
从返回结果可以看到,变换后索引值的类型变为PeriodIndex,记录条数和具体内容不变,用以下方法可查看其具体起止时间:
由于是在同一个月,因此第一条记录和第二条记录的起止时间相同,用is_unique查看其索引值有重复。使用to_timestamp方法可将Period类型转换回Timestamp类型。
经过以上程序中的两次转换,日期都被转换成当月的第一天了。
3.筛选和切分
使用时间日期类型索引的一个重要原因是Pandas支持使用简单的写法按时间范围筛选数据,如用月份或年份筛选当月或当年的所有数据。
用切片方法获取某段时间的数据。
4.重采样
重采样是指对时序数据按不同的频率重新采样,把高频数据降为低频数据是降采样downsampling,反之则为升采样upsampling。
用降采样聚合一段时间的数据,先看一个常用的例程,将以日为单位的记录降采样成以周为单位,并累加其值。除了累加,常用的操作还有取其第一个值、最大值等。从结果可以看到,日期置为该周的最后一天,字段取值为该周所有数值之和。
另一种常用的降采样是ohlc方法,它常用于金融领域中计算统计区域内开盘open、最高high、最低low和收盘close的值。从返回结果可以看到,它返回了双层列索引。
降采样中的to_period方法和resample方法常常配合使用,同时实现时间和取值的聚合,下例中聚合了月度数据。
升采样常用于时序数据的插补,如在做时序预测时,缺少某一日期对应的记录,这时就需要升采样补全所有日期范围内的数据。下例中只含有三月份的三条数据,需要补全当月所有日期的数据,按日'D'升采样并使用插值方法插补数据。除了插值方法,常用的插补方法还有使用后面数据插补ffill、使用前面数据插补bfill、使用空值插补fillna等。
resample方法返回的值为DatetimeIndexResampler类型,需要进一步处理才能转换成DataFrame。它提供的功能较多,可使用该对象的aggregate,apply,transfrom等方法聚合数据。如果只需要简单填充,也可以使用asfreq方法,其直接返回DataFrame并将新记录填充为空值。
5.偏移
偏移操作适用于各种类型的数据,但在时序问题中最为常用。数据偏移使用shift方法实现,DataFrame,Series及Index都支持该方法,其语法如下:
其中,Period默认为1,即取其前项的数据。如果Period设置为负数,则为取其后项的数据。freq可设置频率,用法和Period中的freq相同。
在时序预测时,常将前一天的数据作为当日预测的一个特征,本例仍使用本小节第一部分中创建的df数据表来创建字段prev,其值为前一天的val。
6.计算滑动窗口
当时序数据波动较大时,往往会用一段时间的均值来取代该值。在趋势预测中常常需要计算前N天的均值,如股票预测中常用的N日均线,即前N日收盘价之和除以N。在这个区间内N就是窗口,窗口随着时间的流逝向前滑动,即滑动窗口。DataFrame的rolling方法可以通过对窗口中的数值计算其统计值构造新的字段,其语法如下:
其中,参数window指定窗口大小;min_peroids指定窗口中最小观测值的数据量,如果达不到该观测值,则将统计值置为空;center为当前记录是否居中,默认为居右。
下例中设置窗口长度为3,min_periods为None,center使用默认值False。如果观察到有空值,则将其计算结果置为空。从程序返回结果可以看到,第三条的记录对应的窗口范围是前三条的记录,其均值为3.666667。
emw方法比rolling方法的功能更强,实现了指数加权滑动窗口,赋予近距离的记录更大的权重。下例用作图的方法比较ewm设置参数span为3,7以及rolling为7的部分计算结果,如图3.1所示。
图3.1 滑动窗口对比图
其中,val为实际值,emw_3距离实际值最近,emw_7更多地参考了过去的数据,而rolling取7天均值,画出了一条直线。
7.时区转换
从其他数据源读出的数据有时带有时区信息,如“2019-03-31 11:21:49.915103+08:00”,有时虽然没有显性地带有时区信息,但从其内容能推断出是格林尼治时间,即比北京时间少8个小时。
当能确定数据为格林尼治时间时,计算对应的北京时间用shift方法偏移8个小时即可。而更多的时候,不能确定两个时区间的具体差异,此时建议使用Python或Pandas提供的时区转换功能。首先来看Python提供的基本时区功能,其中pytz模块用于时区转换,通过common_timezones属性查看其可支持的时区列表。本例中列出了其中的前三个时区:
接下来,用datetime的now方法取当前时间。
其返回结果为凌晨三点,比当前的实际时间早8个小时,由此可以确定其返回的是格林尼治时间。在此使用pytz提供的工具,pytz.utc.localize为其加入了时区信息,时间被设置为格林尼治时间的三点。
接下来使用timezone创建北京时间所在的时区'Asia/Shanghai',并将格林尼治时间改为北京时间,转换后时间显示正常。
使用Pandas自带的时区设置和置换功能更为简单,只需要使用tz_localize和tz_convert两个函数即可。首先,建立时间索引的DataFrame来显示其索引信息,可以看到其不带时区信息。
使用tz_localize函数指定时区为格林尼治时间:
再将格林尼治时间转换为北京时间:
对比其返回结果,时间索引值被修改,而其具体值df.index.value始终未改变,这说明修改时区修改的是显示形式,而内部时间数据未被修改。
3.4.4 数据重排
1.数据表转置
数据表转置即行列互换,与矩阵转置类似,使用DataFrame自带的方法T即可实现。首先创建数据表(此处数据表在后续例程中也会用到),然后调用转置方法T。
2.行转列和列转行
使用DataFrame的stack方法可将原数据表中的列转换为新数据表中的行索引,原行索引不变,数据沿用上例中创建的df。从返回结果可见,数据表变为双重行索引,其数据内容为原表中的数据,而结构被修改。
与stack方法的功能相反,unstack方法是将行索引转换成列索引,原列索引不变。从返回结果可看到:左侧输出使用默认参数,将内层的行索引转换成了列索引,转换之后数据与原数据一致;右侧输出使用参数level=0,将外层行索引转换成列索引,转换后与原数据的转置结果一致。stack方法和unstack方法常用于处理多重索引向单层索引的转换,stack方法也支持level参数。
3.透视转换
pivot函数和pivot_table函数提供数据的透视转换功能,它们能将数据的行列按一定规则重组,pivot_table函数是pivot函数的扩展,下面介绍pivot函数的基本功能。本例中的基本数据是将期中和期末的各科成绩以多条记录的形式放在一个表中(输出结果中的左侧表),目标是将其中一部分列索引转换成行索引,以便缩减表的长度。pivot函数需要指定三个参数:新的行索引index,列索引columns及表中内容values。
本小节学习的数据重排、转换表中的行列,以及转换表中的索引与具体的值,都是主要针对分类特征的。数值型特征展开后维度太大,一般只作为表中存储的数据。