У меня есть следующий dataframe df:

data={'id':[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],
      'value':[2,2,3,2,2,2,3,3,3,3,1,4,1,1,1,4,4,1,1,1,1,1]}
df=pd.DataFrame.from_dict(data)
df
Out[8]: 
    id  value
0    1      2
1    1      2
2    1      3
3    1      2
4    1      2
5    1      2
6    1      3
7    1      3
8    1      3
9    1      3
10   2      1
11   2      4
12   2      1
13   2      1
14   2      1
15   2      4
16   2      4
17   2      1
18   2      1
19   2      1
20   2      1
21   2      1

Что мне нужно сделать, это идентифицировать на уровне идентификатора (df.groupby ['id']), когда значение показывает одно и то же число последовательно в течение 3 или более раз.

Я хотел бы получить следующий результат для вышеупомянутого:

df
Out[12]: 
    id  value  flag
0    1      2     0
1    1      2     0
2    1      3     0
3    1      2     1
4    1      2     1
5    1      2     1
6    1      3     1
7    1      3     1
8    1      3     1
9    1      3     1
10   2      1     0
11   2      4     0
12   2      1     1
13   2      1     1
14   2      1     1
15   2      4     0
16   2      4     0
17   2      1     1
18   2      1     1
19   2      1     1
20   2      1     1
21   2      1     1

Я попробовал вариации groupby и lambda, используя pandas roll.mean, чтобы определить, где среднее значение периода прокатки сравнивается с «значением», и где они совпадают, это указывает на флаг. Но у этого есть несколько проблем, включая то, что у вас могут быть разные значения, которые будут усреднены по значению, которое вы пытаетесь пометить. Кроме того, я не могу понять, как «пометить» все значения скользящего среднего, которые создали начальный флаг. Смотрите здесь, это определяет «правую сторону» флага, но тогда мне нужно заполнить предыдущие значения скользящей средней длины. Смотрите мой код здесь:

test=df.copy()
test['rma']=test.groupby('id')['value'].transform(lambda x: x.rolling(min_periods=3,window=3).mean())
test['flag']=np.where(test.rma==test.value,1,0)

И результат здесь:

test
Out[61]: 
    id  value       rma  flag
0    1      2       NaN     0
1    1      2       NaN     0
2    1      3  2.333333     0
3    1      2  2.333333     0
4    1      2  2.333333     0
5    1      2  2.000000     1
6    1      3  2.333333     0
7    1      3  2.666667     0
8    1      3  3.000000     1
9    1      3  3.000000     1
10   2      1       NaN     0
11   2      4       NaN     0
12   2      1  2.000000     0
13   2      1  2.000000     0
14   2      1  1.000000     1
15   2      4  2.000000     0
16   2      4  3.000000     0
17   2      1  3.000000     0
18   2      1  2.000000     0
19   2      1  1.000000     1
20   2      1  1.000000     1
21   2      1  1.000000     1

Не могу дождаться, чтобы увидеть, что мне не хватает! Благодарность

10
clg4 25 Авг 2017 в 20:15

4 ответа

Лучший ответ

Вы можете попробовать это; 1) Создайте дополнительную групповую переменную с df.value.diff().ne(0).cumsum(), чтобы обозначить изменения значения; 2) используйте transform('size'), чтобы вычислить размер группы и сравнить с тремя, после чего вы получите столбец flag, который вам нужен:

df['flag'] = df.value.groupby([df.id, df.value.diff().ne(0).cumsum()]).transform('size').ge(3).astype(int) 
df

enter image description here


Разбивка .

1) diff не равен нулю (что буквально означает, что df.value.diff().ne(0) означает) дает условие True всякий раз, когда происходит изменение значения:

df.value.diff().ne(0)
#0      True
#1     False
#2      True
#3      True
#4     False
#5     False
#6      True
#7     False
#8     False
#9     False
#10     True
#11     True
#12     True
#13    False
#14    False
#15     True
#16    False
#17     True
#18    False
#19    False
#20    False
#21    False
#Name: value, dtype: bool

2) Тогда cumsum дает не убывающую последовательность идентификаторов, где каждый идентификатор обозначает последовательный блок с одинаковыми значениями, обратите внимание, что при суммировании логических значений True рассматривается как единица, а False рассматривается как нуль:

df.value.diff().ne(0).cumsum()
#0     1
#1     1
#2     2
#3     3
#4     3
#5     3
#6     4
#7     4
#8     4
#9     4
#10    5
#11    6
#12    7
#13    7
#14    7
#15    8
#16    8
#17    9
#18    9
#19    9
#20    9
#21    9
#Name: value, dtype: int64

3) в сочетании со столбцом id вы можете сгруппировать фрейм данных, рассчитать размер группы и получить столбец flag.

