Към съдържанието
wannacode

Съединяване: merge

Накратко: merge в pandas съединява две таблици по общ ключ, както JOIN в SQL, а how решава кои редове остават.

Проблем

„Коя категория носи най-много пари?“

Управителят пита коя категория продава най-много. Редовете на поръчките казват колко бройки от кой продукт са купени и на каква цена:

Pythonданни: таблиците на магазина

Категория обаче там няма. Има само product_id, номерът на продукта. Името и категорията са в products.csv, а името на категорията е в трета таблица, categories.csv.

Данните в магазин почти винаги са разделени така: всяко нещо е записано на едно място, а другите таблици сочат към него с номер. За отговора трябва да слепиш таблиците в една.

Идеята

Слепване по ключ: merge

merge взима всеки ред от лявата таблица, търси в дясната реда със същата стойност в колоната ключ и слепва двата реда в един. Това се казва съединяване (join), а колоната, по която се търси, е ключ. В SQL същото е JOIN.

Тук ключът се казва различно в двете таблици: product_id в редовете, id в продуктите. left_on и right_on казват коя колона от коя страна е:

Pythonданни: таблиците на магазина

Редовете остават 152, а колоните стават 10: четирите от items и шестте от products. Колоните product_id и id са едно и също число, вече два пъти.

Затова по-често първо преименуваш ключа, за да се казва еднакво, и съединяваш с on. Взимаш и само колоните, които ти трябват:

Pythonданни: таблиците на магазина

Виж го

Как merge намира двойките

Ето същото на малки таблици: 5 реда от поръчки и ценоразпис с 4 реда. В ценоразписа кафето (17) е два пъти, със старата и с новата цена, а продукт 26 още го няма. С how="left" всеки ред от поръчките остава:

Pythonданни: таблиците на магазина

import pandas as pd
lines = pd.DataFrame({
'order_id': [7, 7, 8, 8, 9],
'product_id': [1, 17, 5, 1, 26],
'quantity': [2, 1, 3, 1, 2],
})
catalog = pd.DataFrame({
'product_id': [1, 5, 17, 17],
'name': ['Бял хляб', 'Прясно мляко', 'Кафе на зърна', 'Кафе на зърна'],
'price': [1.2, 1.6, 13.9, 14.9],
})
both = lines.merge(catalog, on='product_id', how='left')
print(len(lines), len(both))
both
Стъпка по стъпка, с думи
  1. lines са 5 реда от поръчки. product_id казва кой продукт е купен.
  2. catalog е ценоразписът: 4 реда. Продукт 17 е в него два пъти, със старата и с новата цена.
  3. Ред 1: product_id 1. merge търси всички редове с 1 в catalog: Бял хляб. Двойката става ред в both.
  4. Ред 2: product_id 17 е два пъти в catalog, с 13,90 € и 14,90 €. Двете двойки стават два реда.
  5. Ред 3: product_id 5, това е Прясно мляко.
  6. Ред 4: product_id 1, пак Бял хляб. Един ред от catalog може да е двойка на много редове.
  7. Ред 5: продукт 26 го няма в catalog. С how='left' редът остава, а name и price са NaN.
  8. 5 реда влязоха, 6 излязоха: повтореният ключ удвои реда. Затова броиш len преди и след.
  • Всеки ред от поръчките търси всички редове в ценоразписа със същия product_id.
  • Редът с кафето намира два и става два реда. Петте реда стават 6.
  • Продукт 26 не намира нищо. С how="left" редът остава, а колоните от ценоразписа са NaN.

Пробвай

Колко реда?

1. Ако в ценоразписа catalog кафето (17) беше три пъти, с три цени, колко реда щеше да има both за същите 5 реда от поръчки?

Идеята

Кои редове остават: how

Не всеки ред има двойка. Двама от 20-те клиенти още не са поръчвали нищо, а поръчките са 60. Параметърът how решава какво става с редовете без двойка:

Pythonданни: таблиците на магазина
  • inner (по подразбиране): само двойките. 60 реда, по един за всяка поръчка. Клиентите без поръчки ги няма.
  • left: всички редове отляво, с NaN, където няма двойка. 62 реда: 60 поръчки и двамата клиенти без поръчки.
  • right: всички редове отдясно. Тук е 60, защото всяка поръчка има клиент.
  • outer: всички редове от двете страни.

Кой ред откъде е, казва indicator=True. Той добавя колона _merge със стойности both, left_only и right_only:

Pythonданни: таблиците на магазина

