Filtern und Sortieren
Kurz gesagt: Filtern in pandas behält nur die Zeilen, in denen eine Maske True ist (oder isin, between, query), und sort_values ordnet sie nach einer Spalte.
Problem
Die Liste für den Lieferanten
Morgen kommt der Lieferant für Lebensmittel. Der Chef will eine Liste: die Lebensmittel, von denen wir weniger als 20 haben, das Dringendste zuerst. Danach fragt er auch nach den Bestellungen des Jahres: wie viele storniert wurden und wie viele Kunden regelmäßig bei uns kaufen.
25 Produkte kannst du mit dem Auge überblicken. Die Bestellungen des Jahres sind 11.116, und da reichen die Augen nicht. Du brauchst eine Möglichkeit, der Tabelle zu sagen „gib mir nur diese Zeilen“ und „ordne sie so“.
Die Idee
Eine Maske auf einer Tabelle
Die Maske aus NumPy funktioniert auch in pandas. products['stock'] < 20 vergleicht jede Zeile und liefert eine Spalte (eine Series) aus True und False, mit dem Index der Tabelle. In eckige Klammern gesetzt, behält die Maske die Zeilen mit True:
low.sum() zählt die Trues: 9 Produkte haben weniger als 20 auf Lager. Das nennt man einen Filter: eine Tabelle mit nur den Zeilen, für die die Bedingung wahr ist.
Sieh dir links den Index an: 3, 6, 7, 12 … Die Zeilen behalten ihre Beschriftungen aus products, du weißt also immer, woher jede kam. In [] wählt ein Spaltenname eine Spalte, und eine Maske wählt Zeilen.
Die Idee
Zwei Bedingungen: &, | und ~
Unter 20 auf Lager und unter 5 € sind zwei Bedingungen zusammen. Mit and bekommst du dasselbe wie in NumPy:
and will ein einziges True oder False, und die Spalte hat 25. Also schreibst du auch hier & für „und“, | für „oder“ und ~ für „nicht“, mit jeder Bedingung in Klammern:
Unter 20 auf Lager und unter 5 € sind die Banitsa und die Gurken, beide ausverkauft. Unter 1 € oder über 20 € sind das Butterbrötchen (0,80 €), das Wasser (0,90 €), die Bratpfanne (24,90 €) und das Messerset (39 €). Die Geschenkkarte kostet genau 20 € und ist nicht „über 20“.
Probier es aus
Die teureren Milchprodukte
Welche Milchprodukte (Kategorie 4) kosten über 3 €? Lege die Tabelle mit ihnen in pricey_dairy ab.
Lösung
import pandas as pd
products = pd.read_csv('products.csv')
pricey_dairy = products[(products['category_id'] == 4) & (products['price'] > 3)]
pricey_dairy[['name', 'price']]products[(products['category_id'] == 4) & (products['price'] > 3)] liefert 3 Produkte: den Weißkäse (7,90 €), den Kaschkawal (9,50 €) und die Butter (3,20 €). Mit | kommen alle Milchprodukte und alles über 3 € hinein, und ohne die Klammern scheitert der Code mit einem Fehler. Die Maske allein, ohne products[...], ist nur eine Spalte aus True und False.
Die Idee
Aus einer Liste und zwischen zwei Zahlen: isin und between
Lebensmittel sind in vier Kategorien: 3, 4, 5 und 6. Statt vier Bedingungen mit | schreibst du isin, das fragt „steht es in dieser Liste?“:
Es gibt 18 Lebensmittel. Mit ~ bekommst du die anderen 7: die sechs Haushaltsprodukte und die Geschenkkarte. Die Karte hat keine Kategorie (NaN), und NaN steht in keiner Liste, also erfasst sie ~.
Für einen Bereich gibt es between. between(2, 5) heißt „von 2 bis 5 einschließlich“: Beide Enden gehören dazu:
Es liefert 9 Produkte, so viele wie (prices >= 2) & (prices <= 5) in der NumPy-Lektion.
Genauer, für Neugierige
between(2, 5, inclusive='neither') lässt beide Enden weg, und 'left' und 'right' behalten nur eines davon. Standard ist 'both'.
Die Idee
Text, der etwas enthält: str.contains
„Zeig mir alle Brote.“ Unter den Namen sind „Weißbrot“ und „Vollkornbrot“, aber du kannst nicht == 'brot' schreiben: Kein Name ist genau „brot“. str.contains fragt, ob der Text irgendwo im Namen vorkommt:
.str öffnet die Textmethoden für die ganze Spalte. Genauer siehst du es in der Lektion über Typen.
Die Suche unterscheidet Groß- und Kleinbuchstaben. „käse“ findet nur „Weißkäse“, also ist die erste Zahl 1. „Banitsa mit Käse“ hat ein großes K und fällt heraus, und „Kaschkawal“ ist zwar ein Käse, hat das Wort aber nicht im Namen. Mit case=False werden die Buchstaben nicht unterschieden, und du bekommst 2.
Die Idee
Die Bedingung als Text: query
Lange Masken mit überall products['...'] werden schwer lesbar. query nimmt die Bedingung als Text, in dem die Spaltennamen für sich stehen:
In query schreibst du and, or und not. Der Text wird nicht von Python gerechnet, sondern von pandas, und pandas weiß, dass das Spalten sind. Unter 20 auf Lager und über 5 € sind 7 Produkte.
Eine Python-Variable kommt mit @ hinein: category_id in @food ist dasselbe wie isin(food). Das liefert die fünf Lebensmittel mit weniger als 20 auf Lager, aus denen du gleich die Liste für den Lieferanten machst.
Die Idee
Sortieren: sort_values und nlargest
sort_values('price') ordnet die Zeilen nach Preis, vom kleinen zum großen. Mit ascending=False ist es umgekehrt. Für „die Top N“ gibt es nlargest(N, Spalte) und nsmallest:
Am teuersten sind das Messerset (39 €), die Bratpfanne (24,90 €) und die Geschenkkarte (20 €). nlargest(3, 'price') liefert dieselben drei in einer Zeile.
Du kannst auch nach mehreren Spalten sortieren: zuerst nach Kategorie, und darin nach Preis. ascending ist dann eine Liste, eine für jede Spalte. Die Kategorien steigen und die Preise darin fallen: Die Banitsa (2,50 €) steht beim Brot an erster Stelle, der Kaschkawal (9,50 €) bei den Milchprodukten.
Genauer, für Neugierige
Das Sortieren verschiebt die Zeilen, ändert aber nicht ihre Beschriftungen: Der Index in der Ausgabe ist 22, 21, 24 … Brauchst du die Nummern 0, 1, 2 in der neuen Reihenfolge, füge .reset_index(drop=True) hinzu.
Die Idee
Wie viele von jedem: value_counts
„Wie viele Bestellungen wurden storniert?“ ist eine Zählfrage. value_counts zählt, wie oft jeder Wert in einer Spalte vorkommt, den häufigsten oben:
Von den 11.116 Bestellungen des Jahres wurden 10.254 geliefert und 657 storniert. Mit normalize=True bekommst du statt der Zahl einen Anteil: 0.059, das heißt 5,9 % der Bestellungen wurden storniert.
Dasselbe mit der Kundennummer sagt dir, wer wie oft bestellt hat:
1.390 Kunden haben bestellt. Der treueste, Kunde 1, hat 121-mal bestellt, und 224 Kunden haben nur einmal bestellt. Der Index des Ergebnisses sind die Kundennummern, und die Werte sind die Zahl der Bestellungen jedes Kunden.
Probier es aus
Die offenen Bestellungen
Bestellungen mit dem Status new, paid oder shipped sind noch nicht beim Kunden angekommen. Lege die Tabelle mit ihnen in open_orders ab.
Lösung
import pandas as pd
year = pd.read_csv('year_orders.csv')
open_orders = year[year['status'].isin(['new', 'paid', 'shipped'])]
print(len(open_orders))year[year['status'].isin(['new', 'paid', 'shipped'])] liefert 205 Bestellungen: 90 neue, 63 bezahlte und 52 versendete, genau wie value_counts es sagt. != 'delivered' ließe auch die 657 stornierten herein, und die werden nicht versendet.
Übung
Die Liste und die Stammkunden
Mach die Liste für den Lieferanten: die Lebensmittel (Kategorien 3, 4, 5 und 6) mit einem Bestand unter 20, nach Bestand sortiert, vom kleinsten an. Bei gleichem Bestand steht das teurere Produkt zuerst. Lege die Tabelle in restock ab.
Lösung
import pandas as pd
products = pd.read_csv('products.csv')
food = [3, 4, 5, 6]
restock = products[products['category_id'].isin(food) & (products['stock'] < 20)]
restock = restock.sort_values(['stock', 'price'], ascending=[True, False])
restock[['name', 'price', 'stock']]Der Filter isin(food) & (stock < 20) liefert 5 Lebensmittel. sort_values(['stock', 'price'], ascending=[True, False]) ordnet sie: die Gurken (2,80 €, Bestand 0), die Banitsa (2,50 €, Bestand 0), der Kaschkawal (12), der Kaffee (15) und der Weißkäse (18). Ohne isin kommen auch die Bratpfanne, das Messerset, das Schneidebrett und das Waschpulver hinein, und mit ascending=False für beide Spalten rutschen die dringendsten nach unten.
Wie viele Kunden haben im Jahr mindestens 20-mal bestellt? Lege ihre Zahl in regulars ab.
Lösung
import pandas as pd
year = pd.read_csv('year_orders.csv')
per_customer = year['customer_id'].value_counts()
regulars = (per_customer >= 20).sum()
print(regulars)per_customer = year['customer_id'].value_counts() liefert die Zahl der Bestellungen jedes Kunden, und (per_customer >= 20).sum() zählt die Kunden mit mindestens 20: Es sind 119. (year['customer_id'] >= 20).sum() liefert 9.471, weil es Kundennummern vergleicht, nicht ihre Bestellungen. Mit > 20 bekommst du 110: Du verpasst die mit genau 20.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Vektorisierung · Zusammenfassungen und fehlende Werte · Series und DataFrame · Auswählen: [], loc und iloc
- Geht weiter in: Neue Spalten · Typen und fehlende Werte · Gruppen: groupby · Verbinden: merge
- Aufgaben zum Thema: Tabellen mit pandas