寻找带有条件的连续模式

8

我有一个数据框,如下所示:

  Id  Event SeqNo
   1    A    1
   1    B    2
   1    C    3
   1    ABD  4
   1    A    5
   1    C    6
   1    A    7
   1    CDE  8
   1    D    9
   1    B    10 
   1    ABD  11
   1    D    12
   1    B    13
   1    CDE  14
   1    A    15

我可以帮忙翻译。您需要查找一个模式,即“ABD后跟CDE,没有事件B在它们之间”。例如,这个数据框的输出将会是:

 Id  Event SeqNo
 1    ABD  4
 1    A    5
 1    C    6
 1    A    7
 1    CDE  8

对于单个ID,可以多次遵循此模式,我希望找到所有这些ID及其各自的计数(如果可能)。


这个模式可以被单个ID多次使用,我想找到所有这些ID的列表。这意味着您想要出现此模式的所有ID的列表吗?因此,您不需要找到实际模式或每个ID找到的模式数量。 - jdehesa
一个模式可以有不同的ID吗? - jdehesa
谢谢指出。我已经在问题上更新了它。我的主要目标是获取所有ID的列表。如果我能得到计数,那对我来说更好。数据集中有多个ID(成千上万)。我只对遵循特定模式的ID感兴趣。 - No_body
@Jondiedoop 是的。在CDE之前,ABD可以出现多次。例如:ABD、C、A、ABD、ABD、CDE。对我来说,这只算一次计数。 - No_body
这两个发布的解决方案中,有一个对你有用吗? - Divakar
显示剩余2条评论
3个回答

2

这里有一个向量化的方法,利用一些缩放技巧和卷积来找到所需的模式 -

最初的回答:

# Get the col in context and scale it to the three strings to form an ID array
a = df['Event']
id_ar = (a=='ABD') + 2*(a=='B') + 3*(a=='CDE')

# Mask of those specific strings and hence extract the corresponding masked df
mask = id_ar>0
df1 = df[mask]

# Get pattern col with 1s at places with the pattern found, 0s elsewhere
df1['Pattern'] = (np.convolve(id_ar[mask],[9,1],'same')==28).astype(int)

# Groupby Id col and sum the pattern col for final output
out = df1.groupby(['Id'])['Pattern'].sum()

那个`卷积(convolution)`部分可能有些棘手。这里的想法是使用值为`1`、`2`和`3`的`id_ar`对应于字符串`'ABD'`、`'B'`和`'CDE'`。我们要寻找的是跟随着`1`之后出现的`3`,所以使用核`[9,1]`进行卷积将导致具有`'ABD'`和然后是`'CDE'`的窗口的卷积总和为`1*1 + 3*9 = 28`。因此,我们寻找匹配的卷积总和为`28`。对于跟随着`'ABD'`、`'B'`和`'CDE'`的情况,卷积总和将不同,因此将被过滤掉。
样例运行 - 1) 输入数据框:
In [377]: df
Out[377]: 
   Id Event SeqNo
0   1     A     1
1   1     B     2
2   1     C     3
3   1   ABD     4
4   1     B     5
5   1     C     6
6   1     A     7
7   1   CDE     8
8   1     D     9
9   1     B    10
10  1   ABD    11
11  1     D    12
12  1     B    13
13  2     A     1
14  2     B     2
15  2     C     3
16  2   ABD     4
17  2     A     5
18  2     C     6
19  2     A     7
20  2   CDE     8
21  2     D     9
22  2     B    10
23  2   ABD    11
24  2     D    12
25  2     B    13
26  2   CDE    14
27  2     A    15

2) 中间过滤输出(查看Pattern列是否包含所需模式):
In [380]: df1
Out[380]: 
   Id Event SeqNo  Pattern
1   1     B     2        0
3   1   ABD     4        0
4   1     B     5        0
7   1   CDE     8        0
9   1     B    10        0
10  1   ABD    11        0
12  1     B    13        0
14  2     B     2        0
16  2   ABD     4        0
20  2   CDE     8        1
22  2     B    10        0
23  2   ABD    11        0
25  2     B    13        0
26  2   CDE    14        0

最终输出:

3)最终结果:

In [381]: out
Out[381]: 
Id
1    0
2    1
Name: Pattern, dtype: int64

1
我使用了一种基于假设的解决方案,即除ABDCDEB之外的任何内容都与或解决方案无关。因此,我首先通过过滤操作将它们清除掉。
然后,我想知道是否存在一个在B之间没有ABDCDE。我将Events列向后移动一位(请注意,这不必是按SeqNo单位为1步)。
然后,我检查新df的每一列,看是否Events==ABD 并且 Events_1_Step==CDE,这意味着在它们之间没有B,但可能有其他东西,如AC甚至什么也没有。这为我提供了一个布尔值列表,表示每次我有这样的序列。如果我把它们加起来,我就得到了计数。
最后,我必须确保这些都是在Id级别完成的,所以使用.groupby

重要提示:此解决方案假定您的数据框首先按Id排序,然后再按SeqNo排序。如果不是,请进行排序。

import pandas as pd
df = pd.read_csv("path/to/file.csv")
df2 = df[df["Event"].isin(["ABD", "CDE", "B"])]
df2.loc[:,"Event_1_Step"] = df2["Event"].shift(-1)
df2.loc[:,"SeqNo_1_Step"] = df2["SeqNo"].shift(-1)
for id, id_df in df2.groupby("Id"):
    print(id) # Set a counter object here per Id to track count per id
    id_df = id_df[id_df.apply(lambda x: x["Event"] == "ABD" and x["Event_1_Step"] == "CDE", axis=1)]
    for row_id, row in id_df.iterrows():
        print(df[(df["Id"] == id) * df["SeqNo"].between(row["SeqNo"], row["SeqNo_1_Step"])])

我喜欢你过滤其他模式的想法。我在思考如果我计算ABD和CDE的连续出现次数,它会起作用。 df2.groupby('id')['Event'].agg(''.join).str.count('ABDCDE ')。这将给出这些模式连续出现的次数。 - No_body
基本上,如果 id_df.iterrows() 中的每一行都是一个模式。我会在那个循环下面简单地添加一个 counter 对象,其键由 df['Id'].unique().values 初始化。 - D_Serg

-1
你可以使用这个:
s = (pd.Series(
np.select([df['Event'] == 'ABD', df['Event'] =='B', df['Id'] != df['Id'].shift()],

                         [True, False, False], default=np.nan))
.ffill()
.fillna(False)
.astype(bool))
corr = (df['Event'] == "CDE") & s
corr.groupby(df['Id']).max()

使用np.select创建一个列,当Event == 'CDE'时为True,并且对于B或者在新的Id开始时为False。通过使用ffill进行向前填充。您可以得到每个值之前是ABD还是B。然后您可以检查是否为CDE的值为True。接下来,您可以使用GroupBy检查每个Id中是否有任何值为True
对于...
   Id  Event SeqNo
   1    A    1
   1    B    2
   1    C    3
   1    ABD  4
   1    A    5
   1    C    6
   1    A    7
   1    CDE  8
   1    D    9
   1    B    10 
   1    ABD  11
   1    D    12
   1    B    13
   1    CDE  14
   1    A    15
   2    B    16
   3    ABD  17
   3    B    18
   3    CDE  19
   4    ABD  20
   4    CDE  21
   5    CDE  22

输出:

Id
1     True
2    False
3    False
4     True
5    False

这个程序并没有输出期望的结果,而且真值和假值不匹配,无法适当地开始和停止。 - d_kennetz

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