Сообщить об ошибке.

Методы .duplicated() и .drop_duplicates() объектов DataFrame/Series в pandas

Просмотр/удаление повторяющихся значений столбцов/строк DataFrame/Series

Материал рассматривает методы работы с повторяющимися строками или значениями столбцов в DataFrame. В частности рассматриваются методы:


DataFrame.duplicated(subset=None, keep='first'):

Метод DataFrame.duplicated() возвращает логическую Series, которая обозначает повторяющиеся строки.

Учет определенных столбцов subset при поиске дубликатов не является обязательным.

Принимаемые аргументы:

  • subset=None - поиск дубликатов только в определенных столбцах. По умолчанию используются все столбцы. Принимает метку столбца или список меток столбцов.
  • keep='first' - определяет, какие дубликаты (если есть) оставлять в DataFrame.
    • first : пометить дубликаты как True, за исключением первого вхождения.
    • last : пометить дубликаты как True, за исключением последнего вхождения.
    • False: пометить все дубликаты как True.

Пример использования DataFrame.duplicated()

Рассмотрим следующий набор данных:

>>> import pandas as pd

df = pd.DataFrame({
    'brand': ['Yum Yum', 'Yum Yum', 'Indomie', 'Indomie', 'Indomie'],
    'style': ['cup', 'cup', 'cup', 'pack', 'pack'],
    'rating': [4, 4, 3.5, 15, 5]
})

>>> df
#     brand style  rating
# 0  Yum Yum   cup     4.0
# 1  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0

По умолчанию для каждого набора повторяющихся значений первое вхождение имеет значение False, а все остальные - True.

>>> df.duplicated()
# 0    False
# 1     True
# 2    False
# 3    False
# 4    False
# dtype: bool

А как собственно посмотреть убранные дубликаты из DataFrame? Просто используем логическую индексацию:

>>> df.loc[df.duplicated(), :]
#      brand style  rating
# 1  Yum Yum   cup     4.0

Теперь, что бы посмотреть DataFrame без дубликатов, необходимо в исходный DataFrame передать отрицание ~df.duplicated() или использовать метод DataFrame.drop_duplicates():

# передадим в `DataFrame` отрицание 
# логической индексации
>>> df.loc[~df.duplicated(), :]
#      brand style  rating
# 0  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0

При использовании keep='last', для последнего вхождения каждого набора повторяющихся значений устанавливается значение False, а для всех остальных - значение True.

>>> df.duplicated(keep='last')
# 0     True
# 1    False
# 2    False
# 3    False
# 4    False
# dtype: bool

Если установить для keep=False, то все дубликаты будут иметь значение True.

>>> df.duplicated(keep=False)
# 0     True
# 1     True
# 2    False
# 3    False
# 4    False
# dtype: bool

Чтобы найти дубликаты в определенных столбцах, необходимо использовать аргумент subset.

>>> df.duplicated(subset=['brand'])
# 0    False
# 1     True
# 2    False
# 3     True
# 4     True
# dtype: bool

Series.duplicated(keep='first'):

Метод Series.duplicated() возвращает логическую Series, которая обозначает повторяющиеся строки.

Повторяющиеся значения в результирующей серии обозначаются как True. Могут быть указаны либо все дубликаты, либо все, кроме первого, либо все дубликаты, кроме последнего.

Принимаемые аргументы:

  • keep='first' - определяет, какие дубликаты (если есть) оставлять в DataFrame.
    • first : пометить дубликаты как True, за исключением первого вхождения.
    • last : пометить дубликаты как True, за исключением последнего вхождения.
    • False: пометить все дубликаты как True.

Пример использования Series.duplicated()

>>> import pandas as pd
>>> animals = pd.Series(['llama', 'cow', 'llama', 'beetle', 'llama'])
>>> animals.duplicated()
# 0    False
# 1    False
# 2     True
# 3    False
# 4     True
# dtype: bool

# что эквивалентно:
>>> animals.duplicated(keep='first')

Теперь смотрим убранные дубликаты из Series, для этого просто используем логическую индексацию:

>>> animals.loc[animals.duplicated()]
# 2    llama
# 4    llama
# dtype: object

А теперь чистый Series - БЕЗ дубликатов, передав в исходный Series отрицание логической индексации или можно использовать метод Series.drop_duplicates():

>>> animals.loc[~animals.duplicated()]
# 0     llama
# 1       cow
# 3    beetle
# dtype: object

При использовании keep='last' последнее вхождение каждого набора повторяющихся значений устанавливается в False, а все остальные - в True:

>>> animals.duplicated(keep='last')
# 0     True
# 1    False
# 2     True
# 3    False
# 4    False
# dtype: bool

При установке keep=False, все дубликаты имеют значение True:

>>> animals.duplicated(keep=False)
# 0     True
# 1    False
# 2     True
# 3    False
# 4     True
# dtype: bool

DataFrame.drop_duplicates(subset=None, *, keep='first', inplace=False, ignore_index=False):

Метод DataFrame.drop_duplicates() возвращает DataFrame с удаленными дубликатами строк.

Поиск дублирующих значений в определенных столбцах subset не является обязательным. Индексы, в том числе индексы времени, игнорируются.

Принимаемые аргументы:

  • subset=None - поиск дубликатов только в определенных столбцах. По умолчанию используются все столбцы. Принимает метку столбца или список меток столбцов.
  • keep='first' - определяет, какие дубликаты (если есть) оставлять в DataFrame.
    • first : пометить дубликаты как True, за исключением первого вхождения.
    • last : пометить дубликаты как True, за исключением последнего вхождения.
    • False: пометить все дубликаты как True.
  • inplace=False - следует ли изменять исходный DataFrame, а не создавать новый.
  • ignore_index=False - если значение True, то результирующая ось будет помечена как 0, 1, ..., n - 1.

