Урок курса
Дубли: duplicated и drop_duplicates по ключевым столбцам
NumPy и pandas: практический тренажёрДублирующиеся строки — одна из самых частых проблем в реальных данных: один и тот же пользователь попадает в таблицу дважды, событие записывается повторно из-за сбоя пайплайна, справочник загружается с пересечениями. Прежде чем что-то удалять, нужно научиться точно находить дубли — и делать это не по всей строке целиком, а по конкретным ключевым столбцам.
duplicated: обнаружение дублирующихся строк по subset
Метод df.duplicated() возвращает булев Series той же длины, что и исходный DataFrame. Каждое значение отвечает на вопрос: «эта строка уже встречалась выше?» Если встречалась — True, если нет — False.
По умолчанию duplicated() сравнивает сразу все столбцы. Но на практике чаще нужно проверять дублирование только по ключевым полям — например, по паре (user_id, action). Для этого есть параметр subset:
import pandas as pd
df = pd.DataFrame({
'user_id': [1, 2, 1, 3, 2],
'action': ['login', 'buy', 'login', 'view', 'buy'],
'amount': [0, 100, 0, 50, 200]
})
mask = df.duplicated(subset=['user_id', 'action'])
print(mask)
# 0 False
# 1 False
# 2 True
# 3 False
# 4 True
# dtype: bool
print(mask.sum()) # 2 — сколько строк являются кандидатами в дубли
Строки с индексами 2 и 4 отмечены True: пара (1, 'login') уже встречалась в строке 0, а пара (2, 'buy') — в строке 1. Столбец amount в сравнение не входит — только то, что указано в subset.
Важно понимать, что duplicated(subset=...) фиксирует совпадение значений по выбранным столбцам — и ничего больше. Это не означает, что строки бизнес-эквивалентны. Посмотри на пару (2, 'buy'): строки 1 и 4 совпадают по user_id и action, но amount у них разный — 100 и 200. Это могут быть два разных события одного пользователя, а не случайный дубль. Поэтому результат duplicated() — это список кандидатов в дубли по выбранному ключу, которых нужно осмыслить, прежде чем удалять.
Булев Series из duplicated() — это обычная маска. Её можно передать в df[mask], чтобы посмотреть на строки-кандидаты:
print(df[mask])
# user_id action amount
# 2 1 login 0
# 4 2 buy 200
Прежде чем переходить к удалению, стоит взглянуть и на «оригиналы» рядом — например, через keep=False (это разберём в следующем разделе). Убедившись, что совпадение ключа действительно означает нежелательный повтор, а не два легитимных события, можно переходить к drop_duplicates().
Параметр keep: управление тем, что считается дублем
Когда duplicated() находит группу строк с одинаковыми значениями по subset, она должна решить: какую из них считать «оригиналом», а какие — дублями. Этим управляет параметр keep.
По умолчанию keep='first': первое вхождение получает False (не дубль), все последующие — True. При keep='last' логика зеркальная: последнее вхождение считается «оригиналом», все предыдущие помечаются True.
import pandas as pd
df = pd.DataFrame({
'user_id': [1, 2, 1, 3, 2],
'action': ['login', 'buy', 'login', 'view', 'buy'],
'amount': [0, 100, 0, 50, 200]
})
key_cols = ['user_id', 'action']
print(df.duplicated(subset=key_cols, keep='first').tolist())
# [False, False, True, False, True]
print(df.duplicated(subset=key_cols, keep='last').tolist())
# [True, True, False, False, False]
Здесь строки 0 и 2 — это пара (1, 'login'). При keep='first' дублем считается строка 2; при keep='last' — строка 0. Строки 1 и 4 — пара (2, 'buy'): аналогично меняется, кто из них получает True.
Третье значение — keep=False — отличается принципиально: дублями помечаются все строки группы, включая первое вхождение.
print(df.duplicated(subset=key_cols, keep=False).tolist())
# [True, True, True, False, True]
Это удобно для диагностики: чтобы увидеть все строки, участвующие в дублировании, достаточно отфильтровать по этой маске:
print(df[df.duplicated(subset=key_cols, keep=False)])
# user_id action amount
# 0 1 login 0
# 1 2 buy 100
# 2 1 login 0
# 4 2 buy 200
Один из важных практических нюансов: если передать keep=False не в duplicated(), а в drop_duplicates(), результат будет удалять все строки с дублями — включая «оригиналы». То есть из таблицы исчезнут все четыре строки выше, а не только «лишние» копии. Для большинства задач это не то, что нужно.
Правило простое: keep=False — инструмент диагностики, чтобы посмотреть на проблему. Для реального удаления используй keep='first' или keep='last', явно выбирая, какую запись из группы сохранить.
Самостоятельная практика. Потренируйтесь применять keep=False: найдите все строки с повторяющимися ключами и проверьте результат на задаче. Решить задачу на поиск дублей. В бесплатном тренажёре NumPy и pandas есть последовательная практика обработки таблиц с проверкой кода; после регистрации дальнейший прогресс сохраняется. Открыть бесплатный тренажёр pandas.
drop_duplicates: удаление дублей с явным subset и keep
Когда duplicated() показал группы совпадений по ключу и вы убедились, что этот ключ действительно должен быть уникален, можно переходить к удалению. drop_duplicates() возвращает новый DataFrame, в котором из каждой группы совпадений остаётся ровно одна строка — в соответствии с выбранным keep.
Синтаксис симметричен duplicated(): те же параметры subset и keep, тот же смысл:
import pandas as pd
df = pd.DataFrame({
'user_id': [1, 2, 1, 3, 2],
'action': ['login', 'buy', 'login', 'view', 'buy'],
'amount': [0, 100, 0, 50, 200]
})
key_cols = ['user_id', 'action']
# keep='first' — оставить первое вхождение каждой группы
df_first = df.drop_duplicates(subset=key_cols, keep='first')
print(df_first[['user_id', 'action', 'amount']].values.tolist())
# [[1, 'login', 0], [2, 'buy', 100], [3, 'view', 50]]
# keep='last' — оставить последнее вхождение
df_last = df.drop_duplicates(subset=key_cols, keep='last')
print(df_last[['user_id', 'action', 'amount']].values.tolist())
# [[1, 'login', 0], [3, 'view', 50], [2, 'buy', 200]]
Разница между двумя вариантами наиболее заметна по паре (2, 'buy'): при keep='first' остаётся строка с amount=100 (индекс 1), при keep='last' — строка с amount=200 (индекс 4). Для user_id=1 результат внешне одинаков в обоих случаях — не потому что у этого пользователя нет дублей, а потому что оба вхождения пары (1, 'login') полностью идентичны, включая amount=0: неважно, какое из них сохранить. Строка user_id=3 не имеет совпадений по ключу и присутствует в результате без изменений.
drop_duplicates() не меняет исходный DataFrame. Результат — новый объект, его нужно присвоить переменной. Вызов без присвоения ничего не изменит.
Выбор между keep='first' и keep='last' определяется логикой данных: если строки упорядочены по времени и нужна самая свежая запись — берёшь keep='last'; если важна первая зафиксированная — keep='first'. В обоих случаях нужно убедиться, что данные отсортированы так, как ожидается, иначе «первое» и «последнее» вхождение окажутся случайными — и сохранится не та запись, что нужна.
Проверка результата дедупликации по shape и уникальности ключа
После drop_duplicates() стоит сделать две простые проверки, прежде чем двигаться дальше. Они занимают одну-две строки кода, но дают уверенность, что операция сработала именно так, как ожидалось.
Первая проверка — сравнить число строк до и после:
import pandas as pd
df = pd.DataFrame({
'user_id': [1, 2, 1, 3, 2],
'action': ['login', 'buy', 'login', 'view', 'buy'],
'amount': [0, 100, 0, 50, 200]
})
key_cols = ['user_id', 'action']
df_clean = df.drop_duplicates(subset=key_cols, keep='first')
print(df.shape[0]) # 5 — исходное число строк
print(df_clean.shape[0]) # 3 — после дедупликации
Числа сошлись с ожиданием: было 5 строк, 2 из них — дубли, осталось 3. Если цифра неожиданная, это сигнал проверить subset — возможно, ключевые столбцы заданы не так, как нужно.
Вторая проверка — убедиться, что в очищенном DataFrame действительно нет дублей по ключевым столбцам:
print(df_clean.duplicated(subset=key_cols).sum()) # 0
Если результат не ноль, значит дедупликация не завершила работу — например, из-за опечатки в subset или неожиданного содержимого данных. Здесь используется тот же duplicated() с теми же key_cols, что и при диагностике — никаких новых инструментов.
Обе проверки вместе занимают буквально три строки и дают ответы на два разных вопроса:
shape[0]говорит о том, сколько строк удалено — структурный размер;duplicated(...).sum() == 0говорит о том, что уникальность по ключу достигнута.
Важно понимать, что эти проверки не замещают понимание семантики: они не скажут, правильную ли запись из группы дублей ты сохранил. Если нужна уверенность в содержании — смотри на конкретные строки через фильтрацию. Но структурные проверки выше стоит делать всегда, как финальный шаг дедупликации.
Задачи на очистку данных в pandas. Потренируйтесь выбирать ключ дубликатов и keep="first" или keep="last", очищать текстовые ключи и заполнять пропуски.
Задача 1
DataFrame df содержит 8 строк. После вызова df.duplicated(subset=['city', 'date']).sum() получается 3. Сколько строк будет в df.drop_duplicates(subset=['city', 'date'], keep='first')?
Задача 2
DataFrame df создан так:
import pandas as pd
df = pd.DataFrame({
'a': [1, 2, 1, 3, 2],
'b': ['x', 'y', 'x', 'z', 'w']
})
Что вернёт df.duplicated(subset=['a'], keep=False).tolist()?
Задача 3
keep=False: диагностика всех участников дублей и итоговая дедупликация
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая работает с таблицей событий:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит три токена через пробел:sensor(строка без пробелов),ts(строка видаYYYY-MM-DD),val(целое число). Создаётpd.DataFrameс именемdfи столбцами'sensor','ts','val'. - Шаг A. Строит маску
mask_all = df.duplicated(subset=['sensor', 'ts'], keep=False)— помечает True все строки, участвующие в дублировании (включая первое вхождение). Выводит маску как строку из0/1через пробел (True→1,False→0). - Шаг B. Выводит
dup_count=<k>, гдеk = mask_all.sum()— общее число строк, помеченных True. - Шаг C. Применяет
df.drop_duplicates(subset=['sensor', 'ts'], keep='first'), сохраняет вdf_first. Выводитfirst_shape=<shape>иfirst_unique=<u>, где<u>=df_first.duplicated(subset=['sensor', 'ts']).sum(). - Шаг D. Применяет
df.drop_duplicates(subset=['sensor', 'ts'], keep='last'), сохраняет вdf_last. Выводитlast_shape=<shape>иlast_unique=<u>, где<u>=df_last.duplicated(subset=['sensor', 'ts']).sum(). - Шаг E. Для каждой строки
df_first(в порядке её индексов) выводит одну строку вида<sensor> <val>.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n sensor1 ts1 val1 sensor2 ts2 val2 ...
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
<маска 0/1 через пробел>
dup_count=<k>
first_shape=(<rows>, <cols>)
first_unique=<u>
last_shape=(<rows>, <cols>)
last_unique=<u>
<sensor> <val> (по одной строке для каждой строки df_first)
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
keep='first' vs keep='last': сравнение сохранённых значений после дедупликации
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая сравнивает результаты дедупликации с keep='first' и keep='last' на таблице измерений:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит три токена через пробел:device(строка без пробелов),date(строка видаYYYY-MM-DD),reading(целое число). Создаётpd.DataFrameс именемdfи столбцами'device','date','reading'. - Шаг A. Применяет
df.drop_duplicates(subset=['device', 'date'], keep='first'), сохраняет вdf_first. Выводитfirst_rows=<n>— число строк результата. - Шаг B. Применяет
df.drop_duplicates(subset=['device', 'date'], keep='last'), сохраняет вdf_last. Выводитlast_rows=<n>— число строк результата. - Шаг C. Проверяет уникальность обоих результатов. Выводит
first_dups=<k>иlast_dups=<k>, гдеk— результатdf_first.duplicated(subset=['device', 'date']).sum()и аналогично дляdf_last. - Шаг D. Для каждой пары
(device, date), которая встречалась в исходномdfболее одного раза (в порядке первого появления вdf_firstпо индексу), выводит одну строку вида<device> <date> first=<r1> last=<r2>, гдеr1— значение'reading'изdf_firstдля этой пары,r2— изdf_last.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n device1 date1 reading1 device2 date2 reading2 ...
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
first_rows=<n>
last_rows=<n>
first_dups=<k>
last_dups=<k>
<device> <date> first=<r1> last=<r2>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Найди и исправь: три ошибки в duplicated, drop_duplicates и проверке уникальности
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:
maskстроится черезdf.duplicated(subset=['product'])— проверяется только столбец'product', хотя по условию дублем считается совпадение сразу по паре('client_id', 'product').df_cleanсоздаётся черезdf.drop_duplicates(subset=['client_id', 'product'], keep='last')— хотя по условию нужно сохранить первое вхождение каждой группы (keep='first').- Проверка уникальности сделана через
df_clean.duplicated(subset=['client_id']).sum()— проверяется только'client_id', хотя уникальность нужно проверять по тому же ключу['client_id', 'product'].
Каждая ошибка — в отдельной строке кода. Исправьте все три.
Что нужно сделать
Исправьте три ошибки так, чтобы программа:
- Читала из
stdinцелое числоn— количество строк таблицы. - Читала следующие
nстрок; каждая содержит три токена через пробел:client_id(целое число),product(строка без пробелов),amount(целое число). Создавалаpd.DataFrameс именемdfи столбцами'client_id','product','amount'. - Строила маску
mask = df.duplicated(subset=['client_id', 'product'])— по паре(client_id, product),keep='first'по умолчанию. - Выводила
dup_count=<k>, гдеk = mask.sum(). - Создавала
df_clean = df.drop_duplicates(subset=['client_id', 'product'], keep='first'). - Выводила
clean_shape=<shape>в виде(rows, cols). - Проверяла
uniq = df_clean.duplicated(subset=['client_id', 'product']).sum()и выводилаuniq=<uniq>. - Для каждой строки
df_clean(в порядке исходного индекса) выводила одну строку вида<client_id> <product> <amount>.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n client_id1 product1 amount1 client_id2 product2 amount2 ...
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
dup_count=<k>
clean_shape=(<rows>, <cols>)
uniq=<uniq>
<client_id> <product> <amount>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Карантин дублей: keep=False, подсчёт удалённых строк и список выживших ключей
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая диагностирует дубли в таблице транзакций и отправляет все строки с дублирующимся бизнес-ключом в «карантин»:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит три токена через пробел:client_id(целое число),product(строка без пробелов),amount(целое число). Создаётpd.DataFrameс именемdfи столбцами'client_id','product','amount'. - Шаг A. Строит маску
mask_all = df.duplicated(subset=['client_id', 'product'], keep=False)— помечаетTrueвсе строки, у которых есть хотя бы один дубль по паре(client_id, product), включая первое вхождение. Выводит одну строку:quarantine_count=<k>, гдеk = mask_all.sum(). - Шаг B. Формирует
df_quarantine = df[mask_all]— DataFrame со всеми строками-дублями. Выводитquarantine_shape=<shape>в виде(rows, cols). - Шаг C. Формирует
df_clean = df[~mask_all]— DataFrame из строк, у которых нет ни одного дубля по бизнес-ключу. Выводитclean_count=<m>, гдеm = df_clean.shape[0], иremoved_count=<r>, гдеr = df.shape[0] - df_clean.shape[0]. - Шаг D. Проверяет, что в
df_cleanнет дублей по['client_id', 'product']. Выводитclean_unique=<u>, гдеu = df_clean.duplicated(subset=['client_id', 'product']).sum(). - Шаг E. Для каждой строки
df_clean(в порядке исходного индекса) выводит одну строку вида<client_id> <product>.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n client_id1 product1 amount1 client_id2 product2 amount2 ...
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
quarantine_count=<k>
quarantine_shape=(<rows>, <cols>)
clean_count=<m>
removed_count=<r>
clean_unique=<u>
<client_id> <product>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Дубли: duplicated и dropduplicates по ключевым столбцам — задача по NumPy и pandas
dropduplicates(subset=['city', 'date'], keep='first')? Проверьте ответ в задаче из бесплатного курса NumPy и pandas.
- pandas: удаление дубликатов — subset, keep first и last
Как удалить дубликаты по выбранным столбцам pandas и сохранить первую или последнюю строку? Рабочий пример и задача с проверкой.
- pandas duplicated keep=False: найти все повторяющиеся ключи
Как отметить все дубликаты pandas через duplicated(subset=..., keep=False)? Пример маски и задача на исключение повторных ключей.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
