Ищем последовательный паттерн с условием - PullRequest
0 голосов
/ 06 февраля 2019

У меня есть df как

  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 между ними". Например, вывод этого df будет:

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

Этот шаблон можно использовать несколько раз для одного идентификатора, и я хочу найти список всех этих идентификаторов и их соответствующее количество (если возможно).

Ответы [ 3 ]

0 голосов
/ 06 февраля 2019

Я использовал решение, основанное на предположении, что все, кроме ABD, CDE и B, не имеет отношения к решению или решению.Поэтому я сначала избавляюсь от них с помощью операции фильтрации.

Тогда, что я хочу знать, если есть ABD, за которым следует CDE без B между ними.Я сдвигаю столбец Events на единицу времени (обратите внимание, что это не должен быть 1 шаг в единицах SeqNo).

Затем я проверяю каждый столбец нового df: Events==ABD и Events_1_Step==CDE, что означает, что между ними не было B, но, возможно, другие вещи, такие как Aили C или даже ничего.Это дает мне список логических значений для каждого такого случая.Если я подведу их итог, я получу счет.

Наконец, я должен убедиться, что все это сделано на уровне Id, поэтому используйте .groupby.

ВАЖНО: В этом решении предполагается, что ваш dfотсортировано сначала по 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"])])
0 голосов
/ 06 февраля 2019

Вот векторизованный с некоторой хитростью масштабирования и использованием свертки, чтобы найти нужный шаблон -

# 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 может быть немного хитрой.Идея состоит в том, чтобы использовать id_ar со значениями 1, 2 и 3, соответствующими строкам 'ABD', ''B' и 'CDE'.Мы ищем 1, за которым следует 3, поэтому использование свертки с ядром [9,1] приведет к 1*1 + 3*9 = 28 в качестве суммы свертки для окна, которое имеет 'ABD', а затем 'CDE'.Следовательно, мы ищем конв.сумма 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) Промежуточный отфильтрованный o / p (посмотрите настолбец 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) Конечный o / p:

In [381]: out
Out[381]: 
Id
1    0
2    1
Name: Pattern, dtype: int64
0 голосов
/ 06 февраля 2019

Вы можете использовать это:

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 для создания столбца, который имеет True, если Event == 'CDE" и False для B или приначало нового Id.При прямом заполнении с использованием ffill.У вас есть для каждого значения, был ли ABD или B последним.Затем вы можете проверить, истинно ли это, где значение равно CDE.Затем вы можете использовать GroupBy, чтобы проверить, является ли оно True для любого значения на Id.

, которое для

   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
...