merge: объединение таблиц по ключу
merge соединяет две таблицы по общему ключу — это прямой аналог SQL JOIN и центральная операция при работе с несколькими источниками.
merge сопоставляет строки двух DataFrame по значениям ключевых столбцов и склеивает совпавшие строки в одну.
Зачем нужен merge
Данные почти никогда не лежат в одной таблице. Заказы в одной, клиенты в другой, товары в третьей. Чтобы посчитать «выручку по регионам клиентов», надо соединить заказы с клиентами по client_id. Это и есть merge: «найди для каждой строки левой таблицы соответствующие строки правой по ключу и склей».
import pandas as pd
merged = pd.merge(orders, clients, on="client_id", how="left")
# или метод: orders.merge(clients, on="client_id", how="left")
Четыре типа соединения (how)
Параметр how решает, какие строки попадут в результат, когда ключи совпадают не полностью:
| how | Что остаётся |
inner (по умолчанию) | только ключи, есть в обеих таблицах |
left | все строки левой; недостающее справа → NaN |
right | все строки правой; недостающее слева → NaN |
outer | объединение ключей обеих таблиц; пробелы → NaN |
На практике чаще всего нужен left: «оставить все заказы и подтянуть данные клиента, если он есть». inner молча выбрасывает строки без пары — это удобно, но опасно, если вы не ожидали потери. outer ничего не теряет и хорош для сверки «что есть где».
Выбор how — это решение о том, какая таблица «главная». При left главная левая: её строки священны, правая лишь дополняет. Это нужно, когда левая таблица — ваш основной набор (все заказы), а правая — справочник (клиенты), и потерять заказ нельзя, даже если клиент не найден. inner уместен, когда вам нужны только полные пары (заказы с известным клиентом). outer — для аудита: «покажи всё из обеих таблиц, в том числе то, что не сошлось». Неправильный выбор how — самая частая логическая ошибка при объединении, и она тихая: код отработает, но данные будут не те.
Модель соединения на чистом Python
Под капотом merge строит индекс по ключу правой таблицы и для каждой строки левой ищет совпадение. Сделаем left join руками:
orders = [
{"order": 1, "client_id": 10},
{"order": 2, "client_id": 20},
{"order": 3, "client_id": 99}, # клиента 99 нет в справочнике
]
clients = {10: "Аня", 20: "Боря", 30: "Вера"}
# LEFT JOIN: для каждой строки orders ищем клиента по ключу
for o in orders:
name = clients.get(o["client_id"]) # нет совпадения → None (NaN)
print(o["order"], o["client_id"], name if name is not None else "NaN")
Вывод:
1 10 Аня 2 20 Боря 3 99 NaN
Все три заказа сохранились (left), но у заказа 3 имя клиента — NaN, потому что клиента 99 нет в справочнике. Клиент 30 (Вера) в результат не попал — у него нет заказов, а left join идёт «от левой таблицы». Если бы мы делали inner, заказ 3 тоже выпал бы; outer добавил бы строку с Верой и пустым заказом.
Ключи с разными именами: left_on / right_on
Если ключ называется по-разному в двух таблицах, on не подойдёт — используйте left_on/right_on:
orders.merge(clients, left_on="client_id", right_on="id", how="left")
validate: защита от неожиданных дубликатов
Самая коварная ошибка merge — размножение строк из-за дубликатов ключа. Если в правой таблице ключ не уникален, каждая строка левой склеится с каждым совпадением справа, и число строк неожиданно вырастет. Параметр validate ловит это сразу, проверяя кратность связи:
# упадёт с ошибкой, если связь НЕ один-ко-многим (т.е. справа есть дубли ключа)
orders.merge(clients, on="client_id", how="left", validate="many_to_one")
Значения validate: "one_to_one", "one_to_many", "many_to_one", "many_to_many". Объявив ожидаемую кратность, вы превращаете тихий баг «строк стало в 3 раза больше» в явную ошибку при разработке. Это одна из самых полезных привычек.
Почему «взрыв строк» так коварен? Представьте: вы соединяете заказы со справочником клиентов, но в справочнике клиент 10 случайно продублирован (две строки). Тогда каждый заказ клиента 10 склеится с обеими строками справочника и удвоится. Если дальше вы суммируете выручку, она тоже удвоится — и отчёт молча соврёт. Никакой ошибки не будет, просто числа окажутся завышены. validate="many_to_one" ловит это сразу: вы заявили «много заказов на одного клиента», и pandas проверит, что справа ключ действительно уникален. Привычка декларировать кратность спасает от целого класса незаметных искажений в аналитике.
indicator: откуда пришла строка
Параметр indicator=True добавляет служебный столбец _merge со значениями left_only, right_only, both — мгновенно видно, какие ключи нашли пару, а какие нет. Незаменимо для сверки данных:
res = orders.merge(clients, on="client_id", how="outer", indicator=True)
res[res["_merge"] == "left_only"] # заказы без клиента в справочнике
res[res["_merge"] == "right_only"] # клиенты без единого заказа
Суффиксы при совпадении имён
Если в обеих таблицах есть одноимённый неключевой столбец (например, value), pandas добавит к ним суффиксы _x и _y. Лучше задать осмысленные через suffixes:
a.merge(b, on="id", suffixes=("_orders", "_clients"))
Подводные камни
- Молчаливая потеря строк при inner. Если ожидали все строки, а ключ совпал не везде,
innerтихо их выбросит. Проверяйтеshapeдо и после. - Взрыв числа строк из-за дублей ключа. Дубликаты справа размножают строки слева. Спасает
validateи предварительная проверка уникальности ключа. - Несовпадение типов ключа. Ключ
intв одной таблице иstr(«10» против 10) в другой не сольётся — приведите типы заранее. - NaN не сливаются как обычные значения. Пропуски в ключе не сопоставляются друг с другом; чистите ключ до merge.
Лучшие практики
- Всегда указывайте
howявно — не полагайтесь наinnerпо умолчанию. - Используйте
validate=— это дешёвая страховка от размножения строк. - Проверяйте
df.shapeдо и после merge; неожиданный рост числа строк — сигнал о дублях ключа. - Для сверки «кто с кем не сошёлся» применяйте
indicator=True. - Приводите тип ключа к одному и тому же в обеих таблицах перед соединением.
Итог
mergeсоединяет таблицы по ключу;howзадаёт inner/left/right/outer.leftсохраняет все строки левой и подтягивает правую (частый случай).validateловит неожиданные дубликаты ключа,indicatorпоказывает источник строки.- Главные риски — потеря строк (inner) и их размножение (дубли ключа).