Двамата клиенти без поръчки са с id 19 и 20. right_only е 0: няма поръчка на клиент, който го няма в таблицата. Ако имаше, това щеше да е грешка в данните и indicator щеше да я покаже.

По-точно, за любопитните

indicator е най-бързата проверка след съединяване. value_counts() на _merge веднага казва колко реда не са намерили двойка. Ако очакваш 0, а са 300, ключовете не съвпадат: например единият е текст '17', а другият е число 17.

Пробвай

Продукти без отзив

Кои продукти още нямат нито един отзив? Съедини products с отзивите (reviews) така, че да останат всички продукти, и запиши в no_review само редовете без двойка.

Pythonданни: таблиците на магазина

Идеята

Когато ключът се повтаря

Сега управителят иска до всеки ред от поръчка да стои оценката на продукта. Отзивите са в reviews.csv и изглежда естествено да ги съединиш по product_id:

Pythonданни: таблиците на магазина

Редовете станаха 167 от 152, а сборът на поръчките скочи от 1 963,50 € на 2 485,60 €. Кафето има 3 отзива, затова всеки ред с кафе се слепи с трите и стана три реда. Тиганът (22) има 2 отзива. Нищо не гърми, но числата вече са грешни.

Затова броиш редовете преди и след всяко съединяване. Ако дясната таблица трябва да има по един ред на ключ, кажи го на pandas с validate:

Pythonданни: таблиците на магазина

validate="many_to_one" значи „отляво ключът може да се повтаря, отдясно трябва да е уникален“. Ако не е, merge спира с MergeError и ти казва кой ключ се повтаря. Има и "one_to_one" и "one_to_many".

Пробвай

Оценка до всеки ред

Сложи до всеки ред от поръчките средната оценка на продукта. Редовете трябва да останат колкото са (152), а продуктите без отзив да са с NaN.

Pythonданни: таблиците на магазина

Идеята

Две колони name: suffixes

Следва категорията. И продуктите, и категориите имат колони id и name. След съединяването pandas пази и двете и им слага окончания _x (отляво) и _y (отдясно). С suffixes ги избираш сам:

Pythonданни: таблиците на магазина

suffixes=("", "_category") оставя колоните на продукта както са, а на категорията добавя _category. Подаръчната карта е без категория, затова name_category е NaN.

Тук е капанът на урока. Ако забравиш how="left", вътрешното съединяване изхвърля подаръчната карта без нито едно предупреждение:

Pythonданни: таблиците на магазина

152 реда стават 148. Четири продажби на подаръчна карта, общо 240 €, изчезват от отчета по категории. С how="left" редовете остават 152, а липсващата категория се вижда като NaN.

Идеята

Една под друга: concat

Понякога таблиците не трябва да се слепват една до друга, а една под друга. Магазинът сменил системата: поръчките до края на 2025 са изтеглени от старата, а тези от 2026 от новата, с едни и същи колони. pd.concat ги нарежда в една таблица:

Pythonданни: таблиците на магазина

21 + 39 = 60 реда. ignore_index=True номерира редовете наново от 0. Без него индексите от двете таблици се запазват и може да се повтарят.

Датите тук са текст във вида „2025-09-01“, затова сравнението < '2026-01-01' по азбучен ред съвпада с реда във времето. Истинските дати са в урока „Дати и времеви редове“.

По-точно, за любопитните

merge търси двойки по ключ и слага колоните една до друга. concat не търси нищо: нарежда редовете (или с axis=1 колоните) по реда на таблиците. Ако колоните не съвпадат, concat пак работи, но липсващите клетки стават NaN, затова преди него сравни df.columns на двете таблици.

Упражнение

Отчетът по категории

Отговори на управителя: колко е оборотът на всяка категория, без отказаните поръчки? Съедини редовете с поръчките (за status), с продуктите (за category_id) и с категориите, така че след всяко съединяване редовете да са пак 152. Продуктите без категория да са под „Без категория“. Запиши резултата в by_category.

Pythonданни: таблиците на магазина

Тест

Провери се

1. Какво става с ред от лявата таблица, за който в дясната няма ред със същия ключ, ако не зададеш how?
2. 152 реда от поръчки, съединени с отзивите по product_id, станаха 167. Защо?
3. Какво прави validate="many_to_one"?
4. И products, и categories имат колона name. Какви колони дава съединяването им?
5. Имаш поръчките от старата система и от новата, с едни и същи колони. Как ги правиш една таблица?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·