Нашли ошибку?
Скачать чистый
урок 2 из 4≈ 20 минупражнения: 0 из 4

Соединить по ключу

merge соединяет две таблицы по общему столбцу-ключу: к каждой строке заказа подставляет данные о товаре или покупателе. Виды соединения inner и left, строки без пары и проверка числа строк.

Python запустится при первом выполнении

До сих пор мы работали с таблицей shop_orders.csv, где в каждой строке уже записаны и город покупателя, и название товара, и цена. Настоящие базы данных устроены иначе: сведения хранятся в разных таблицах без повторов. Название и цена товара записаны один раз — в справочнике товаров, а в заказах стоит только его код.

[ ]Заказы и справочник товаров
import pandas as pd

orders = pd.read_csv("data/orders.csv")
products = pd.read_csv("data/products.csv")
print(orders.head(3))
print(products.head(3))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
   order_id        date customer_id product_id  quantity      channel
0     10001  2025-01-01        C095        P18         2  маркетплейс
1     10001  2025-01-01        C095        P09         1  маркетплейс
2     10002  2025-01-01        C059        P10         1   приложение
  product_id                 name category  price  cost
0        P01  Эспрессо-смесь 1 кг     Кофе   1450   870
1        P02       Колумбия 250 г     Кофе    690   380
2        P03        Эфиопия 250 г     Кофе    790   430

В заказах нет цены — есть product_id. В справочнике нет продаж — есть product_id, цена и себестоимость. Чтобы посчитать выручку, нужно к каждой строке заказа подставить строку справочника с тем же кодом. Общий столбец, по которому сопоставляются строки, называют ключом.

merge на маленьком примере

Две таблицы с общим столбцом id:

[ ]Соединение по id
left = pd.DataFrame({"id": [1, 2, 3], "name": ["Аня", "Борис", "Вика"]})
right = pd.DataFrame({"id": [2, 3, 4], "city": ["Тула", "Сочи", "Омск"]})
left.merge(right, on="id")
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
id name city
0 2 Борис Тула
1 3 Вика Сочи

Метод merge взял каждую строку левой таблицы, нашёл в правой строку с тем же id и склеил их в одну. В результате остались только ключи 2 и 3 — те, что есть в обеих таблицах. Ани (id 1) в правой таблице нет, Омска (id 4) нет в левой — и обе строки пропали. Такое соединение называется внутренним (inner), и оно используется по умолчанию.

Параметр how («как») меняет правило. how="left" сохраняет все строки левой таблицы; где пары не нашлось, ставит пропуск:

[ ]Все строки левой таблицы
left.merge(right, on="id", how="left")
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
id name city
0 1 Аня NaN
1 2 Борис Тула
2 3 Вика Сочи

Остальные варианты — "right" (все строки правой) и "outer" (все строки обеих):

[ ]Все строки обеих таблиц
left.merge(right, on="id", how="outer")
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
id name city
0 1 Аня NaN
1 2 Борис Тула
2 3 Вика Сочи
3 4 NaN Омск
Вопрос

Сколько строк будет в left.merge(right, on="id", how="right")?

Все четыре вида на одной схеме:

Заказы и товары

[ ]К каждой строке заказа — название товара
named = orders.merge(products[["product_id", "name"]], on="product_id")
print(len(orders), len(named))
named.head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
2448 2448
order_id date customer_id product_id quantity channel name
0 10001 2025-01-01 C095 P18 2 маркетплейс Кофемолка ручная
1 10001 2025-01-01 C095 P09 1 маркетплейс Травяной сбор 50 г
2 10002 2025-01-01 C059 P10 1 приложение Шоколад горький

Из справочника мы взяли два столбца — ключ и название (зачем брать только нужные — ниже, в разделе «Брать только нужное»). В orders один и тот же товар встречается в сотнях строк, в products — один раз. merge подставил название к каждой строке заказа с этим кодом. Такое соединение называют «многие к одному» — самый частый случай.

Число строк не изменилось: было 2448 — и стало 2448. Это первое, что проверяют после соединения.

[ ]Упражнение 1Выручка через справочник решено

Соедините orders со справочником products по ключу product_id и сохраните в priced. Добавьте столбец revenue — цена × количество. В total сохраните выручку за год.

priced = ...
# добавьте в priced столбец revenue
total = ...

Сумма сошлась с той, что мы считали по готовой таблице: 3 301 420 ₽. Но теперь у нас есть то, чего в shop_orders.csv не было, — себестоимость.

