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