Verbinden: merge
Kurz gesagt: merge in pandas verbindet zwei Tabellen über einen gemeinsamen Schlüssel, wie ein JOIN in SQL, und how entscheidet, welche Zeilen bleiben.
Problem
„Welche Kategorie bringt das meiste Geld?“
Der Chef fragt, welche Kategorie am meisten verkauft. Die Bestellzeilen sagen, wie viele Stück welches Produkts gekauft wurden und zu welchem Preis:
Eine Kategorie steht da aber nicht. Es gibt nur product_id, die Produktnummer. Der Name und die Kategorie stehen in products.csv, und der Name der Kategorie steht in einer dritten Tabelle, categories.csv.
Shopdaten sind fast immer so aufgeteilt: Jede Sache steht an einer Stelle, und die anderen Tabellen verweisen mit einer Nummer darauf. Um zu antworten, musst du die Tabellen zu einer verbinden.
Die Idee
Über einen Schlüssel verbinden: merge
merge nimmt jede Zeile der linken Tabelle, sucht in der rechten Tabelle die Zeile mit demselben Wert in der Schlüsselspalte und klebt die beiden Zeilen zu einer zusammen. Das heißt Join, und die Spalte, über die gesucht wird, ist der Schlüssel. In SQL ist dasselbe JOIN.
Hier hat der Schlüssel in jeder Tabelle einen anderen Namen: product_id in den Zeilen, id in den Produkten. left_on und right_on sagen, welche Spalte auf welcher Seite ist:
Die Zeilen bleiben 152, und die Spalten werden 10: die vier aus items und die sechs aus products. Die Spalten product_id und id enthalten dieselbe Zahl, jetzt doppelt.
Deshalb benennst du den Schlüssel meistens zuerst um, damit er überall gleich heißt, und verbindest mit on. Außerdem nimmst du nur die Spalten, die du brauchst:
Sieh es dir an
Wie merge die Paare findet
Hier dasselbe an kleinen Tabellen: 5 Bestellzeilen und eine Preisliste mit 4 Zeilen. In der Preisliste steht der Kaffee (17) zweimal, mit dem alten und dem neuen Preis, und Produkt 26 gibt es noch nicht. Mit how="left" bleibt jede Bestellzeile:
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': ['Weißbrot', 'Frischmilch', 'Kaffeebohnen', 'Kaffeebohnen'], 'price': [1.2, 1.6, 13.9, 14.9],})both = lines.merge(catalog, on='product_id', how='left')print(len(lines), len(both))bothSchritt für Schritt, in Worten
- lines sind 5 Bestellzeilen. product_id sagt, welches Produkt gekauft wurde.
- catalog ist die Preisliste: 4 Zeilen. Produkt 17 steht zweimal darin, mit dem alten und dem neuen Preis.
- Zeile 1: product_id 1. merge sucht alle Zeilen mit 1 in catalog: Weißbrot. Das Paar wird eine Zeile in both.
- Zeile 2: product_id 17 steht zweimal in catalog, mit 13,90 € und 14,90 €. Die zwei Paare werden zu zwei Zeilen.
- Zeile 3: product_id 5, das ist Frischmilch.
- Zeile 4: product_id 1, wieder Weißbrot. Eine Zeile aus catalog kann das Paar vieler Zeilen sein.
- Zeile 5: Produkt 26 gibt es nicht in catalog. Mit how='left' bleibt die Zeile, und name und price sind NaN.
- 5 Zeilen gingen hinein, 6 kamen heraus: Der wiederholte Schlüssel hat eine Zeile verdoppelt. Deshalb zählst du len vorher und nachher.
- Jede Bestellzeile sucht alle Zeilen der Preisliste mit derselben
product_id. - Die Kaffeezeile findet zwei und wird zu zwei Zeilen. Aus fünf Zeilen werden 6.
- Produkt 26 findet nichts. Mit
how="left"bleibt die Zeile, und die Spalten aus der Preisliste sind NaN.
Probier es aus
Wie viele Zeilen?
Die Idee
Welche Zeilen bleiben: how
Nicht jede Zeile hat ein Paar. Zwei der 20 Kunden haben noch nichts bestellt, und es gibt 60 Bestellungen. Der Parameter how entscheidet, was mit den Zeilen ohne Paar passiert:
inner(der Standard): nur die Paare. 60 Zeilen, eine für jede Bestellung. Die Kunden ohne Bestellung sind weg.left: jede Zeile links, mit NaN dort, wo es kein Paar gibt. 62 Zeilen: 60 Bestellungen und die zwei Kunden ohne Bestellung.right: jede Zeile rechts. Hier sind es 60, weil jede Bestellung einen Kunden hat.outer: jede Zeile von beiden Seiten.
indicator=True sagt dir, woher jede Zeile kam. Es fügt eine Spalte _merge mit den Werten both, left_only und right_only hinzu:
Die zwei Kunden ohne Bestellung haben die IDs 19 und 20. right_only ist 0: Keine Bestellung gehört zu einem Kunden, der in der Tabelle fehlt. Wenn doch, wäre das ein Fehler in den Daten, und indicator würde ihn zeigen.
Genauer, für Neugierige
indicator ist die schnellste Prüfung nach einem Join. value_counts() auf _merge sagt dir sofort, wie viele Zeilen kein Paar gefunden haben. Wenn du 0 erwartest und es 300 sind, passen die Schlüssel nicht zusammen: zum Beispiel ist der eine der Text '17' und der andere die Zahl 17.
Probier es aus
Produkte ohne Bewertung
Welche Produkte haben noch gar keine Bewertung? Verbinde products so mit den Bewertungen, dass jedes Produkt bleibt, und lege in no_review nur die Zeilen ohne Paar ab.
Lösung
import pandas as pd
products = pd.read_csv('products.csv')
reviews = pd.read_csv('reviews.csv')
both = products.merge(reviews[['product_id', 'rating']], left_on='id', right_on='product_id', how='left', indicator=True)
no_review = both[both['_merge'] == 'left_only']
print(len(no_review))
no_review['name']Mit how="left" und indicator=True bleiben alle 25 Produkte, und die ohne Bewertung haben _merge == "left_only". Es sind 12, darunter das Butterbrötchen, die Banitsa mit Käse und die Geschenkkarte. Bei einem Inner Join verschwinden die Produkte ohne Bewertung schon beim Verbinden, und left_only ist leer.
Die Idee
Wenn sich der Schlüssel wiederholt
Jetzt will der Chef die Produktbewertung neben jeder Bestellzeile. Die Bewertungen stehen in reviews.csv, und es scheint natürlich, sie über product_id zu verbinden:
Die Zeilen wurden 167 statt 152, und die Summe der Bestellungen sprang von 1.963,50 € auf 2.485,60 €. Der Kaffee hat 3 Bewertungen, also bildete jede Kaffeezeile mit allen dreien ein Paar und wurde zu drei Zeilen. Die Bratpfanne (22) hat 2 Bewertungen. Nichts stürzt ab, aber die Zahlen sind jetzt falsch.
Deshalb zählst du die Zeilen vor und nach jedem Join. Wenn die rechte Tabelle eine Zeile pro Schlüssel haben soll, sag es pandas mit validate:
validate="many_to_one" heißt „links darf sich der Schlüssel wiederholen, rechts muss er eindeutig sein“. Wenn nicht, stoppt merge mit MergeError und sagt dir, welcher Schlüssel sich wiederholt. Es gibt auch "one_to_one" und "one_to_many".
Probier es aus
Eine Bewertung auf jeder Zeile
Leg die mittlere Bewertung des Produkts neben jede Bestellzeile. Die Zeilen müssen so viele bleiben, wie sie sind (152), und Produkte ohne Bewertung sollen NaN bekommen.
Lösung
import pandas as pd
items = pd.read_csv('order_items.csv')
reviews = pd.read_csv('reviews.csv')
avg = reviews.groupby('product_id', as_index=False)['rating'].mean()
with_rating = items.merge(avg, on='product_id', how='left', validate='many_to_one')
print(len(items), len(with_rating))
with_rating.head(3)Zuerst liefert reviews.groupby("product_id", as_index=False)["rating"].mean() eine Zeile pro Produkt: 13 Produkte, der Kaffee mit 3,33. Jetzt ist der Schlüssel rechts eindeutig, validate="many_to_one" geht durch, und die Zeilen bleiben 152. 75 davon gehören zu Produkten ohne Bewertung und haben NaN. Bei einem Inner Join würden sie verschwinden, und ohne groupby würden die Zeilen 167.
Die Idee
Zwei Namensspalten: suffixes
Als Nächstes kommt die Kategorie. Sowohl die Produkte als auch die Kategorien haben die Spalten id und name. Nach dem Join behält pandas beide und hängt die Endungen _x (links) und _y (rechts) an. Mit suffixes wählst du sie selbst:
suffixes=("", "_category") lässt die Produktspalten, wie sie sind, und hängt _category an die der Kategorie. Die Geschenkkarte hat keine Kategorie, also ist name_category NaN.
Hier ist die Falle der Lektion. Wenn du how="left" vergisst, wirft der Inner Join die Geschenkkarte ohne eine einzige Warnung weg:
Aus 152 Zeilen werden 148. Vier Verkäufe der Geschenkkarte, zusammen 240 €, verschwinden aus dem Bericht nach Kategorie. Mit how="left" bleiben die Zeilen 152, und die fehlende Kategorie erscheint als NaN.
Die Idee
Untereinander: concat
Manchmal sollen Tabellen nicht nebeneinander, sondern untereinander geklebt werden. Der Shop hat sein System gewechselt: Die Bestellungen bis Ende 2025 wurden aus dem alten exportiert, die ab 2026 aus dem neuen, mit denselben Spalten. pd.concat stapelt sie zu einer Tabelle:
21 + 39 = 60 Zeilen. ignore_index=True nummeriert die Zeilen neu ab 0. Ohne es bleiben die Indizes beider Tabellen erhalten und können sich wiederholen.
Die Datumsangaben sind hier Text wie „2025-09-01“, also stimmt der Vergleich < '2026-01-01' in alphabetischer Reihenfolge mit der zeitlichen Reihenfolge überein. Echte Daten kommen in der Lektion „Datumswerte und Zeitreihen“.
Genauer, für Neugierige
merge sucht Paare über einen Schlüssel und legt die Spalten nebeneinander. concat sucht nichts: Es stapelt die Zeilen (oder mit axis=1 die Spalten) in der Reihenfolge der Tabellen. Wenn die Spalten nicht zusammenpassen, funktioniert concat trotzdem, aber die fehlenden Zellen werden NaN, also vergleiche vorher df.columns beider Tabellen.
Übung
Der Bericht nach Kategorie
Beantworte den Chef: Wie hoch ist der Umsatz jeder Kategorie, ohne die stornierten Bestellungen? Verbinde die Zeilen mit den Bestellungen (für status), mit den Produkten (für category_id) und mit den Kategorien, sodass nach jedem Join noch 152 Zeilen da sind. Produkte ohne Kategorie kommen unter „Keine Kategorie“. Lege das Ergebnis in by_category ab.
Lösung
import pandas as pd
items = pd.read_csv('order_items.csv')
orders = pd.read_csv('orders.csv').rename(columns={'id': 'order_id'})
products = pd.read_csv('products.csv').rename(columns={'id': 'product_id'})
categories = pd.read_csv('categories.csv').rename(columns={'id': 'category_id', 'name': 'category'})
lines = items.merge(orders[['order_id', 'status']], on='order_id', how='left', validate='many_to_one')
lines = lines.merge(products[['product_id', 'category_id']], on='product_id', how='left', validate='many_to_one')
lines = lines.merge(categories[['category_id', 'category']], on='category_id', how='left', validate='many_to_one')
print(len(items), len(lines))
lines['category'] = lines['category'].fillna('Keine Kategorie')
sold = lines[lines['status'] != 'cancelled']
sold['revenue'] = sold['quantity'] * sold['unit_price']
by_category = sold.groupby('category')['revenue'].sum().round(2).sort_values(ascending=False)
by_categoryMit how="left" und validate="many_to_one" behalten alle drei Joins 152 Zeilen. fillna("Keine Kategorie") gibt der Geschenkkarte einen Namen. Ohne die stornierten Bestellungen bleiben 131 Zeilen, zusammen 1697,00 €. „Küche“ bringt am meisten, 516,90 €, und die Geschenkkarte macht 160,00 €. Mit einem Inner Join verschwindet die Karte, und wenn du die stornierten behältst, kommt „Küche“ auf 601,50 €.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Filtern und Sortieren · Neue Spalten · Typen und fehlende Werte · Gruppen: groupby
- Geht weiter in: Breit und lang: pivot_table und melt · Datumswerte und Zeitreihen · Das erste Diagramm · Welches Diagramm für welche Frage
- Aufgaben zum Thema: Daten aufbereiten