大熊猫在一组中不包括重叠的时间间隔求和

Chi*_*ony 4 python python-3.x pandas pandas-groupby

我有一个数据框,其中每一行都有一个开始时间和一个结束时间。每行也有一个他们所属的组。我想要一个新列,以秒为单位给出该组中所有时间的总和。

例如,如果我们有一个如下所示的组:

         id1:    |----|
         id2:       |-----|
         id3:                      |--------|
                 .  .  .  .  .  .  .  .  .  .  .
time ->        12:00    12:04    12:07    12:10
Run Code Online (Sandbox Code Playgroud)

然后对于属于该组的所有行将获得 4+3 分钟 = 420 秒的总和时间

如果它们完全重叠,那么我们会得到这样的场景:

         id1:    |--------|
         id2:    |--------|
                 .  .  .  .  .  .  .  .  .  .  .
time ->        12:00    12:04    12:07    12:10
Run Code Online (Sandbox Code Playgroud)

这会给我们结果 4 分钟 = 240 秒。

下面是一些虚拟数据:


import pandas as pd

ids = [x for x in range(10)]
group = [0, 1, 1, 2, 2, 3, 4, 4, 4, 4]

start = pd.to_datetime(["2019-10-21-16:20:00", "2019-10-21-16:22:00", "2019-10-21-16:22:00", "2019-10-21-16:15:00",
         "2019-10-21-16:22:00", "2019-10-21-16:58:00", "2019-10-21-17:02:00", "2019-10-21-17:03:00",
         "2019-10-21-17:04:00", "2019-10-21-17:20:00"])

end = pd.to_datetime(["2019-10-21-16:25:00", "2019-10-21-16:24:00", "2019-10-21-16:24:00", "2019-10-21-16:18:00",
       "2019-10-21-16:26:00", "2019-10-21-17:02:00", "2019-10-21-17:06:00", "2019-10-21-17:07:00",
       "2019-10-21-17:08:00", "2019-10-21-17:22:00"])

cols = ["id", "group", "start", "end"]


df = pd.DataFrame(dict(zip(cols, [ids, group, start, end])))
Run Code Online (Sandbox Code Playgroud)

到目前为止,我尝试过的方法显然是不正确的。我试过分组,然后找到该组的每个开始和结束的最小值和最大值,然后将该间隔设置为总和。这种方法是不正确的,因为它还会包括间隔中的间隙。

gr = df.groupby("group").apply(lambda x : x.end.max() - x.start.min())
df['total_time'] = df.group.map(gr)
Run Code Online (Sandbox Code Playgroud)

Joh*_*nck 5

首先,添加一列跟踪迄今为止看到的最新结束时间(但仅考虑同一组):

df['notbefore'] = df.groupby('group').end.shift().cummax()
Run Code Online (Sandbox Code Playgroud)

它移动了 1 以反映在前一行中看到的最晚结束时间,不包括同一行。拥有shift()before很重要cummax(),否则转移会在组之间“泄漏”值。

然后添加一个包含“有效”开始时间的列:

df['effstart'] = df[['start', 'notbefore']].max(1)
Run Code Online (Sandbox Code Playgroud)

这是修改后的开始时间,使其不在任何先前的结束时间之前(以避免重叠)。

然后计算涵盖的总秒数:

df['effsec'] = (df.end - df.effstart).clip(np.timedelta64(0))
Run Code Online (Sandbox Code Playgroud)

df 就是现在:

   id  group               start                 end           notbefore            effstart   effsec
0   0      0 2019-10-21 16:20:00 2019-10-21 16:25:00                 NaT 2019-10-21 16:20:00 00:05:00
1   1      1 2019-10-21 16:22:00 2019-10-21 16:24:00                 NaT 2019-10-21 16:22:00 00:02:00
2   2      1 2019-10-21 16:22:00 2019-10-21 16:24:00 2019-10-21 16:24:00 2019-10-21 16:24:00 00:00:00
3   3      2 2019-10-21 16:15:00 2019-10-21 16:18:00                 NaT 2019-10-21 16:15:00 00:03:00
4   4      2 2019-10-21 16:22:00 2019-10-21 16:26:00 2019-10-21 16:24:00 2019-10-21 16:24:00 00:02:00
5   5      3 2019-10-21 16:58:00 2019-10-21 17:02:00                 NaT 2019-10-21 16:58:00 00:04:00
6   6      4 2019-10-21 17:02:00 2019-10-21 17:06:00                 NaT 2019-10-21 17:02:00 00:04:00
7   7      4 2019-10-21 17:03:00 2019-10-21 17:07:00 2019-10-21 17:06:00 2019-10-21 17:06:00 00:01:00
8   8      4 2019-10-21 17:04:00 2019-10-21 17:08:00 2019-10-21 17:07:00 2019-10-21 17:07:00 00:01:00
9   9      4 2019-10-21 17:20:00 2019-10-21 17:22:00 2019-10-21 17:08:00 2019-10-21 17:20:00 00:02:00
Run Code Online (Sandbox Code Playgroud)

要获得最终结果:

df.groupby('group').effsec.sum()
Run Code Online (Sandbox Code Playgroud)

这给了你:

group
0   00:05:00
1   00:02:00
2   00:05:00
3   00:04:00
4   00:08:00
Run Code Online (Sandbox Code Playgroud)