Pandas 按时间窗口分组

7
编辑:使用pandas从日志文件分析生成会话似乎正是我所寻找的。
我有一个包含非唯一时间戳的数据框,想要按时间窗口分组。基本逻辑如下:
1)通过在时间戳之前和之后添加n分钟创建每个时间戳的时间范围。
2)按重叠的时间范围进行分组。最终效果是时间窗口可以小到单个时间戳+/-时间缓冲区,但没有时间窗口大小上限,只要多个事件之间的距离小于时间缓冲区即可。
感觉 df.groupby(pd.TimeGrouper(minutes = n)) 是正确的答案,但我不知道如何让 TimeGrouper 在看到在时间缓冲区内的事件时创建动态时间范围。
例如,如果我针对一组事件尝试 TimeGrouper('20s'):10:34:00、10:34:08、10:34:08、10:34:15、10:34:28 和 10:34:54,则 Pandas 将给出三个组(事件落在 10:34:00-10:34:20、10:34:20-10:34:40 和 10:34:40-10:35:00 之间)。我希望只返回两个组,即 10:34:00-10:34:28,因为在该时间范围内的事件之间没有超过 20 秒的间隔,并且第二个组是 10:34:54。
找到不是静态时间范围的时间窗口的最佳方法是什么?
给定类似于以下 Series -
      time
0     2013-01-01 10:34:00+00:00
1     2013-01-01 10:34:12+00:00
2     2013-01-01 10:34:28+00:00
3     2013-01-01 10:34:54+00:00
4     2013-01-01 10:34:55+00:00
5     2013-01-01 10:35:19+00:00
6     2013-01-01 10:35:30+00:00

如果我在该序列上执行df.groupby(pd.TimeGrouper('20s')),我将返回5个分组,即10:34:00-:20、:20-:40、:40-10:35:00等。 我想做的是创建一些创建弹性时间范围的函数... 只要事件在20秒内,就扩展时间范围。所以我希望得到如下结果 -

2013-01-01 10:34:00 - 2013-01-01 10:34:48 
    0 2013-01-01 10:34:00+00:00
    1 2013-01-01 10:34:12+00:00
    2 2013-01-01 10:34:28+00:00

2013-01-01 10:34:54 - 2013-01-01 10:35:15
    3 2013-01-01 10:34:54+00:00
    4 2013-01-01 10:34:55+00:00

2013-01-01 10:35:19 - 2013-01-01 10:35:50
    5 2013-01-01 10:35:19+00:00
    6 2013-01-01 10:35:30+00:00

感谢您的选择。
3个回答

9
这是如何使用创建自定义分组器的方法。(需要pandas >= 0.13进行时间差计算,但在其他版本中也适用。)
创建你的系列数据。
In [31]: s = Series(range(6),pd.to_datetime(['20130101 10:34','20130101 10:34:08', '20130101 10:34:08', '20130101 10:34:15', '20130101 10:34:28', '20130101 10:34:54','20130101 10:34:55','20130101 10:35:12']))

In [32]: s
Out[32]: 
2013-01-01 10:34:00    0
2013-01-01 10:34:08    1
2013-01-01 10:34:08    2
2013-01-01 10:34:15    3
2013-01-01 10:34:28    4
2013-01-01 10:34:54    5
2013-01-01 10:34:55    6
2013-01-01 10:35:12    7
dtype: int64

这只是计算相邻元素之间时间差的秒数,但可以用于计算任何东西。
In [33]: indexer = s.index.to_series().order().diff().fillna(0).astype('timedelta64[s]')

In [34]: indexer
Out[34]: 
2013-01-01 10:34:00     0
2013-01-01 10:34:08     8
2013-01-01 10:34:08     0
2013-01-01 10:34:15     7
2013-01-01 10:34:28    13
2013-01-01 10:34:54    26
2013-01-01 10:34:55     1
2013-01-01 10:35:12    17
dtype: float64

任意将20秒以下的事物分配到组0,否则分配到组1。这也可以更加任意。如果与前一个相比的差值小于0但是总的差值(从第一个开始)大于50,则将其放入组2中。
In [35]: grouper = indexer.copy()

