python:有条件地删除每个组的第一行

python:有条件地删除每个组的第一行,python,pandas,Python,Pandas,使用Python 3.6和0.19.2 我有这样一个数据帧: tid datetime event data 0 0 2017-03-22 10:59:59.864 START NaN 1 0 2017-03-22 10:59:59.931 END NaN 2 0 2017-03-22 10:59:59.935 START NaN 3 1 2017-03-22 10:59:59.939 END NaN

使用Python 3.6和0.19.2

我有这样一个数据帧:

   tid                datetime  event  data
0    0 2017-03-22 10:59:59.864  START   NaN
1    0 2017-03-22 10:59:59.931    END   NaN
2    0 2017-03-22 10:59:59.935  START   NaN
3    1 2017-03-22 10:59:59.939    END   NaN
4    0 2017-03-22 10:59:59.940    END   NaN
5    1 2017-03-22 10:59:59.941  START   NaN
6    1 2017-03-22 10:59:59.945    END   NaN
7    0 2017-03-22 10:59:59.947  START   NaN
8    1 2017-03-22 10:59:59.955  START   NaN
它包含线程内发生的事务的开始日期和结束日期(tid是线程id)。遗憾的是,事务本身没有唯一的ID。因此,我需要按tid对这些行进行分组,按日期排序,然后按2乘2的顺序对行进行排序,以便每个事务有1个开始和1个结束

我当前的问题是,我的初始数据帧可能会错过每个线程的第一个开始事件(在我上面的示例中,索引为3的行是一个没有先前开始的结束事件)。我需要删除这些结束线,但我不知道怎么做。 对于没有匹配终点线的最后一个起点线,也存在相同的问题

样本输入

import pandas as pd
import io
df = pd.read_csv(io.StringIO('''tid;datetime;event
0;2017-03-22 10:59:59.864;START
0;2017-03-22 10:59:59.931;END
0;2017-03-22 10:59:59.935;START
1;2017-03-22 10:59:59.939;END
0;2017-03-22 10:59:59.940;END
1;2017-03-22 10:59:59.941;START
1;2017-03-22 10:59:59.945;END
0;2017-03-22 10:59:59.947;START
1;2017-03-22 10:59:59.955;START'''), sep=';', parse_dates=['datetime'])
预期产出

import pandas as pd
import io
df = pd.read_csv(io.StringIO('''tid;datetime;event
0;2017-03-22 10:59:59.864;START
0;2017-03-22 10:59:59.931;END
0;2017-03-22 10:59:59.935;START
1;2017-03-22 10:59:59.939;END
0;2017-03-22 10:59:59.940;END
1;2017-03-22 10:59:59.941;START
1;2017-03-22 10:59:59.945;END
0;2017-03-22 10:59:59.947;START
1;2017-03-22 10:59:59.955;START'''), sep=';', parse_dates=['datetime'])
相同的数据帧,但删除了第#2行,因为它是Tid 1的第一行,不是开始事件:

   tid                datetime  event
0    0 2017-03-22 10:59:59.864  START
1    0 2017-03-22 10:59:59.931    END
3    1 2017-03-22 10:59:59.933  START
4    1 2017-03-22 10:59:59.945    END
5    0 2017-03-22 10:59:59.947  START
6    0 2017-03-22 10:59:59.955    END
顺便说一句,如果您最终得到以下奖励,您将获得额外积分:

   tid          start_datetime           stop_datetime
0    0 2017-03-22 10:59:59.864 2017-03-22 10:59:59.931
1    1 2017-03-22 10:59:59.933 2017-03-22 10:59:59.945
2    0 2017-03-22 10:59:59.947 2017-03-22 10:59:59.955
我尝试过的

df.sort(['tid','datetime']).groupby('tid').first().event='END'
不包含数据帧中的初始索引,因此我无法使用它删除行。(或者,如果我可以的话,如何做到这一点并不明显)

一种方法是(我们可以整理自定义函数以处理更多不同的输入,但这对示例输入有效):

输出:

       tid              start_datetime                end_datetime
group                                                             
0        0  2017-03-22 10:59:59.864000  2017-03-22 10:59:59.931000
1        1  2017-03-22 10:59:59.933000  2017-03-22 10:59:59.945000
2        0  2017-03-22 10:59:59.947000  2017-03-22 10:59:59.955000
一种方法是(我们可以整理自定义函数以处理更多不同的输入,但这适用于示例输入。):

输出:

       tid              start_datetime                end_datetime
group                                                             
0        0  2017-03-22 10:59:59.864000  2017-03-22 10:59:59.931000
1        1  2017-03-22 10:59:59.933000  2017-03-22 10:59:59.945000
2        0  2017-03-22 10:59:59.947000  2017-03-22 10:59:59.955000
您可以使用+创建唯一的
系列
进行分组,然后使用自定义功能,其中“选择依据”和“上一次对列重新排序依据”:

