concat, join по индексу и сравнение с SQL

Кроме merge есть concat для простой склейки таблиц и join для соединения по индексу — у каждого своя ниша.

concat складывает таблицы стопкой (по строкам) или бок о бок (по столбцам). join соединяет по индексу, как merge по ключу.

concat: склейка по строкам

Когда у вас несколько таблиц одинаковой структуры (выгрузки за разные месяцы, части одного датасета), их нужно просто поставить друг под друга. Это concat с axis=0 (по умолчанию):

import pandas as pd
all_data = pd.concat([jan, feb, mar])           # стопкой, по строкам
all_data = pd.concat([jan, feb, mar], ignore_index=True)  # перенумеровать индекс
all_data = pd.concat({"янв": jan, "фев": feb}, names=["месяц"])  # пометить источник

ignore_index=True важен: без него склеенная таблица сохранит исходные индексы, и они начнут повторяться (три раза по 0, 1, 2…). keys= (или словарь) добавляет верхний уровень индекса, помечающий, из какой таблицы пришла строка.

Идея склейки по строкам элементарна — это конкатенация списков записей:

jan = [{"товар": "мышь", "шт": 3}, {"товар": "кабель", "шт": 10}]
feb = [{"товар": "монитор", "шт": 1}]

all_rows = jan + feb                 # concat по строкам = склейка
for i, r in enumerate(all_rows):     # ignore_index: новая нумерация 0..n
    print(i, r["товар"], r["шт"])

Вывод:

0 мышь 3
1 кабель 10
2 монитор 1

В pandas pd.concat([jan, feb], ignore_index=True) делает ровно это: ставит строки одну под другую и присваивает сквозной индекс 0..n.

concat по столбцам

С axis=1 таблицы ставятся бок о бок, выравниваясь по индексу строк (вспомните главную идею pandas). Это удобно, когда у вас два набора столбцов с общим индексом:

pd.concat([prices, stock], axis=1)   # столбцы рядом, выравнивание по индексу

Если индексы не совпадают, в местах рассогласования появятся NaN — это не баг, а выравнивание. По смыслу concat(axis=1) близок к outer join по индексу.

Когда брать concat(axis=1), а когда merge? concat по столбцам уместен, когда таблицы уже выровнены по индексу и вы просто хотите поставить их рядом — например, посчитали несколько Series по одному и тому же индексу и собираете их в один DataFrame. Если же соединять надо по значению столбца (id клиента), а индексы произвольные — это работа для merge, а не concat. Грубо: concat — «склей по позиции/метке индекса как есть», merge — «найди соответствие по ключу и сопоставь». Путать их — частая причина неожиданных NaN.

verify_integrity: защита от дублей при склейке

У concat есть полезный параметр verify_integrity=True: он проверяет, что в итоговом индексе нет повторов, и падает с ошибкой, если они появились. Это аналог validate у merge — дешёвая страховка, когда вы ожидаете уникальный индекс после склейки:

pd.concat([a, b], verify_integrity=True)  # упадёт, если индексы пересеклись

join: соединение по индексу

join — это merge, но соединяющий по индексу, а не по столбцу-ключу (по умолчанию). Удобен, когда естественный ключ уже стоит индексом обеих таблиц:

clients = clients.set_index("client_id")
orders.join(clients, on="client_id", how="left")  # left.column ↔ right.index

По сути orders.join(clients)orders.merge(clients, left_on=..., right_index=True). Если у обеих таблиц ключ в индексе — join короче и читаемее.

Когда что использовать

ЗадачаИнструмент
Поставить таблицы одной структуры стопкойconcat(axis=0)
Поставить столбцы рядом по общему индексуconcat(axis=1)
Соединить по столбцу-ключуmerge
Соединить по индексуjoin

Прямое соответствие SQL

Операции pandas — это реляционная алгебра с другим синтаксисом. Сравним на живой SQLite-песочнице: тот же left join.

CREATE TABLE orders (id INTEGER, client_id INTEGER);
INSERT INTO orders VALUES (1, 10), (2, 20), (3, 99);

CREATE TABLE clients (client_id INTEGER, name TEXT);
INSERT INTO clients VALUES (10, 'Аня'), (20, 'Боря'), (30, 'Вера');

SELECT o.id, o.client_id, c.name
FROM orders o
LEFT JOIN clients c ON o.client_id = c.client_id
ORDER BY o.id;

Вывод:

1|10|Аня
2|20|Боря
3|99|

Это в точности результат orders.merge(clients, on="client_id", how="left") из прошлого урока: заказ 3 остался, но имя пустое (в SQL — NULL, в pandas — NaN). Таблица соответствий:

SQLpandas
JOIN ... ONmerge(on=...)
LEFT JOINmerge(how="left")
UNION ALLconcat(axis=0)
GROUP BYgroupby
WHEREdf[маска] / query

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

  • Повторяющийся индекс после concat. Без ignore_index=True метки начнут дублироваться, что ломает дальнейший loc и слияния.
  • concat(axis=1) и рассогласованный индекс. Несовпадение меток строк даёт NaN; убедитесь, что индексы сопоставимы.
  • concat не проверяет ключи. В отличие от merge с validate, concat просто склеивает — следите за структурой сами.
  • Разные столбцы при concat по строкам. Если у таблиц разный набор столбцов, недостающие заполнятся NaN — иногда это сюрприз.

Лучшие практики

  • Склеиваете однотипные таблицы — concat(axis=0, ignore_index=True).
  • Соединяете по ключу-столбцу — merge; по индексу — join.
  • Помечайте источник строк через keys=, если важно знать, откуда они.
  • Думаете в терминах SQL — переносите интуицию JOIN/UNION напрямую на merge/concat.

Итог

  • concat(axis=0) — стопка таблиц (как UNION ALL); используйте ignore_index.
  • concat(axis=1) — столбцы рядом с выравниванием по индексу.
  • join соединяет по индексу, merge — по столбцу-ключу.
  • Операции pandas прямо соответствуют SQL JOIN/UNION/GROUP BY/WHERE.
Проверьте себя
1. Зачем при pd.concat([df1, df2]) обычно указывают ignore_index=True?
AЧтобы ускорить склейку
BЧтобы избежать повторяющихся меток индекса и получить сквозную нумерацию 0..n
CЧтобы удалить дубликаты строк
DЧтобы соединить по ключу
2. Чем join отличается от merge по умолчанию?
Ajoin работает только с числами
Bjoin соединяет по индексу, а merge — по столбцу-ключу
Cjoin всегда делает inner
DОни полностью идентичны
3. Какой операции SQL соответствует pd.concat([a, b], axis=0)?
AJOIN
BGROUP BY
CUNION ALL
DWHERE
Поддержать проект