Merge в Pandas: шпаргалка для собеседования

Зачем аналитику уверенность в merge

Merge в pandas — прямой аналог JOIN в SQL, но с нюансами, которые ловят на собеседовании. Чаще всего — дублирование строк, неожиданный NaN и путаница между merge / join / concat.

Базовый синтаксис

import pandas as pd

result = df1.merge(df2, on='key', how='inner')
  • on — общий столбец-ключ.
  • how — тип объединения: 'inner' (по умолчанию), 'left', 'right', 'outer', 'cross'.

Типы how

inner (по умолчанию)

Только совпадения:

users = pd.DataFrame({'user_id': [1, 2, 3], 'name': ['a', 'b', 'c']})
orders = pd.DataFrame({'user_id': [1, 2, 2, 5], 'amount': [100, 200, 50, 300]})

users.merge(orders, on='user_id', how='inner')
# user 1: 1 row, user 2: 2 rows, user 3 и 5: дропнуты

left

Все строки левой + совпадения справа:

users.merge(orders, on='user_id', how='left')
# user 3 будет с amount=NaN

outer

Все строки обеих + NaN где нет совпадений:

users.merge(orders, on='user_id', how='outer')
# user 3: amount=NaN, user 5: name=NaN

cross

Декартово произведение (pandas 1.2+):

df1.merge(df2, how='cross')

on vs left_on/right_on

# Если столбцы называются одинаково
df1.merge(df2, on='user_id')

# Если по-разному
df1.merge(df2, left_on='user_id', right_on='id')

# По нескольким ключам
df1.merge(df2, on=['user_id', 'date'])

Тренироваться на таких вопросах можно в Telegram-боте Карьерник — там 1500+ задач с реальных собесов с разборами.

indicator=True

Добавляет колонку _merge со значениями left_only, right_only, both:

result = df1.merge(df2, on='key', how='outer', indicator=True)

# Посмотреть расхождения
print(result['_merge'].value_counts())

Полезно для сверки данных между источниками — видим, что есть только в левой, только в правой, в обеих.

validate — защита от неожиданных дублей

df1.merge(df2, on='user_id', validate='one_to_one')  # 1:1
df1.merge(df2, on='user_id', validate='one_to_many') # 1:n
df1.merge(df2, on='user_id', validate='many_to_one') # n:1
df1.merge(df2, on='user_id', validate='many_to_many') # n:m

Бросает ошибку, если данные не соответствуют ожиданию. Золотая защита от бага: если по бизнес-логике у user должен быть один заказ, но кто-то залил дубли — validate это поймает.

Суффиксы при одинаковых именах

df1.merge(df2, on='user_id', suffixes=('_left', '_right'))
# Если у обеих был столбец name — станут name_left и name_right

merge vs join vs concat

Функция Для чего
merge JOIN по ключу (SQL-way)
join Как merge, но по индексу
concat Стэкование таблиц (vertical/horizontal append)

join vs merge

# merge — по столбцу
df1.merge(df2, on='user_id')

# join — по индексу
df1.set_index('user_id').join(df2.set_index('user_id'))

concat — append / stack

# Склеить вертикально (append rows)
pd.concat([df1, df2], axis=0)

# Склеить горизонтально (side-by-side)
pd.concat([df1, df2], axis=1)

concat НЕ объединяет по ключу — просто стэкует. Если нужно JOIN по ключу — merge.

merge_asof — самая мощная функция

«Ближайший ключ» merge, идеален для time-series:

trades = pd.DataFrame({
    'time': pd.to_datetime(['10:00:00', '10:00:05', '10:00:10']),
    'price': [100, 101, 102]
})
quotes = pd.DataFrame({
    'time': pd.to_datetime(['10:00:00', '10:00:03', '10:00:08']),
    'bid': [99, 100, 101]
})

pd.merge_asof(trades, quotes, on='time', direction='backward')
# Для каждой строки trades найдёт ближайшую quote <= time

Практика: сопоставить события с последней известной ценой/курсом.

