Neue Spalten
Kurz gesagt: Eine neue Spalte in pandas wird aus den anderen Spalten auf einmal berechnet: Arithmetik, np.where oder map; assign liefert eine neue Tabelle, und apply ist der langsamste Weg.
Problem
Was jede Position wert ist
order_items.csv sagt dir für jede Position einer Bestellung, welches Produkt es ist, wie viele Stück und zu welchem Preis. Was die ganze Position wert war, steht nirgends:
Der Chef will den Umsatz, aber ohne stornierte Bestellungen. Er will auch wissen, welche Positionen groß sind, und im Bericht sollen Kategorienamen stehen, keine Zahlen. Für all das brauchst du Spalten, die nicht in der Datei stehen. Du berechnest sie aus denen, die da sind.
Die Idee
Eine Spalte aus Arithmetik
Eine Spalte ist eine Series, und Arithmetik mit Series ist vektorisiert, wie bei NumPy-Arrays. Stück mal Preis liefert den Wert aller 152 Positionen auf einmal. Speicherst du das Ergebnis unter einem neuen Namen, bekommt die Tabelle eine neue Spalte:
total ist rechts erschienen: Die erste Position sind 4 Stück zu 7,90 €, das sind 31,60 €. Gibt es den Namen schon, wird die Spalte durch die neuen Werte ersetzt.
Die Summe aller Positionen ist 1.963,50 €. Das ist noch nicht der Umsatz: In diesen Positionen stecken auch Bestellungen, die Kunden storniert haben.
Sieh es dir an
Umsatz ohne die stornierten
Eine stornierte Bestellung ist nicht bezahlt und zählt deshalb nicht zum Umsatz. Der Status steht in orders.csv, die Positionen in order_items.csv. Mit loc nimmst du die Nummern der stornierten Bestellungen, und dann findet isin aus der letzten Lektion ihre Positionen:
7 Bestellungen wurden storniert. Ohne ihre Positionen bleiben 131 von 152, und der Umsatz ist 1.697,00 €. Die Regel gilt für den ganzen Kurs: Geht es um Umsatz, zählen stornierte Bestellungen nicht.
Probier es aus
Geld in den Regalen
Füge products eine Spalte stock_value hinzu: wie viel Geld für jedes Produkt im Bestand steckt (Preis mal Bestand). Lege dann in top3 die drei Produkte mit dem größten stock_value ab, ohne die Geschenkkarte (id 25).
Lösung
import pandas as pd
products = pd.read_csv('products.csv')
products['stock_value'] = products['price'] * products['stock']
shelf = products[products['id'] != 25]
top3 = shelf.nlargest(3, 'stock_value')
top3[['name', 'price', 'stock', 'stock_value']]products['stock_value'] = products['price'] * products['stock'], dann products[products['id'] != 25].nlargest(3, 'stock_value'). Das meiste Geld steckt im Kaffee (223,50 €), in der Bratpfanne (199,20 €) und im Messerset (195,00 €). Ohne den Filter steht die Geschenkkarte mit 19.980 € an erster Stelle, aber 999 ist ein Platzhalter, keine Karten im Regal.
Die Idee
Eine neue Tabelle mit assign
items['total'] = … ändert die Tabelle selbst. assign macht das Gegenteil: Es liefert eine neue Tabelle mit einer Spalte mehr, und die alte bleibt, wie sie ist. So kannst du gleich mit einem Filter weitermachen:
6 Positionen sind 50 € oder mehr wert. Die letzte Zeile zeigt, dass items kein total hat: assign hat es nicht angefasst. Das ist Copy-on-Write aus der Lektion über das Auswählen: Eine Änderung liefert eine neue Tabelle, und das Original ändert sich nicht.
In assign kannst du statt einer Spalte auch eine Funktion übergeben. lambda d: … bekommt die Tabelle in diesem Moment, deshalb sieht das zweite assign das total des ersten:
Eine solche Folge von Methoden, von denen jede mit dem Ergebnis der vorigen arbeitet, heißt Kette. Mit den Klammern drumherum kannst du sie über mehrere Zeilen schreiben.
Die Idee
Das eine oder das andere: np.where
Eine „große“ Position ist 20 € oder mehr wert. np.where aus der NumPy-Lektion funktioniert auch mit Spalten: Für jede Zeile nimmt es den einen oder den anderen Wert:
Es gibt 35 große und 117 kleine Positionen. Das Ergebnis hat für jede Zeile einen Wert, also wird es zu einer Spalte.
Die Idee
Von der Zahl zum Namen: map
In products ist die Kategorie eine Zahl, und der Chef will einen Namen. map mit einem Dictionary ersetzt jeden Wert durch den passenden aus dem Dictionary:
3 wird zu „Brot und Gebäck“, 8 wird zu „Küche“. Die Geschenkkarte hat keine Kategorie, im Dictionary gibt es keinen Schlüssel dafür, und map lässt NaN stehen.
Das Dictionary musst du nicht von Hand tippen. categories.set_index('id')['name'] ist eine Series mit den Zahlen als Index und den Namen als Werten, und map funktioniert auch mit einer solchen Series: Es schlägt den Wert in ihrem Index nach.
Die Idee
Deine eigene Funktion: apply
Der Shop gibt auf eine große Position Rabatt: 10 % ab 50 € und 5 % ab 20 €. Die Regel ist eine Python-Funktion mit if. apply führt sie für jeden Wert der Spalte aus:
117 Positionen bekommen keinen Rabatt, 29 bekommen 5 % und 6 bekommen 10 %. Alle Rabatte zusammen machen 84,14 € aus.
apply ist praktisch, wenn die Regel schon eine Funktion ist, die mit einem Wert arbeitet. Diese Regel ließe sich auch vektorisiert schreiben, mit zwei ineinander verschachtelten np.where. Bei 152 Positionen spürst du den Unterschied nicht. Bei vielen Positionen schon.
Sieh es dir an
Was apply kostet
Hier ist dieselbe Arithmetik auf zwei Wegen über alle 26.619 Positionen der Bestellungen des Jahres. apply(…, axis=1) führt die Funktion für jede Zeile aus, und time.perf_counter() misst, wie viel Zeit vergeht:
Das Ergebnis ist dasselbe (True in der vorletzten Zeile). Bei unseren Läufen war apply über hundertmal langsamer als die vektorisierte Arithmetik. Die genauen Zahlen hängen vom Rechner ab, aber der Abstand bleibt.
Die Regel: Zuerst suchst du vektorisierte Arithmetik, np.where oder map. apply bleibt für eine Regel, die sich nicht anders schreiben lässt.
Genauer, für Neugierige
apply mit axis=1 macht aus jeder Zeile eine kleine Series und ruft deine Python-Funktion 26.619-mal auf. Die vektorisierte Arithmetik geht einmal über die Spalte, in einer C-Schleife in NumPy. Deshalb wächst der Abstand mit der Zahl der Zeilen: Bei 152 Zeilen ist er unsichtbar, bei einer Million ist es der Unterschied zwischen einer Sekunde und einer Minute.
Die Idee
Der Bericht: rename und drop
Bevor du die Tabelle dem Chef schickst, entfernst du die Spalten, die er nicht braucht, und gibst Namen, die er versteht. drop(columns=[…]) entfernt Spalten, und rename(columns={alt: neu}) benennt sie um:
Beide liefern eine neue Tabelle, wie assign. Spalten, die nicht im Dictionary von rename stehen, behalten ihren alten Namen.
Übung
Der Umsatz des Jahres
Wie hoch ist der Umsatz des ganzen Jahres? Füge year_items eine Spalte total hinzu (Stück mal Preis), entferne die Positionen stornierter Bestellungen und lege die Summe, auf 2 Nachkommastellen gerundet, in revenue ab.
Lösung
import pandas as pd
year = pd.read_csv('year_orders.csv')
year_items = pd.read_csv('year_items.csv')
year_items['total'] = year_items['quantity'] * year_items['unit_price']
cancelled = year.loc[year['status'] == 'cancelled', 'id']
kept = year_items[~year_items['order_id'].isin(cancelled)]
revenue = round(kept['total'].sum(), 2)
print(revenue)year_items['total'] = year_items['quantity'] * year_items['unit_price'], dann kept = year_items[~year_items['order_id'].isin(cancelled)]. Der Umsatz ist 111.186,20 €. Mit den stornierten Bestellungen sind es 117.902,70 €: 6.716,50 € in 1.550 Positionen, die niemand bezahlt hat. Ohne ~ bekommst du nur diese 6.716,50 €.
Wie viel haben die Kaffeebohnen (Produkt 17) im Jahr eingebracht, ohne stornierte Bestellungen? Lege die Summe, auf 2 Nachkommastellen gerundet, in coffee_revenue ab.
Lösung
import pandas as pd
year = pd.read_csv('year_orders.csv')
year_items = pd.read_csv('year_items.csv')
cancelled = year.loc[year['status'] == 'cancelled', 'id']
coffee = year_items[(year_items['product_id'] == 17) & ~year_items['order_id'].isin(cancelled)]
coffee = coffee.assign(total=coffee['quantity'] * coffee['unit_price'])
coffee_revenue = round(coffee['total'].sum(), 2)
print(coffee['unit_price'].value_counts())
print(coffee_revenue)coffee_revenue ist 9.585,10 €. value_counts auf unit_price zeigt zwei Preise: 13,90 € in 192 Positionen (bis zum 1. März) und 14,90 € in 345 Positionen. Nimmst du mit map den heutigen Preis aus products, bekommst du 9.819,10 €: 234 € mehr, als die Kunden tatsächlich bezahlt haben. Mit den stornierten Bestellungen sind es 10.021,10 €.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Zusammenfassungen und fehlende Werte · Series und DataFrame · Auswählen: [], loc und iloc · Filtern und Sortieren
- Geht weiter in: Typen und fehlende Werte · Gruppen: groupby · Verbinden: merge · Breit und lang: pivot_table und melt
- Aufgaben zum Thema: Tabellen mit pandas