Pandas时间序列切片(范围选取)前先将日期按升序排序!

《DK Science year by year》

在数据分析中,时间序列数据所占的比例应该不在少数。《利用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()命令可实现更为灵活的(日期时间段)范围选取。

参考资料
  1. Python for Data Analysis, 2nd Edition
  2. python pandas dataframe slicing by date conditions
  3. Select DataFrame rows between two dates
  4. 《Pandas Cookbook》第10章 时间序列分析
  5. pandas处理时间序列(2):DatetimeIndex、索引和选择……
  6. pandas.DataFrame.truncate
  7. pandas对时间索引进行分割(truncate requires a sorted index)
  8. Pandas日期数据处理:如何按日期筛选、显示及统计数据
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352