【问题标题】:Pandas group by time windowsPandas 按时间窗口分组
【发布时间】:2014-04-01 04:34:21
【问题描述】:

编辑:Session generation from log file analysis with 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 .

找到不是静态时间范围的时间窗口的最佳方法是什么?

给定一个看起来像这样的系列 -

      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

谢谢。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    这是创建自定义分组的方法。 (需要 pandas >= 0.13)用于 timedelta 计算,否则将在其他版本中工作。

    创建你的系列

    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 组。这也可能更加随意。如果与前一个的差异 50。

    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(也可以在这里使用 apply)

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

    【讨论】:

    • 杰夫,我绝对喜欢你用这个去哪里。不过,我不确定如何将其扩大规模。如果您在 2013-01-01 10:34:55 和 10:35:12 再添加两个事件会怎样。你最终会得到索引器数据框还有两行: 2013-01-01 10:34:55 1, 2013-01-01 10:35:12 17 然后这两行都会出现在 [indexer
    • 如果您愿意,索引器可以与原始系列一样大。我将更新示例以执行我认为您想要的操作。
    • 感谢杰夫的编辑。不过,现在在您的小组中,您有三个小组,而实际上应该只有两个小组(10:34:00 - 10:34:28 和 10:34:54 - 10:35:12)。
    • 这只是一个例子,我不确定你想要什么。如果您愿意,您可以轻松地使用一个函数来执行此操作。随心所欲地创建石斑鱼。
    • 我想做的是为通过识别那些重叠事件创建的每个时间范围创建一个窗口 id。在您的示例(grouper 数据框)中,您最终得到 10:34:54 作为与 10:34:55 和 10:35:12 不同的数字。也许我通过关注熊猫来构建错误的问题,这实际上是一个“在python中创建弹性重叠事件范围的最佳方法是什么”的问题。我曾希望 pandas 已经在这里有一种内置的 TimeSeries 操作功能。
    【解决方案2】:

    您可能需要考虑使用apply

    def my_grouper(datetime_value):
        return some_group(datetime_value)
    
    df.groupby(df['date_time'].apply(my_grouper))
    

    您可以在 grouper 函数中实现任何分组逻辑。顺便说一句,合并重叠的时间范围是一种迭代任务:例如,A = (0, 10), B = (20, 30), C = (10, 20)。 C出现后,A、B、C三者应该合并。

    UPD:

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

    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
    

    【讨论】:

    • 木芝,感谢您的回复!不过,我不确定逻辑在 my_grouper 函数中是如何发挥作用的。如果您只是找到重叠的元组(因为您的 A、B 和 C 在您的帖子中),您能否向我展示 my_grouper 的外观示例?
    • 更新了代码。嗯......它看起来不是很优雅:)
    • Muzhig,我很欣赏这个例子!
    【解决方案3】:

    试试这个:

    • 创建一个列tsdiff,其中包含连续时间之间的差异(使用shift
    • df['new_group'] = df.tsdiff &gt; timedelta
    • fillnanew_group
    • groupby那个专栏

    这只是非常粗略的伪代码,但解决方案就在某处......

    【讨论】:

      猜你喜欢
      • 2021-04-11
      • 2021-10-02
      • 2021-09-05
      • 2021-03-06
      • 2018-09-04
      • 2017-01-30
      • 2021-10-02
      • 1970-01-01
      相关资源
      最近更新 更多