Пример использования DataFrame.drop_duplicates()

Рассмотрим следующий набор данных:

>>> import pandas as pd

df = pd.DataFrame({
    'brand': ['Yum Yum', 'Yum Yum', 'Indomie', 'Indomie', 'Indomie'],
    'style': ['cup', 'cup', 'cup', 'pack', 'pack'],
    'rating': [4, 4, 3.5, 15, 5]
})

>>> df
#     brand style  rating
# 0  Yum Yum   cup     4.0
# 1  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0

По умолчанию, метод удаляет повторяющиеся строки на основе всех столбцов.

>>> df.drop_duplicates()
#      brand style  rating
# 0  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0

Чтобы удалить дубликаты в определенных столбцах, используем аргумент subset.

>>> df.drop_duplicates(subset=['brand'])
#      brand style  rating
# 0  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5

Чтобы удалить дубликаты значений столбцов 'brand' и 'style' и при этом сохранить последние вхождения, используем аргумент keep.

>>> df.drop_duplicates(subset=['brand', 'style'], keep='last')
#      brand style  rating
# 1  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 4  Indomie  pack     5.0

Series.drop_duplicates(*, keep='first', inplace=False, ignore_index=False):

Метод Series.drop_duplicates() возвращает Series с удаленными дубликатами строк.

Принимаемые аргументы:

  • keep='first' - определяет, какие дубликаты (если есть) оставлять в DataFrame.
    • first : пометить дубликаты как True, за исключением первого вхождения.
    • last : пометить дубликаты как True, за исключением последнего вхождения.
    • False: пометить все дубликаты как True.
  • inplace=False - следует ли изменять исходный DataFrame, а не создавать новый.
  • ignore_index=False - если значение True, то результирующая ось будет помечена как 0, 1, ..., n - 1.

Пример использования Series.drop_duplicates()

Создадим серию с повторяющимися записями.

>>> import pandas as pd
>>> import numpy as np
>>> s = pd.Series(['llama', 'cow', 'llama', 'beetle', 'llama', 'hippo'], name='animal')
>>> s
# 0     llama
# 1       cow
# 2     llama
# 3    beetle
# 4     llama
# 5     hippo
# Name: animal, dtype: object

С помощью аргумента keep можно изменить поведение выбора повторяющихся значений. Значение 'first' сохраняет первое вхождение для каждого набора повторяющихся записей. Значение keep по умолчанию 'first'.

>>> s.drop_duplicates()
# 0     llama
# 1       cow
# 3    beetle
# 5     hippo
# Name: animal, dtype: object

Значение keep='last' сохраняет последнее вхождение для каждого набора повторяющихся записей.

>>> s.drop_duplicates(keep='last')
# 1       cow
# 3    beetle
# 4     llama
# 5     hippo
# Name: animal, dtype: object

Значение keep=False отбрасывает все наборы повторяющихся записей.

>>> s.drop_duplicates(keep=False)
# 1       cow
# 3    beetle
# 5     hippo
# Name: animal, dtype: object

Повторяющиеся метки индекса строк и столбцов

Объекты pandas.Index не обязательно должны быть уникальными и в нем могут встречаться повторяющиеся метки строк или столбцов. Поначалу это может сбить с толку. Например, в SQL метки строк аналогичны первичному ключу таблицы. Но одна из ролей библиотеки pandas - очищать беспорядочные реальные данные, прежде чем они попадут в какую-либо систему. А реальные данные имеют дубликаты даже в полях, которые должны быть уникальными.

Некоторые методы pandas (например, Series.reindex()) просто не работают при наличии дубликатов. Выход не может быть определен, поэтому pandas будет поднимать исключение.

>>> s1 = pd.Series([0, 1, 2], index=["a", "b", "b"])
>>> s1.reindex(["a", "b", "c"])
# Traceback (most recent call last):
# ...
# ...
# ValueError: cannot reindex on an axis with duplicate labels

Обнаружение повторяющихся меток индекса строк и столбцов

Можно проверить, является ли индекс (хранящий метки строк или столбцов) уникальным с помощью Index.is_unique:

>>> df = pd.DataFrame({"A": [0, 1, 2]}, index=["a", "a", "b"])
>>> df
#    A
# a  0
# a  1
# b  2

>>> df.index.is_unique
# False
>>> df.columns.is_unique
# True

Примечание. Проверка уникальности индекса является несколько дорогостоящей для больших наборов данных. pandas кэширует этот результат, поэтому повторная проверка по тому же индексу происходит очень быстро.

Метод Index.duplicated() вернет логический массив, указывающий, повторяется ли метка. Его можно использовать в логической индексации:

>>> df.loc[~df.index.duplicated(), :]
#    A
# a  0
# b  2

Если необходима дополнительная логика для обработки повторяющихся меток, а не просто отбрасывание повторов, использование DataFrame.groupby() для индекса является обычным приемом. Например, устраним дубликаты, взяв среднее значение всех строк с одинаковой меткой.

>>> df.groupby(level=0).mean()
#      A
# a  0.5
# b  2.0

Запрет появления дубликатов индексных меток в дальнейшем

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

# Внимание! Абстрактный код. 
raw_data = pd.read_csv("...")
# удаляем дубликаты
deduplicated = raw_data.groupby(level=0).first()
# запретить повторяющиеся индексные метки
deduplicated.flags.allows_duplicate_labels = False

Установка allows_duplicate_labels=False для Series или DataFrame с повторяющимися метками или выполнение операции, которая вводит повторяющиеся метки в Series/DataFrame, вызовет исключение errors.DuplicateLabelError.