另一种解决方案是使用
布尔索引
代替
查询
(可能更快,
查询
在更大的
df中更好):

您可以使用+创建唯一的
系列
进行分组,然后使用自定义功能,其中“选择依据”和“上一次对列重新排序依据”:

另一种解决方案是使用
布尔索引
代替
查询
(可能更快,
查询
在更大的
df中更好):


下面是另一种方法,其
groupby()
策略基于:


下面是另一种方法,其
groupby()
策略基于:


我设法通过以下方式部分解决了我的问题:

# order events by thread id and datetime
df = df.sort_values(['tid', 'datetime']).reset_index(drop=True)
# then group by tid
for tid, group in df.groupby('tid'):
     # for each group, drop the first line if it is a END event
     head = group.head(1).iloc[0]
     if head.status == 'END':
         df.drop(head.name, inplace=True)
     # and drop the last line if it is a START event
     tail = group.tail(1).iloc[0]
     if tail.status == 'START':
         df.drop(tail.name, inplace=True)

# take lines 2 by 2, that will be a START and an END event, that can be aggregated
df.groupby(np.arange(len(df)) // 2).agg({'Tid': 'first', 'DateTime': {'start': 'min', 'stop': 'max'}})

我设法通过以下方式部分解决了我的问题:

# order events by thread id and datetime
df = df.sort_values(['tid', 'datetime']).reset_index(drop=True)
# then group by tid
for tid, group in df.groupby('tid'):
     # for each group, drop the first line if it is a END event
     head = group.head(1).iloc[0]
     if head.status == 'END':
         df.drop(head.name, inplace=True)
     # and drop the last line if it is a START event
     tail = group.tail(1).iloc[0]
     if tail.status == 'START':
         df.drop(tail.name, inplace=True)

# take lines 2 by 2, that will be a START and an END event, that can be aggregated
df.groupby(np.arange(len(df)) // 2).agg({'Tid': 'first', 'DateTime': {'start': 'min', 'stop': 'max'}})

:)我更喜欢你的回答。”转换“cumsum”。@ScottBoston-谢谢。谢谢!我将尽快调整并测试我的真实数据帧,它比我在这里发布的输入要复杂一些。我不确定
(df.tid!=df.tid.shift()).cumsum()
应该做什么?(现在查看文档)我已经测试过了,但我不确定它是否能按需要工作。当我的初始数据帧包含相同tid的4条或更多连续行时,会发生什么情况?(当连续2个事件发生在同一个线程上时)如果我理解得很好,只有第一行和最后一行被考虑为StaskDead和EndoDeDy,所以中间的行将被忽略。@纪尧姆-你认为样本中的<代码> TID<代码>的所有值都是<代码> 0 < /代码>吗?或者别的什么?:)我更喜欢你的答案转换“cumsum”。@ScottBoston-谢谢。谢谢!我将尽快调整并测试我的真实数据帧,它比我在这里发布的输入要复杂一些。我不确定
(df.tid!=df.tid.shift()).cumsum()
应该做什么?(现在查看文档)我已经测试过了,但我不确定它是否能按需要工作。当我的初始数据帧包含相同tid的4条或更多连续行时,会发生什么情况?(当连续2个事件发生在同一个线程上时)如果我理解得很好,只有第一行和最后一行被考虑为StaskDead和EndoDeDy,所以中间的行将被忽略。@纪尧姆-你认为样本中的<代码> TID<代码>的所有值都是<代码> 0 < /代码>吗?还是别的?谢谢!我还在查看文档以了解您的答案^^谢谢!我仍在查看文档以了解您的答案^^谢谢您,特别是
.groupby(np.arange(len(df2))//2)
部分。结合其他回答,我能够解决我的问题:)见我的答案谢谢你,特别是
.groupby(np.arange(len(df2))//2)
部分。结合其他回答,我能够解决我的问题:)看我的答案
# order events by thread id and datetime
df = df.sort_values(['tid', 'datetime']).reset_index(drop=True)
# then group by tid
for tid, group in df.groupby('tid'):
     # for each group, drop the first line if it is a END event
     head = group.head(1).iloc[0]
     if head.status == 'END':
         df.drop(head.name, inplace=True)
     # and drop the last line if it is a START event
     tail = group.tail(1).iloc[0]
     if tail.status == 'START':
         df.drop(tail.name, inplace=True)

# take lines 2 by 2, that will be a START and an END event, that can be aggregated
df.groupby(np.arange(len(df)) // 2).agg({'Tid': 'first', 'DateTime': {'start': 'min', 'stop': 'max'}})