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