In [36]: grouper[indexer<20] = 0

In [37]: grouper[indexer>20] = 1

In [95]: grouper[(indexer<20) & (indexer.cumsum()>50)] = 2

In [96]: grouper
Out[96]: 
2013-01-01 10:34:00    0
2013-01-01 10:34:08    0
2013-01-01 10:34:08    0
2013-01-01 10:34:15    0
2013-01-01 10:34:28    0
2013-01-01 10:34:54    1
2013-01-01 10:34:55    2
2013-01-01 10:35:12    2
dtype: float64

Groupem (can also use an apply here)

In [97]: s.groupby(grouper).sum()
Out[97]: 
0    10
1     5
2    13
dtype: int64

索引器如果需要的话可以和原始序列一样大。我会更新示例以实现您想要的功能。 - Jeff
感谢您的编辑,Jeff。不过现在,在您的分组中,您有三个组,而实际上您只需要两个组(10:34:00 - 10:34:28 和 10:34:54 - 10:35:12)。 - Kafonek
这只是一个例子,我不确定你想要什么。如果你想要的话,你可以很容易地创建一个函数来完成这个任务。只需按照你想要的方式创建分组器即可。 - Jeff
仍然不确定您在问什么,也许可以提出一个非常明确的问题。或者更好的方法是展示您期望的结果。 - Jeff
是的,对不起 Jeff 造成了混淆。我已经编辑了我的原始帖子,可能会进行一些更改。这有点麻烦,因为我在这台电脑上没有解释器可以复制/粘贴代码。 - Kafonek
显示剩余2条评论

1

您可能希望考虑使用 apply

def my_grouper(datetime_value):
    return some_group(datetime_value)

df.groupby(df['date_time'].apply(my_grouper))

你需要在群组函数中实现任何分组逻辑。另外,合并重叠时间范围有点迭代的任务:例如,A =(0,10),B =(20,30),C =(10,20)。出现C后,应合并所有三个范围,即A,B和C。

更新:

这是我丑陋版本的合并算法:

groups = {}

def in_range(val, begin, end):
    return begin <= val <= end

global max_group_id
max_group_id = 1

def find_merged_group(begin, end):
    global max_group_id
    found_common_group = None
    full_wraps = []

    for (group_start, group_end), group in groups.iteritems():
        begin_inclusion = in_range(begin, group_start, group_end)
        end_inclusion = in_range(end, group_start, group_end)
        full_inclusion = begin_inclusion and end_inclusion
        full_wrap = not begin_inclusion and not end_inclusion and in_range(group_start, begin, end) and in_range(group_end, begin, end)
        if full_inclusion:
            groups[(begin, end)] = group
            return group
        if full_wrap:
            full_wraps.append(group)
        elif begin_inclusion or end_inclusion:
            if not found_common_group:
                 found_common_group = group
            else:  # merge
                for range, g in groups.iteritems():
                    if g == group:
                        groups[range] = found_common_group

    if not found_common_group:
        found_common_group = max_group_id
        max_group_id += 1
    groups[(begin, end)] = found_common_group
    return found_common_group

def my_grouper(date_time):
    return find_merged_group(date_time - 1, date_time + 1)

df['datetime'].apply(my_grouper) # first run to fill groups dict
grouped = df.groupby(df['datetime'].apply(my_grouper))  # this run is using already merged groups

Muzhig,感谢您的回复!但我不确定my_grouper函数中的逻辑是如何运作的。如果您只是查找重叠元组(就像您在帖子中的A、B和C一样),您能否向我展示一下my_grouper会是什么样子? - Kafonek
更新了代码。嗯...看起来不太优雅 :) - Arseniy
木之格,感谢您的示例! - Kafonek

0
尝试一下这个:
  • 创建一个列tsdiff,该列具有连续时间之间的差异(使用shift
  • df['new_group'] = df.tsdiff > timedelta
  • new_group上使用fillna
  • 按该列进行groupby

这只是粗略的伪代码,但解决方案就在其中...


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