Урок по треку Python для анализа данных 7 мин

merge, join и concat в pandas

Типы соединений, ключи, размножение строк, validate, indicator, concat по осям.

Данные почти никогда не лежат в одной таблице: заказы отдельно, клиенты отдельно, справочник городов отдельно. Склеить их правильно и не размножить строки по дороге: одна из главных проверок на собесе аналитика.

merge: соединение по колонкам

pd.merge(left, right, on=..., how=...) работает как SQL JOIN.

import pandas as pd

orders = pd.DataFrame({'order_id': [1, 2, 3], 'user_id': [10, 20, 30], 'amount': [500, 300, 900]})
users = pd.DataFrame({'user_id': [10, 20, 40], 'city': ['Москва', 'Казань', 'Пермь']})

orders.merge(users, on='user_id', how='left')
#    order_id  user_id  amount    city
# 0         1       10     500  Москва
# 1         2       20     300  Казань
# 2         3       30     900     NaN

Значения how:

howЧто остаётся
inner (по умолчанию)только ключи, которые есть в обеих таблицах
leftвсе строки левой таблицы
rightвсе строки правой
outerвсе ключи из обеих
crossдекартово произведение, ключ не нужен

Если ключи называются по-разному, используйте left_on и right_on. Если ключ в индексе, есть left_index=True и right_index=True.

Размножение строк

Если в правой таблице ключ повторяется, каждая строка слева склеится с каждым совпадением справа. Было 1000 заказов, после merge со справочником, где у клиента две записи (например, два адреса), стало 1300. Суммы выручки после этого завышены.

Защита: параметр validate.

orders.merge(users, on='user_id', how='left', validate='many_to_one')

Если в users ключ не уникален, pandas выбросит MergeError. Варианты: 'one_to_one', 'one_to_many', 'many_to_one', 'many_to_many'. Хорошая привычка: всегда проверять число строк до и после соединения.

indicator: откуда пришла строка

m = orders.merge(users, on='user_id', how='outer', indicator=True)
m['_merge'].value_counts()
# both, left_only, right_only

Так удобно искать «заказы без клиента» или «клиентов без заказов». Анти-джойн (строки слева без пары справа) делают так: outer или left с indicator=True, потом фильтр _merge == 'left_only'. Ещё проще для одного ключа: orders[~orders['user_id'].isin(users['user_id'])].

Одинаковые имена колонок

Если в обеих таблицах есть колонка name, которая не входит в ключ, pandas добавит суффиксы name_x и name_y. Их можно задать: suffixes=('_order', '_user').

Типы ключей

Ключ user_id в одной таблице int64, а в другой строка '10': pandas не приведёт их сам. В современных версиях вы получите ошибку о несовместимых типах ключей. Если оба ключа строки, но с пробелами или в разном регистре, соединение молча вернёт пустоту. Перед merge проверьте dtypes и почистите ключи.

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

left.join(right) по умолчанию соединяет по индексу и делает left join. Удобно, когда справочник уже проиндексирован ключом:

users_idx = users.set_index('user_id')
orders.join(users_idx, on='user_id')

join это обёртка над merge с другими умолчаниями, принципиально нового он не делает.

concat: склейка по оси

pd.concat не ищет совпадений по ключу, он ставит таблицы друг под друга (axis=0) или рядом (axis=1, с выравниванием по индексу).

jan = pd.DataFrame({'user_id': [1, 2], 'amount': [10, 20]})
feb = pd.DataFrame({'user_id': [3], 'amount': [30]})
pd.concat([jan, feb], ignore_index=True)

Без ignore_index=True индексы сохранятся и повторятся: 0, 1, 0. Колонки, которых нет в одной из таблиц, заполнятся NaN.

Добавлять строки по одной в цикле через concat медленно: каждый вызов копирует всё. Соберите список словарей или DataFrame и сделайте один concat в конце. Метода DataFrame.append в pandas 2.0+ больше нет.

merge_asof: ближайшее по времени

Когда нужно найти для каждого события последний известный курс или статус на момент события, обычный merge не подходит: точного совпадения времени нет. Для этого есть pd.merge_asof(left, right, on='ts', by='user_id'), обе таблицы должны быть отсортированы по on.

Частые ошибки

  • Не проверили уникальность ключа справа, и строки размножились.
  • inner по умолчанию: молча потеряли заказы без клиента.
  • Фильтр по колонке правой таблицы после left merge выкинул строки с NaN, и left фактически стал inner.
  • Разные типы ключей или грязные строки.
  • concat в цикле.

Что спросят на собесе

  • Чем отличаются inner, left, outer, cross?
  • Почему после merge строк стало больше и как это предотвратить?
  • Как найти строки левой таблицы без пары в правой?
  • Чем merge, join и concat отличаются друг от друга?
  • Как соединить события с последним известным значением по времени?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.