在数据分析中,时间序列数据所占的比例应该不在少数。《利用Python进行数据分析》一书中就专辟一章对数据序列的切片、重采样、绘图、移动窗口等逐一加以介绍。
《利用Python进行数据分析》
我手头的是该书的第一版,其中《第10章 时间序列》的“索引、选取、子集构造”一节中,专门提及了下面这一句(见原书P309):
通过日期进行切片的方式只对规则Series有效
一开始不知何意,“规则Series”也不知其具体所指,就稀里糊涂的翻过去了。
直到某次按照书中的方法对时间序列数据传入日期字符串进行切片(范围选取)发现不奏效,在网上搜索答案才发现,上面这句的确是时间序列切片的关键步骤所在。而且强烈怀疑这句是译者为避免读者走弯路自己加上去的(因为后来有机会看到该书的英文版本,也没有找到对应这一句的出处。当然这个也仅仅是怀疑没有实锤)。
日期索引数据的切片关键在于先排序
这里所谓的切片实际上就是按照起止日期等对数据集进行筛选。因此,能够实现的方式多种多样,turncate、query等均能实现pandas范围选取(切片)。只不过我个人看来,直接用日期作为表格的索引,传入日期字符串的方式最为便捷。
来看具体的实例:
trainDF = pd.read_csv('train.csv', parse_dates = ['Dates'])
trainDF = trainDF.drop(['Descript','Resolution'], axis =1)
trainDF_datesIndex = trainDF.set_index('Dates') #日期索引、未排序
trainDF_Ascending = trainDF.set_index('Dates').sort_index(ascending=True) #日期升序
trainDF_Descending = trainDF.set_index('Dates').sort_index(ascending=False) #日期降序
载入'2003-01-06 00:01:00'至'2015-05-13 23:53:00'期间San Fransisco市的犯罪记录报告数据。筛选'2014-1-10'至'2014-1-30'之间的数据。
原数据集中的日期字段 query 与 loc、turncatre命令均可实现范围选取
索引未排序时无法用'起始日期:结束日期'的简便方式切片
从执行结果可见,索引未排序前,用query这些都可以筛选出正确的子数据集,只不过需要键入的代码会稍多些;若直接用代表日期的字符串选取具体的某一个具体的日期('Feb-2013'、'20130104'、'2005Q3'……)都可以被pandas识别;但要指定起止日期,比如pd['01/10/2014': '01/30/2014']或是['2004Q1': '2004Q3']这种时间段的方式,就不一定能成功。用turncate筛选索引时也是一样的效果:在对索引排序之前,用truncate进行单侧截取是可以的,但要用起止时间来掐头去尾就不行了。
换言之,写成:
trainDF_datesIndex.truncate(before='1/30/2004 00:00').truncate(after='1/10/2004 00:00')
是可以得到结果的,但写成:
trainDF_datesIndex.truncate('1/10/2004', '1/30/2004')
就不行了。参考资料7中也明确提到了这一点,truncate requires a sorted index。但既然对日期索引排序后可以直接用更少的代码实现按时间段切片,其实也就用不着turncate命令了。少打几个字母何乐不为呢。
#排序后下面两条指令是等效的
trainDF_Ascending['1/10/2004': '1/30/2004']
trainDF_Ascending .truncate('1/10/2004', '1/30/2004')
truncate同样需要先对索引排序
值得一提的是,pandas可以识别多种格式的表示日期的字符串,但也需注意比如'DD/MM/YYYY'与'MM/DD/YYYY',日期在前还是月份在前等对于日期的不同表示法。
看起来不少人也和我一样遇到过同样的问题,看到在stackoverflow回复pandas时间序列切片失效问题的也有不少。
stackoverflow上对此问题的回答
部分教程上的示例是自己构建的一个DatetimeIndex序列,也就不容易碰到类似的问题。但处理实际中的数据的过程中还是很容易忽视这一点的。
日期升序或降序排列执行结果会有不同
有意思的是,按照日期的升序排序或降序排序,pandas的结果会稍有不同。
将原数据按不同的顺序排序
首先需要指出的是,按照升序或者降序排序后,切片时起止日期的写法必须与排序的升降序保持一致!
- trainDF_Ascending['20040110':'20040130'] #升序 √
- trainDF_Ascending['20040130':'20040110'] #升序 ×
- trainDF_Descending['20040130':'20040110'] #降序 √
- trainDF_Descending['20040110':'20040130'] #降序 ×
一个不同之处在于,降序排序时,只用年来切片,[YYYY: YYYY]或[YYYYMM: YYYYMM]得不到想要的结果;升序排序时无此问题。
降序排序[yyyy:yyyy]筛选不到数据
降序排序时YYYYMM会报错
第二个不同在于,升序或降序排列不同切片时起止日期范围是否封闭也是不一样的。原数据集中只有'2014-1-10'至'2014-1-30'这20天内周数为偶数的数据(下图1),故将日期范围进一步精确到'2014-1-19'至'2014-1-26'这一周(下图2)。
'2014-1-10'至'2014-1-30'的数据筛选 '2014-1-19'至'2014-1-26'的数据筛选由上图运行结果可知,索引降序排列的数据表在'2014-1-19'至'2014-1-26'的数据筛选,没有包含'2014/01/26'这一天,而升序排序索引的数据表进行筛选时是包含了的。亦即升序排序切片左右均封闭,降序排序左开放右封闭(不含日期排在最后的数据)。
升序降序日期切片区别之二:左右是否封闭
日期索引排序的切片也可以传入变量来筛选数据。与前述提到的结论一致,降序排序筛选切片时需注意区间左右是否封闭等细节问题。值得一提的是,如果切片传入的时datetime变量就不存在区间封闭性左右不同的问题,但这样一来,又得多敲键盘打字了。
降序排列传入字符串变量切片与传入datetime变量切片结果不同
从这一点上说,个人认为升序排列时的处理方式更符合常规,好在pandas.sort_index()默认升序,要实在不放心就老老实实加上一个'ascending=True'来得保险。
at_time()与between_time()
另外还有两个与日期范围筛选有关的命令时at_time()和between_time()命令。对于筛选所有日期的某一个具体时刻用at_time(),两个时刻之间的范围筛选用between_time()。注意不论是升序还是降序,between_time()都写成早的时间在前、晚的时间在后!写反了结果可能出错。
这两个命令比较简单,有兴趣的自己找个表格自己一试便知,就不多啰嗦了。
trainDF_datesIndex['2015-05-13'].at_time('20:30')
trainDF_datesIndex['2015-04-13'].between_time('20:25','20:45')
# 不论是升序还是降序,between_time()都写成早的时间在前、晚的时间在后!
# 写反了结果可能出错
trainDF_Ascending.between_time('20:25','20:45')
trainDF_Descending.between_time('20:25','20:45')
本文只对时间序列表格的范围选取加以讨论。除此之外,重采样resample、移动窗口rolling等命令都是pandas中非常高效实用的处理时间序列的命令。在《Python for Data Analysis, 2nd Edition》 Chapter 11: Time Series一章提供了pandas处理时间序列相关指令的.ipynb文件(第一版中文版中时间序列是第10章),其实也可以去将上述提到的问题自己试一试。毕竟“纸上得来终觉浅”,虽然本文讨论的都是些细枝末节的问题。
结论
-
通过日期进行切片的方式只对规则Series有效!所谓的“规则Series”个人的理解就是排过序的Series,姑妄言之。
-
将日期改为DatetimeIndex方便切片、筛选、resample分组。但务必将日期按升序排列(ascending=True),否则有可能筛选不到数据、或是报错。
-
降序排序不推荐!降序排列的表格需按照日期从大到小的方式排列才能可能的切片,且左开右闭的截取方式也可能造成不必要的误解。
-
结合at_time()与between_time()命令可实现更为灵活的(日期时间段)范围选取。
参考资料
- Python for Data Analysis, 2nd Edition
- python pandas dataframe slicing by date conditions
- Select DataFrame rows between two dates
- 《Pandas Cookbook》第10章 时间序列分析
- pandas处理时间序列(2):DatetimeIndex、索引和选择……
- pandas.DataFrame.truncate
- pandas对时间索引进行分割(truncate requires a sorted index)
- Pandas日期数据处理:如何按日期筛选、显示及统计数据
网友评论