Gruppen: groupby
Kurz gesagt: groupby in pandas teilt die Zeilen nach einem Schlüssel, rechnet in jeder Gruppe (sum, mean, agg) und fügt das Ergebnis zu einer Tabelle zusammen.
Problem
„Wie groß ist die durchschnittliche Bestellung?“
Der Chef fragt, wie viel ein Kunde im Durchschnitt für eine Bestellung ausgibt. Du hast die Bestellungen des Jahres und ihre Zeilen: Jede Zeile ist ein Produkt in einer Bestellung, mit Menge und Preis. Stornierte Bestellungen sind kein Umsatz, also entfernst du sie mit einer Maske, wie in der Lektion über Filter:
Es sind 25.069 Zeilen und 10.459 Bestellungen. Der Mittelwert der Spalte revenue ist 4,44 €, aber das ist die durchschnittliche Zeile, nicht die durchschnittliche Bestellung. Eine Bestellung hat mehrere Zeilen, und für eine Bestellung musst du zuerst ihre Zeilen addieren.
Du brauchst eine Summe für jede Bestellung: 10.459 Zahlen aus 25.069 Zeilen. Mit einer for-Schleife und einem Dictionary geht das, aber es ist langsam und lang. pandas hat dafür eine einzige Operation.
Die Idee
Teilen, anwenden, zusammenfügen
groupby('order_id') teilt die Zeilen in Gruppen: eine Gruppe für jeden verschiedenen Wert von order_id. Dann wählst du eine Spalte und eine Rechnung: ['revenue'].sum() addiert den Umsatz in jeder Gruppe. Am Ende legt pandas die Ergebnisse in eine Series mit order_id als Index.
Das heißt Split-Apply-Combine. Hier an den ersten 7 Zeilen:
Bestellung 1 hat eine Zeile (31,60 €), und die Bestellungen 2, 3 und 4 haben je zwei. Bestellung 3 ist 37,5 + 8,4 = 45,90 €. Aus 7 Zeilen werden 4 Zahlen, eine pro Gruppe. Der Schlüssel order_id ist der Index des Ergebnisses, deshalb steht er unter dem Namen der Spalte.
Statt sum kannst du jede Zusammenfassung aus NumPy nehmen: mean, min, max, count.
Sieh es dir an
Wie groupby durch die Zeilen geht
Dieselben 7 Zeilen, langsam: Jede Zeile kommt in die Gruppe ihrer order_id, die Gruppe wird zu einer Zahl addiert, und die Zahlen reihen sich im Ergebnis auf.
import pandas as pd
items = pd.read_csv('order_items.csv').head(7)items['revenue'] = items['quantity'] * items['unit_price']print(items[['order_id', 'revenue']])items.groupby('order_id')['revenue'].sum()Schritt für Schritt, in Worten
- 7 Zeilen aus order_items mit dem Umsatz jeder Zeile: Bestellungen 1, 2, 3 und 4.
- Teilen (split): groupby('order_id') legt jede Zeile in die Gruppe ihrer order_id.
- 4 verschiedene order_id, 4 Gruppen. Bestellung 1 hat eine Zeile, die anderen je 2.
- Anwenden (apply): ['revenue'].sum() in jeder Gruppe. Bestellung 3: 37,5 + 8,4 = 45,9.
- Zusammenfügen (combine): Die 4 Summen werden eine Series mit order_id als Index.
- 7 Zeilen gingen hinein, 4 Zahlen kamen heraus: eine für jede Gruppe.
Probier es aus
Welches Produkt am meisten bestellt wird
In den 60 Bestellungen aus order_items.csv: Wie viele Stück von jedem Produkt wurden bestellt? Leg sie in units ab und die drei Produkte mit den meisten Stück in top3.
Lösung
import pandas as pd
items = pd.read_csv('order_items.csv')
units = items.groupby('product_id')['quantity'].sum()
top3 = units.nlargest(3)
print(top3)units = items.groupby('product_id')['quantity'].sum() liefert eine Zahl für jedes Produkt, und units.nlargest(3) liefert die Produkte 13, 5 und 10: Gurken (31 Stück), Frischmilch (27) und Äpfel (25). Die Gurken haben einen Bestand von 0: Sie sind ausverkauft, weil die Leute sie wollen. Mit count() statt sum() würdest du die Zeilen zählen, und eine Zeile können 4 Stück sein.
Die Idee
Mehrere Rechnungen auf einmal: agg
Oft brauchst du für jede Gruppe mehrere Zahlen. agg nimmt eine Liste von Rechnungen und liefert für jede eine Spalte:
Die Spalten heißen wie die Rechnungen. Für einen Bericht benennst du sie besser selbst. Das sind benannte Aggregationen: neuer_name=('Spalte', 'Rechnung'), und jede kann eine andere Spalte nutzen:
Bestellung 3 hat 2 Zeilen, 7 Stück und 45,90 € Umsatz. Die Tabelle hat eine Zeile pro Bestellung und eine Spalte für jede Frage, die du gestellt hast.
Die Idee
Das Zählen: size oder count
Die zwei Zähler sehen ähnlich aus, zählen aber Verschiedenes. size() zählt die Zeilen in jeder Gruppe. count() zählt die ausgefüllten Werte in jeder Spalte: NaN wird nicht gezählt. Hier die Kunden nach dem Jahr ihrer Anmeldung:
16 Kunden haben sich 2025 angemeldet, aber die Spalte city hat 15 Werte: Bei einem Kunden fehlt die Stadt. Wenn du wissen willst „wie viele Kunden“, ist size() die richtige Antwort. count() auf einer Spalte beantwortet „bei wie vielen kennen wir die Stadt“.
Die Idee
Mehrere Schlüssel
Der Schlüssel kann eine Liste von Spalten sein. Dann ist jedes Paar von Werten eine Gruppe. Hier die Stück des Jahres nach Produkt und nach Preis:
Es gibt 25 Produkte und 26 Gruppen: Ein Produkt wurde zu zwei Preisen verkauft. Das ist der Kaffee (Produkt 17): 245 Packungen zu 13,90 € und 444 Packungen zu 14,90 €, nachdem der Preis am 1. März gestiegen war. Das Ergebnis hat einen Index mit zwei Ebenen, und units.loc[17] holt die Gruppen des Kaffees.
Genauer, für Neugierige
Gruppen mit NaN im Schlüssel fallen heraus: Die Geschenkkarte hat keine Kategorie, und products.groupby('category_id') lässt sie stillschweigend weg. groupby('category_id', dropna=False) behält sie als NaN-Gruppe. Prüfe mit len, wie viele Zeilen in die Gruppen hineingehen und wie viele herauskommen.
Die Idee
Die Zahl der Gruppe auf jeder Zeile: transform
Manchmal willst du die Gruppe nicht schrumpfen, sondern ihre Zahl neben jede ihrer Zeilen legen. Zum Beispiel: wie teuer ein Produkt im Vergleich zum Durchschnittspreis seiner Kategorie ist. transform('mean') rechnet den Mittelwert der Gruppe aus und wiederholt ihn auf jeder Zeile, wobei Reihenfolge und Index der Tabelle erhalten bleiben:
transform liefert 25 Zahlen, eine pro Produkt, und mean() liefert 6, eine pro Kategorie. Deshalb passt die Spalte cat_avg direkt in die Tabelle. Der Kaffee kostet 14,90 €, und der Durchschnitt bei den Getränken ist 5,35 €: das 2,79-Fache. Das Waschpulver ist das 2,19-Fache des Durchschnitts bei der Reinigung.
Genauer, für Neugierige
Das Ergebnis von mean() hat category_id als Index, und die Tabelle products hat den Index 0, 1, 2… Wenn du es als Spalte zuweist, richtet pandas es nach dem Index aus, nicht nach der Kategorie, und fast alles wird NaN. transform behält den Index der Tabelle. Die Geschenkkarte hat keine Kategorie, also ist ihr cat_avg NaN.
Probier es aus
Der Anteil der Zeile an der Bestellung
Für jede Zeile von order_items.csv: Welchen Teil des Umsatzes ihrer Bestellung bringt sie ein? Füge items eine Spalte order_total mit dem Umsatz der ganzen Bestellung und eine Spalte share mit dem Anteil der Zeile hinzu.
Lösung
import pandas as pd
items = pd.read_csv('order_items.csv')
items['revenue'] = items['quantity'] * items['unit_price']
items['order_total'] = items.groupby('order_id')['revenue'].transform('sum')
items['share'] = items['revenue'] / items['order_total']
print(items[items['order_id'] == 3])items.groupby('order_id')['revenue'].transform('sum') legt den Umsatz der Bestellung auf jede ihrer Zeilen, und items['revenue'] / items['order_total'] liefert den Anteil. In Bestellung 3 bringt das Waschpulver 37,5 von 45,90 € ein, etwa 82 %, und die Äpfel etwa 18 %. Die Anteile der Zeilen einer Bestellung ergeben immer zusammen 1. Mit .sum() statt transform wäre die Spalte fast ganz NaN: Die Summe gilt pro Bestellung, und die Tabelle gilt pro Zeile.
Die Idee
Der Schlüssel als Spalte: as_index=False
Das Ergebnis von groupby hat den Schlüssel im Index. Um ihn zu filtern, mit einer anderen Tabelle zu verbinden oder in ein Diagramm zu legen, brauchst du eine gewöhnliche Spalte. Es gibt zwei Wege mit demselben Ergebnis:
as_index=False sagt es beim Gruppieren, und reset_index() macht es danach: Der Index wird zur Spalte, und die Zeilen werden 0, 1, 2… nummeriert. Die Spalten sind order_id und revenue.
Übung
Die durchschnittliche Bestellung
Beantworte die Frage des Chefs von der ersten Karte. Leg den Umsatz jeder Bestellung des Jahres ohne die stornierten in order_totals ab und die durchschnittliche Bestellung, auf 2 Stellen gerundet, in avg_order.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
cancelled = orders.loc[orders['status'] == 'cancelled', 'id']
items = items[~items['order_id'].isin(cancelled)]
items['revenue'] = items['quantity'] * items['unit_price']
order_totals = items.groupby('order_id')['revenue'].sum()
avg_order = round(order_totals.mean(), 2)
print(len(order_totals), avg_order)order_totals = items.groupby('order_id')['revenue'].sum() liefert 10.459 Zahlen, eine pro Bestellung, und round(order_totals.mean(), 2) liefert 10,63 €. Die durchschnittliche Zeile war 4,44 €: Eine Bestellung hat im Schnitt 2,4 Zeilen. Mit den stornierten wären es 11.116 Bestellungen und ein Durchschnitt von 10,61 €, nah dran, aber nicht richtig: Eine stornierte Bestellung ist kein Umsatz.
Wie viele Kunden haben im Lauf des Jahres mindestens einmal bestellt, und wie viele davon nur einmal? Leg die Anzahl der Bestellungen jedes Kunden in per_customer ab, die Anzahl der Kunden in n_customers und wie viele genau eine Bestellung haben in once.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv')
per_customer = orders.groupby('customer_id').size()
n_customers = len(per_customer)
once = (per_customer == 1).sum()
print(n_customers, once, per_customer.max())per_customer = orders.groupby('customer_id').size() liefert eine Zahl für jeden Kunden: 1.390 Kunden. (per_customer == 1).sum() zählt 224 mit einer Bestellung, und der treueste Kunde hat 121 Bestellungen, 2 oder 3 pro Woche. len(orders) würde die Bestellungen liefern (11.116) und per_customer.min() nur die kleinste Anzahl, 1.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Auswählen: [], loc und iloc · Filtern und Sortieren · Neue Spalten · Typen und fehlende Werte
- Geht weiter in: Verbinden: merge · Breit und lang: pivot_table und melt · Datumswerte und Zeitreihen · Das erste Diagramm
- Aufgaben zum Thema: Daten aufbereiten