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) и их размножение (дубли ключа).
Проверьте себя
1. Какой how сохранит все строки левой таблицы и подтянет правую, ставя NaN там, где пары нет?
Aleft
Binner
Cright
Douter
2. Зачем нужен параметр validate в merge?
AЧтобы ускорить соединение
BЧтобы проверить ожидаемую кратность связи и поймать неожиданные дубликаты ключа
CЧтобы автоматически удалить NaN
DЧтобы выбрать тип join
3. После inner merge число строк неожиданно уменьшилось. Вероятная причина?
Avalidate сработал неверно
Bmerge всегда уменьшает число строк
CЧасть ключей не нашла пары, и inner молча выбросил такие строки
DПроизошёл сбой памяти