PandasJuniorCoding

Как обрабатывать дублированные строки в DataFrame — duplicated() и drop_duplicates()?

duplicated() возвращает булеву маску дублей. drop_duplicates() удаляет их. Параметр subset задаёт колонки для сравнения, keep=("first"/"last"/False) — какие вхождения оставить. NaN считаются одинаковыми. Всегда делайте reset_index после очистки.

Два метода — разные задачи

duplicated() возвращает булеву маску: True для строк, которые являются дубликатами. drop_duplicates() возвращает DataFrame без дубликатов. Оба принимают одинаковые параметры.

Базовое использование

import pandas as pd

df = pd.DataFrame({
    "user_id": [1, 2, 2, 3, 3],
    "event":   ["click", "click", "click", "view", "purchase"],
    "ts":      pd.to_datetime(["2024-01-01"] * 3 + ["2024-01-02"] * 2),
})

# Найти полные дубликаты
print(df.duplicated())                # [False, False, True, False, False]

# Удалить полные дубликаты, оставить первое вхождение
df_clean = df.drop_duplicates()
print(df_clean.shape)                 # (4, 3)

# Дубликаты только по user_id (разные события — не дубликаты)
print(df.duplicated(subset=["user_id"]))
# [False, False, True, False, True]

Параметр keep

# keep="first" (по умолчанию) — оставить первое вхождение
df.drop_duplicates(subset=["user_id"], keep="first")

# keep="last" — оставить последнее вхождение
df.drop_duplicates(subset=["user_id"], keep="last")

# keep=False — удалить ВСЕ строки с дублирующимся ключом
df.drop_duplicates(subset=["user_id"], keep=False)
# Полезно, чтобы найти только уникальные события (без повторений вообще)

Практический паттерн: дедупликация событий

# Оставить последнее событие каждого пользователя
# (вместо drop_duplicates лучше sort + keep="last")
df_latest = (
    df.sort_values("ts")
      .drop_duplicates(subset=["user_id"], keep="last")
      .reset_index(drop=True)
)
print(df_latest)

# Посмотреть, сколько дубликатов по каждому user_id
dup_counts = df[df.duplicated(subset=["user_id"], keep=False)].groupby("user_id").size()
print(dup_counts)

Работа с NaN

# По умолчанию два NaN считаются одинаковыми (дублируют друг друга)
df2 = pd.DataFrame({"a": [1, None, None], "b": ["x", "y", "y"]})
print(df2.duplicated())  # [False, False, True]

Подводные камни

  • inplace=True создаёт копию с CoW — с включённым Copy-on-Write (Pandas 2.0+) df.drop_duplicates(inplace=True) может не менять оригинал, если df — срез; безопаснее df = df.drop_duplicates().
  • NaN == NaN для duplicated — два NaN в одном столбце считаются дублями, что расходится с поведением обычного равенства Python (float("nan") != float("nan")).
  • keep=False удаляет все вхождения — легко перепутать с «удалить лишние копии»; если нужно оставить хотя бы одну запись, используйте keep="first".
  • reset_index после drop_duplicates — индекс остаётся от исходного DataFrame; при последующих операциях по позиции это приводит к ошибкам.
  • subset не проверяет орфографию — если в списке subset есть опечатка в имени колонки, Pandas поднимает KeyError только в момент вызова, а не при объявлении.
  • Производительность на больших данныхdrop_duplicates строит хэш-таблицу; на DataFrame с миллионами строк и широким subset стоит профилировать или использовать polars/dask.
  • Порядок строк после keep="last"drop_duplicates(keep="last") не сортирует DataFrame; строки остаются в исходном порядке, просто выбирается последнее вхождение по позиции в df.

Common mistakes

  • Объяснять duplicate rows только синтаксисом без shape, dtype, состояния или режима выполнения.
  • Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
  • Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.

What the interviewer is testing

  • Может ли связать duplicate rows с реальным контрактом входов и выходов.
  • Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
  • Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.

Sources

Related topics