23
Psidom 25 Авг 2017 в 18:32
df=pd.DataFrame.from_dict(
        {'id':[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],
         'value':[2,2,3,2,2,2,3,3,3,3,1,4,1,1,1,4,4,1,1,1,1,1]})

df2 = df.groupby((df['value'].shift() != df['value']).\
                cumsum()).filter(lambda x: len(x) >= 3)

df['flag'] = np.where(df.index.isin(df2.index),1,0)
0
Mott The Tuple 20 Авг 2019 в 23:53
#try this simpler version
a= pd.Series([1,1,1,2,3,4,5,5,5,7,8,0,0,0])
b= a.groupby([a.ne(0), a]).transform('size').ge(3).astype('int')
#ge(x) <- x is the number of consecutive repeated values 
print b
2
Deepan Wadhwa 5 Апр 2018 в 18:27

См. EDIT2 для более надежного решения .

Тот же результат, но немного быстрее:

labels = (df.value != df.value.shift()).cumsum()
df['flag'] = (labels.map(labels.value_counts()) >= 3).astype(int)

    id  value  flag
0    1      2     0
1    1      2     0
2    1      3     0
3    1      2     1
4    1      2     1
5    1      2     1
6    1      3     1
7    1      3     1
8    1      3     1
9    1      3     1
10   2      1     0
11   2      4     0
12   2      1     1
13   2      1     1
14   2      1     1
15   2      4     0
16   2      4     0
17   2      1     1
18   2      1     1
19   2      1     1
20   2      1     1
21   2      1     1

Куда:

  1. df.value != df.value.shift() дает изменение значения
  2. cumsum() создает «метки» для каждой группы с одинаковым значением
  3. labels.value_counts() считает количество каждого ярлыка
  4. labels.map(...) заменяет метки подсчитанным выше количеством
  5. >= 3 создает логическую маску для значения счетчика
  6. astype(int) переводит логические значения в int

В моих руках это дает 1,03 мс на ваш df, по сравнению с 2,1 мс для подхода Psidoms. Но мой не один вкладыш.


РЕДАКТИРОВАТЬ:

Сочетание обоих подходов еще быстрее

labels = df.value.diff().ne(0).cumsum()
df['flag'] = (labels.map(labels.value_counts()) >= 3).astype(int)

Дает 911 мкс с вашим образцом df.


EDIT2: правильное решение для учета изменения идентификатора, как указано @ clg4

labels = (df.value.diff().ne(0) | df.id.diff().ne(0)).cumsum()
df['flag'] = (labels.map(labels.value_counts()) >= 3).astype(int)

Где ... | df.id.diff().ne(0) увеличивает метку, где изменяется идентификатор

Это работает даже при одинаковом значении при изменении идентификатора (проверено со значением 3 в индексе 10) и занимает 1,28 мс

EDIT3: лучшие объяснения

Возьмем случай, когда индекс 10 имеет значение 3. df.id.diff().ne(0)

data={'id':[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],
      'value':[2,2,3,2,2,2,3,3,3,3,3,4,1,1,1,4,4,1,1,1,1,1]}
df=pd.DataFrame.from_dict(data)

df['id_diff'] = df.id.diff().ne(0).astype(int)
df['val_diff'] = df.value.diff().ne(0).astype(int)
df['diff_or'] = (df.id.diff().ne(0) | df.value.diff().ne(0)).astype(int)
df['labels'] = df['diff_or'].cumsum()

     id  value  id_diff  val_diff  diff_or  labels
 0    1      2        1         1        1       1
 1    1      2        0         0        0       1
 2    1      3        0         1        1       2
 3    1      2        0         1        1       3
 4    1      2        0         0        0       3
 5    1      2        0         0        0       3
 6    1      3        0         1        1       4
 7    1      3        0         0        0       4
 8    1      3        0         0        0       4
 9    1      3        0         0        0       4
>10   2      3        1    |    0    =   1       5 <== label increment
 11   2      4        0         1        1       6
 12   2      1        0         1        1       7
 13   2      1        0         0        0       7
 14   2      1        0         0        0       7
 15   2      4        0         1        1       8
 16   2      4        0         0        0       8
 17   2      1        0         1        1       9
 18   2      1        0         0        0       9
 19   2      1        0         0        0       9
 20   2      1        0         0        0       9
 21   2      1        0         0        0       9

| является оператором "bitwise-or", который дает True, пока один из элементов равен True. Поэтому, если в значении, где идентификатор изменяется, отсутствует разница в значении, | отражает изменение идентификатора. В противном случае это ничего не меняет. Когда выполняется .cumsum(), метка увеличивается при изменении идентификатора, поэтому значение 3 в индексе 10 не группируется со значениями 3 из индексов 6-9.

2
FabienP 26 Авг 2017 в 08:17