К слову, набить руку на таких кейсах удобно через тренажёр в Telegram — разбирайте по 10 вопросов в день, через 2 недели тема становится рефлексом.

Частые ловушки

1. Дублирование строк

# Если в df2 несколько строк с одним user_id
# результат растянется
df1.merge(df2, on='user_id')  # каждая строка df1 продублируется

Решение — агрегировать df2 до merge или использовать validate='one_to_one'.

2. Неочевидный NaN при left join

users.merge(orders, on='user_id', how='left')
# Для пользователей без заказов — NaN в amount
# SUM проигнорирует их, AVG сдвинется

Решение: fillna(0) где уместно.

3. Разные типы ключа

# user_id в одной таблице int, в другой str — merge не сматчит
df1['user_id'] = df1['user_id'].astype(int)
df2['user_id'] = df2['user_id'].astype(int)

Самая тупая ошибка, которую все совершали. Проверяйте dtypes.

10 задач с собесов

1. Обогатить заказы именем пользователя

orders.merge(users[['user_id', 'name']], on='user_id', how='left')

2. Найти пользователей без заказов

result = users.merge(orders, on='user_id', how='left', indicator=True)
result[result['_merge'] == 'left_only']

3. Заказы с суммой из items

order_totals = order_items.groupby('order_id')['price'].sum().reset_index()
orders.merge(order_totals, on='order_id', how='left')

4. Сверить две таблицы и найти расхождения

merged = source_a.merge(source_b, on='id', how='outer',
                         indicator=True, suffixes=('_a', '_b'))
diffs = merged[merged['_merge'] != 'both']

5. JOIN по двум ключам

df1.merge(df2, on=['user_id', 'date'])

6. SELF JOIN: найти пары из одной таблицы

df1 = df.copy()
df1['join_key'] = 1
df2 = df.copy()
df2['join_key'] = 1
pairs = df1.merge(df2, on='join_key').query('user_id_x != user_id_y')

Или через merge(on=, how='cross') в pandas 1.2+.

7. Обогатить продажу курсом валюты за тот же день (merge_asof)

pd.merge_asof(sales.sort_values('date'),
              rates.sort_values('date'),
              on='date', direction='backward')

8. Объединить несколько источников

from functools import reduce
dfs = [users, orders_agg, profile, activity]
result = reduce(lambda l, r: l.merge(r, on='user_id', how='left'), dfs)

9. Антиjoin: клиенты без премиума

all_users.merge(premium_users, on='user_id', how='left', indicator=True)
.query('_merge == "left_only"')

10. Проверить 1:1 связь

df1.merge(df2, on='id', validate='one_to_one')
# Бросит ошибку, если не 1:1

Как тренироваться

Merge учится через реальные датасеты с тремя-четырьмя таблицами. Синтаксис простой, ловушки — нет. Нужно чувствовать, когда использовать left, когда inner, когда валидировать.

Тренажёр Карьерник содержит блок задач на merge: типы how, validate, merge_asof, антиjoin. С разборами всех ловушек.

Совет: всегда ставьте validate при merge критичных данных. Это автоматический assert, который ловит баги данных до того, как они доедут до дашборда.

Читайте также

FAQ

Merge или join в pandas?

merge — универсальнее, по любому столбцу. join — по индексу. На практике merge используется в 90% случаев. join имеет смысл, когда у вас настроены индексы и хочется короче.

Когда concat вместо merge?

Concat — когда нужно stack таблицы: объединить логи за разные дни, склеить train и test. Merge — когда обогащаете данные из одной таблицы другой.

Как убедиться, что merge не продублирует строки?

Используйте validate='one_to_one' или 'many_to_one'. Или до merge проверьте, что правая таблица уникальна: df2['key'].is_unique.

Что делать, если после merge появились NaN?

Это либо ожидаемо (left join без совпадения), либо баг. Проверьте _merge через indicator=True. Если NaN только в new columns — скорее всего left_only, что значит — в правой нет этих ключей.