Python:在时间序列内对数据进行分组

3

假设有以下数据库:

import pandas as pd
from time import strptime

data = {'num': ['B', 'A', 'B', 'A', 'B', 'A', 'B', 'C', 'B', 'A', 'C', 'B'],
        'dir': ['IN', 'IN', 'PASS', 'PASS', 'PASS', 'PASS', 'OUT', 'IN', 'IN', 'OUT', 'OUT', 'OUT'],
        'empty': [True, False, True, False, True, False, True, False, False, False, False, False],
        'station': [100, 1, 20, 20, 14, 14, 5, 65, 89, 8, 66, 11],
        'time': [strptime('20180115 00:25', '%Y%m%d %H:%M'),
                 strptime('20180115 00:30', '%Y%m%d %H:%M'),
                 strptime('20180115 01:10', '%Y%m%d %H:%M'),
                 strptime('20180115 01:10', '%Y%m%d %H:%M'),
                 strptime('20180115 02:10', '%Y%m%d %H:%M'),
                 strptime('20180115 02:10', '%Y%m%d %H:%M'),
                 strptime('20180115 03:33', '%Y%m%d %H:%M'),
                 strptime('20180115 03:33', '%Y%m%d %H:%M'),
                 strptime('20180115 03:34', '%Y%m%d %H:%M'),
                 strptime('20180115 04:10', '%Y%m%d %H:%M'),
                 strptime('20180115 04:11', '%Y%m%d %H:%M'),
                 strptime('20180115 10:10', '%Y%m%d %H:%M')]}

df = pd.DataFrame.from_dict(data)

我需要生成以下预期结果的列表:

   num  empty  station_from  station_to       time_from         time_to
0    B   True           100           5  20180115 00:25  20180115 03:33
1    A  False             1           8  20180115 00:30  20180115 04:10
2    C  False            65          66  20180115 03:33  20180115 04:11
3    B  False            89          11  20180115 03:34  20180115 10:10

解释: 这些是铁路车辆,每个车辆都有一个编号(例如A、B、C)。 它们可以是已经充电或未充电的状态(空列为未充电)。 dir列指示我们是否在该特定车辆中加入或卸下。还可以存在许多其他状态(如此示例中的PASS),但我们只对IN和OUT状态感兴趣。 station列是事件发生的位置, time列是事件发生的时间。
我需要将每个IN和OUT配对之间的数据分组(或显示每个IN和OUT的轨迹),并显示活动发生的位置和时间。难点在于这不是“常规”的分组方式,而是一种基于层次化的时间序列方法(在同一天内,一辆车可以被多次装载和卸载)。
我不知道pandas是否是实现这种操作的最佳结构,但在DB中有数亿条记录,因此性能至关重要。(我们根据特定时间段预选择数据)
如何实现所需结果?

根据数据的大小和计算资源的可用性,您可能需要考虑使用并行线扫描算法。 - Travis
1
此外,这是批处理还是流式环境?因为将其视为流式问题可能会使概念更容易理解。 - Travis
好的,我们可以将其视为批处理过程,但如果响应时间可接受(对于此和其他几个处理,都在一分钟以下,但该处理包含大部分数据),那么整个过程将在线上进行(这是首选),用户点击“生成”后,就会得到所需的列表。 - Gabor
如果您每个月只“按需”生成报告一次,那么这可能是对于pandas来说太大的数据集。(请记住,pandas的计算模型完全在内存中)但是除非您拥有比此处声明的数据更多的数据,否则类似dask dask.pydata.org/en/latest/的东西肯定能够处理它。(dask 强烈 模拟pandas接口,因此学习曲线应该非常小) - Travis
我无法预先计算,因为这是一个月度成本分析报告,我必须处理其他相关的成本和收入数据,需要将它们合并在一起。计算规则非常复杂,有大量的“取决于”类型的规则。 - Gabor
显示剩余5条评论
1个回答

1

1) 筛选所有包含'IN'的行

2) 查找每个匹配 num empty 的'IN'后第一个 'OUT'

result = df[df.dir=='IN'][['num','empty','station','time']].rename({'station':'station_from','time':'time_from'},axis='columns')
result[['station_to','time_to']] = result.apply(lambda r: df[(df.index>r.name) & (df.num==r.num) & (df.empty==r.empty) & (df.dir=='OUT')][['station','time']].iloc[0,:], axis=1)

print(result)

  num  empty  station_from                           time_from  station_to  \
0   B   True           100  (2018, 1, 15, 0, 25, 0, 0, 15, -1)           5   
1   A  False             1  (2018, 1, 15, 0, 30, 0, 0, 15, -1)           8   
7   C  False            65  (2018, 1, 15, 3, 33, 0, 0, 15, -1)          66   
8   B  False            89  (2018, 1, 15, 3, 34, 0, 0, 15, -1)          11   

                               time_to  
0   (2018, 1, 15, 3, 33, 0, 0, 15, -1)  
1   (2018, 1, 15, 4, 10, 0, 0, 15, -1)  
7   (2018, 1, 15, 4, 11, 0, 0, 15, -1)  
8  (2018, 1, 15, 10, 10, 0, 0, 15, -1)

哇!看起来不错!我有两个问题:1:r.name是什么?为什么要将其与索引进行比较?2:如果几个IN/OUT轨道都是空的或已充电,那么这个选项(df.empty==r.empty)不会生成错误的结果吗?您的解决方案似乎将它们分组在一起。我是对还是漏了什么?实际上,我们应该在IN之后立即采取第一个OUT事件,而不考虑其是否为空。 - Gabor
r.name 用于在对行进行 apply 操作时访问行号。 我不确定 empy 是否应该匹配,因此添加了条件。您可以删除 (df.emply==r.empty) 条件,但为了清晰起见,在结果表中将其重命名为 empty_from 可能更好。 - Sergey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接