Пояснение с ответом появится, когда решите упражнение «Выручка через справочник».
[ ]Упражнение 2Прибыль решено

Добавьте в priced столбец profit — прибыль строки: разность цены и себестоимости (cost), умноженная на количество. Сохраните в category_profit прибыль по категориям по убыванию, в total_profit — прибыль за год.

# добавьте в priced столбец profit
category_profit = ...
total_profit = ...

Строки без пары

Теперь покупатели. В справочнике customers.csv — 240 клиентов. Посчитаем, сколько заказов сделал каждый:

[ ]Справочник клиентов и число заказов
customers = pd.read_csv("data/customers.csv")
order_counts = orders.groupby("customer_id", as_index=False).agg(n_orders=("order_id", "nunique"))
print(len(customers), len(order_counts))
order_counts.head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
240 213
customer_id n_orders
0 C001 6
1 C002 6
2 C005 8

Клиентов 240, а в таблице с числом заказов только 213 строк: остальные ничего не покупали в 2025 году, и в заказах их просто нет. Что будет при соединении?

[ ]inner и left
print(len(customers.merge(order_counts, on="customer_id")))
print(len(customers.merge(order_counts, on="customer_id", how="left")))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
213
240

Внутреннее соединение молча выбросило 27 клиентов. Ошибки нет, таблица выглядит нормально — и «среднее число заказов на клиента», посчитанное по ней, окажется завышенным.

С how="left" клиенты без заказов остаются, а в столбце n_orders у них пропуск:

[ ]У клиента C003 заказов нет
activity = customers.merge(order_counts, on="customer_id", how="left")
activity.head(4)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
customer_id name city signup_date segment n_orders
0 C001 Сергей Ю. Екатеринбург 2025-06-12 постоянный 6.0
1 C002 Сергей В. Екатеринбург 2025-01-14 новый 6.0
2 C003 Максим С. Екатеринбург 2025-05-19 новый NaN
3 C004 Андрей Д. Москва 2023-09-15 новый NaN

Из-за пропусков столбец стал дробным. Здесь пропуск значит «ноль заказов» — его можно и нужно заполнить.

[ ]Упражнение 3Спящие клиенты решено

В таблице activity замените пропуски в столбце n_orders нулями и переведите столбец в целый тип. Сохраните в sleepers клиентов без единого заказа, в n_sleepers — их число.

# замените пропуски в activity["n_orders"] нулями и сделайте столбец целым
sleepers = ...
n_sleepers = ...

Брать только нужное

Соединять можно и несколько раз подряд. Добавим к заказам с товарами сегмент покупателя. Из справочника клиентов возьмём только два столбца — ключ и то, что нужно:

[ ]Третья таблица — только ключ и сегмент
segments = customers[["customer_id", "segment"]]
with_segment = priced.merge(segments, on="customer_id")
print(with_segment.shape)
with_segment[["order_id", "name", "revenue", "segment"]].head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
(2448, 13)
order_id name revenue segment
0 10001 Кофемолка ручная 6400 новый
1 10001 Травяной сбор 50 г 260 новый
2 10002 Шоколад горький 180 постоянный

Так результат не засоряется лишними столбцами. Есть и вторая причина: в обеих таблицах есть столбец name (название товара и имя клиента) — что происходит при совпадении названий, разберём в следующем уроке.

[ ]Упражнение 4Сегменты покупателей решено

По таблице with_segment соберите segment_stats — таблицу по сегментам (сегмент в индексе): revenue — выручка, buyers — число разных покупателей. Добавьте столбец per_buyer — выручка на одного покупателя, округлённая до рублей. В best_segment сохраните сегмент с наибольшим per_buyer.

segment_stats = ...
# добавьте в segment_stats столбец per_buyer
best_segment = ...

Оптовых покупателей всего 25, но каждый в среднем приносит 30 тысяч рублей в год — вдвое больше остальных.

Пояснение с ответом появится, когда решите упражнение «Сегменты покупателей».

Итоги

  • левая.merge(правая, on="ключ") — к каждой строке левой таблицы подставить строку правой с тем же ключом.
  • how="inner" (по умолчанию) — только совпавшие ключи; how="left" — все строки левой, без пары — NaN.
  • После соединения сравните число строк с исходным.
  • Берите из правой таблицы только нужные столбцы: правая[["ключ", "столбец"]].

Подробнее в справочнике

Нашли ошибку?