Pandas: удалить все записи дублирующих индексов

У меня есть набор данных с потенциально повторяющимися записями идентификатора appkey. Дублированные записи в идеале не должны существовать, и поэтому я воспринимаю их как ошибки сбора данных. Мне нужно удалить все экземпляры appkey, которые встречаются более одного раза.

Метод drop_duplicates не полезен в этом случае (или он?), поскольку он либо выбирает первый, либо последний из дубликатов. Есть ли очевидная идиома для достижения этого с помощью pandas?

Ответ 1

Как и pandas версия 0.12, для этого мы имеем filter. Он делает именно то, что решение @Andy использует transform, но немного более лаконично и несколько быстрее.

df.groupby('AppKey').filter(lambda x: x.count() == 1)

Чтобы украсть пример @Andy,

In [1]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=['AppKey', 'B'])

In [2]: df.groupby('AppKey').filter(lambda x: x.count() == 1)
Out[2]: 
   AppKey  B
2       5  6

Ответ 2

Здесь один из способов, используя transform со счетом:

In [1]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=['AppKey', 'B'])

In [2]: df
Out[2]:
   AppKey  B
0       1  2
1       1  4
2       5  6

Группировать по столбцу AppKey и применять счетчик преобразований, означает, что каждое вхождение AppKey подсчитывается, а счетчик присваивается тем строкам, где он отображается:

In [3]: count_appkey = df.groupby('AppKey')['AppKey'].transform('count')

In [4]: count_appkey
Out[4]:
0    2
1    2
2    1
Name: AppKey, dtype: int64

In [5]: count_appkey == 1
Out[5]:
0    False
1    False
2     True
Name: AppKey, dtype: bool

Затем вы можете использовать это как булевскую маску для исходного DataFrame (оставляя только те строки, чей AppKey встречается ровно один раз):

In [6]: df[count_appkey == 1]
Out[6]:
   AppKey  B
2       5  6

Ответ 3

В версии pandas версии 0.17 функция drop_duplicates имеет параметр 'keep', который может быть установлен на 'False', чтобы не содержать дублированных записей (другие опции: keep = 'first' и keep = 'last'). Итак, в этом случае:

df.drop_duplicates(subset=['appkey'],keep=False)

Ответ 4

Следующее решение, использующее set operations, работает для меня. Это значительно быстрее, хотя и немного более подробное, чем решение filter:

In [1]: import pandas as pd
In [2]: def dropalldups(df, key):
   ...:     first = df.duplicated(key)  # really all *but* first
   ...:     last = df.duplicated(key, take_last=True)
   ...:     return df.reindex(df.index - df[first | last].index)
   ...: 
In [3]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=['AppKey', 'B'])
In [4]: dropalldups(df, 'AppKey')
Out[4]: 
   AppKey  B
2       5  6

[1 rows x 2 columns]
In [5]: %timeit dropalldups(df, 'AppKey')
1000 loops, best of 3: 379 µs per loop
In [6]: %timeit df.groupby('AppKey').filter(lambda x: x.count() == 1)
1000 loops, best of 3: 1.57 ms per loop

В больших наборах данных разница в производительности намного более драматична. Вот результаты для DataFrame с 44k строк. Столбец, который я фильтрую, представляет собой 6-символьную строку. Есть 870 вхождений 560 повторяющихся значений:

In [94]: %timeit dropalldups(eq, 'id')
10 loops, best of 3: 26.1 ms per loop
In [95]: %timeit eq.groupby('id').filter(lambda x: x.count() == 1)
1 loops, best of 3: 13.1 s per loop