Spalten hinzufügen — Zahlen erstellen, die nicht in der Datei stehen
Neue Spalten aus bestehenden erstellen: Arithmetik auf ganzen Spalten und warum der Index entscheidet, wo jeder Wert landet, assign, np.where und np.select, map, pd.cut, .str und .dt, transform zum Vergleichen einer Zeile mit ihrer Gruppe und apply als letzter Ausweg.
Wie möchtest du lernen?
Das Problem, das wir lösen
Die Ladenbesitzerin schickt eine Nachricht: „Für jede Bestellung möchte ich sehen, wie viel Geld sie eingebracht hat, ob es eine große Bestellung war, welche Filialleitung dafür zuständig ist und welcher Wochentag es war. Und bei jeder Bestellung: Welchen Anteil am Geld der jeweiligen Filiale macht das aus?“
Du öffnest orders.csv. Die Datei enthält quantity und price, branch (welche der drei Filialen) und date. Nicht ein einziger der gewünschten Werte steht in der Datei. Jeder einzelne davon muss aus den Spalten erstellt werden, die bereits vorhanden sind.
Wenn du von reinem Python kommst, wissen deine Hände bereits, was zu tippen ist — eine Schleife über die Zeilen:
import pandas as pd
orders = pd.read_csv("orders.csv")
revenues = []
for i in range(len(orders)):
revenues.append(orders.loc[i, "quantity"] * orders.loc[i, "price"])
orders["revenue"] = revenues
print(orders[["product", "quantity", "price", "revenue"]])product quantity price revenue
0 pen 12 15.0 180.0
1 notebook 5 60.0 300.0
2 bag 2 850.0 1700.0
3 bottle 7 120.0 840.0
4 eraser 30 8.0 240.0
5 bag 1 850.0 850.0
6 pen 20 15.0 300.0
7 bottle 4 120.0 480.0Es funktioniert. Es ist allerdings auch die Gewohnheit, die dieses Kapitel ablösen soll — aus drei Gründen, die sich erst später bemerkbar machen.
Es ist langsam: Python führt den Schleifenkörper einmal pro Zeile aus; bei einer Datei mit einer Million Zeilen bedeutet das eine Million Durchläufe durch den Interpreter. Es ist lang: vier Zeilen für eine einzige Multiplikation, und die gewünschte Liste umfasst noch fünf weitere Spalten. Und es ist auf eine Weise fragil, die man leicht übersieht. Die Schleife zählt Positionen 0, 1, 2, … und schlägt sie dann mit .loc als Labels nach. Das funktioniert nur so lange, wie beide zufällig übereinstimmen. Filterst du die Tabelle zuerst — nur die Bestellungen der Filiale north, wie in Kapitel fünf —, ist das nicht mehr der Fall:
north = orders[orders["branch"] == "north"]
print(north.index.tolist())
revenues = []
for i in range(len(north)):
revenues.append(north.loc[i, "quantity"] * north.loc[i, "price"])[0, 2, 4, 7]
KeyError: 1Die north-Zeilen behalten ihre ursprünglichen Labels 0, 2, 4, 7. Die Schleife fragt nach Label 1, was eine south-Bestellung ist und sich nicht in north befindet — das Programm stürzt ab. Schlimmere Varianten dieses Fehlers stürzen gar nicht erst ab — sie lesen stillschweigend die falsche Zeile.
Pandas verfolgt hier eine ganz andere Denkweise: Du berechnest eine Spalte nicht Zeile für Zeile; du berechnest sie einmal für die gesamte Spalte. Genau um diese eine Idee geht es in diesem Kapitel. Sobald es Klick macht, ist jede von der Ladenbesitzerin gewünschte Spalte ein Einzeiler.
Am Ende dieses Kapitels kannst du
- Eine neue Spalte aus arithmetischen Operationen auf ganzen Spalten erstellen und erklären, warum keine Schleife nötig ist
- Vorhersagen, wie pandas Werte anhand des Index ausrichtet, wenn du eine
Serieszuweist, und dasNaNvermeiden, das durch Fehlausrichtungen entsteht - Spalten mit
assign()hinzufügen, ohne die ursprüngliche Tabelle zu verändern - Mit
np.whereundnp.selectzwischen zwei oder mehr Werten pro Zeile wählen - Werte mit
.map()nachschlagen, Zahlen mitpd.cutin Bereiche unterteilen und Teile aus Texten und Datumsangaben mit.strund.dtextrahieren - Jede Zeile über
groupby(...).transform(...)mit ihrer eigenen Gruppe vergleichen - Erkennen, wann
apply(axis=1)gerechtfertigt ist und warum es der letzte Ausweg ist - Erklären, was Copy-on-Write in pandas 3 für das Hinzufügen von Spalten zu einer gefilterten Tabelle bedeutet
Voraussetzungen: Gruppierung und Aggregation.
Erstelle zuerst die Datei
Jedes Beispiel in diesem Kapitel liest orders.csv ein, dieselbe Datei, die seit Kapitel vier verwendet wird. Wenn du sie nicht hast, erstelle sie in deinem Projektordner mit genau diesen Zeilen:
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0Einige Beispiele verwenden auch NumPy, das zusammen mit pandas installiert wurde — ein import numpy as np genügt völlig.
Bevor du den Code schreibst
Eine neue Spalte ist ein kleines Programm. Fünf Minuten Planung sparen eine Stunde Fehlersuche nach der Ursache für ein NaN.
1. Formuliere die Frage in einem Satz pro Spalte. Jede von der Ladenbesitzerin gewünschte Spalte wird zu einer Zeile des Plans:
revenue— wie viel Geld die Bestellung eingebracht hat. Erstellt ausquantity×price.size—"big", wenn der Umsatz mindestens 500 beträgt, andernfalls"small". Erstellt ausrevenue.manager— wer die Filiale leitet, aus der die Bestellung stammt. Erstellt ausbranchüber eine Lookup-Tabelle.weekday— der Wochentag, an dem die Bestellung aufgegeben wurde. Erstellt ausdate.branch_share— der Umsatz dieser Bestellung als Anteil am Gesamtumsatz ihrer Filiale. Erstellt ausrevenueundbranch.
Beachte die eine Sache, die alle gemeinsam haben: ein Wert pro Zeile. Eine neue Spalte ist immer exakt so lang wie die Tabelle. Das ist der Unterschied zu Kapitel acht, wo groupby(...).sum() einen Wert pro Gruppe lieferte. Wenn das, was du berechnest, weniger Werte hat, als die Tabelle Zeilen besitzt, ist es noch keine Spalte.
2. Sieh dir die Eingabe an, bevor du sie anfasst. Welche Werkzeuge du nutzen kannst, hängt von den Datentypen der Ausgangsspalten ab:
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(orders.dtypes)(8, 7)
order_id int64
date str
branch str
product str
category str
quantity int64
price float64
dtype: objectAcht Zeilen; diese Zahl muss auch am Ende noch acht sein. quantity ist int64 und price ist float64, deren Multiplikation führt also echte Arithmetik aus. date ist str — Text, der wie ein Datum aussieht. Text hat keinen Wochentag, muss also umgewandelt werden, bevor .dt darauf verwendet werden kann. Dies jetzt festzuhalten, verhindert den späteren AttributeError.
3. Skizziere die Ausgabe und rechne eine Zeile von Hand durch. Bevor du irgendetwas ausführst, entscheide, was aus zwei der Zeilen werden soll — einer gewöhnlichen und einer extremen:
product quantity price revenue size
pen 12 15.0 180.0 small
bag 2 850.0 1700.0 big12 × 15.0 = 180.0, und 180 liegt unter 500, also small. Eine von Hand geprüfte Zeile ist ein Test: Wenn nach dem Ausführen des Codes in Zeile 0 nicht 180.0 und small steht, ist der Code falsch, ganz gleich, wie plausibel er aussieht.
4. Wähle das Werkzeug, das zur Form der Regel passt. Nahezu jede neue Spalte fällt in eine dieser Kategorien:
- Arithmetik auf Spalten oder einer Zahl →
+ - * /auf ganzen Spalten. Beispiel:quantity * price. - Einer von zwei Werten anhand einer Bedingung →
np.where. Beispiel:"big"oder"small". - Einer von mehreren Werten anhand nacheinander geprüfter Bedingungen →
np.select. Beispiel:"bulk","normal","few". - Ein aus einer festen Tabelle nachgeschlagener Wert →
.map(dict). Beispiel: branch → manager. - In welchen Bereich eine Zahl fällt →
pd.cut. Beispiel: quantity → band. - Ein Teil eines Textes → die
.str-Methoden. Beispiel: die ersten Buchstaben der Kategorie. - Ein Teil eines Datums →
pd.to_datetime, dann.dt. Beispiel: der Wochentag. - Ein Vergleich mit der eigenen Gruppe der Zeile →
groupby(...).transform(...). Beispiel: Anteil an der Filialsumme. - Nichts davon →
apply(axis=1). Beispiel: eine Regel, die zu unregelmäßig für den Rest ist.
Lies die Liste von oben nach unten und halte bei der ersten Zeile an, die passt. Die Reihenfolge ist bewusst gewählt: Die ersten acht arbeiten auf ganzen Spalten auf einmal; die letzte fällt auf einen Python-Aufruf pro Zeile zurück.
5. Entscheide, was du danach überprüfst. Drei Prüfungen, jedes Mal: Die Zeilenanzahl hat sich nicht geändert; die neue Spalte hat den erwarteten Typ; und isna().sum() darauf ist null oder genau die Zahl, die du erklären kannst. Dazu kommt die von Hand geprüfte Zeile.
Arithmetik auf ganzen Spalten
Hier ist die revenue-Spalte auf die pandas-Art:
import pandas as pd
orders = pd.read_csv("orders.csv")
orders["revenue"] = orders["quantity"] * orders["price"]
print(orders[["product", "quantity", "price", "revenue"]])product quantity price revenue
0 pen 12 15.0 180.0
1 notebook 5 60.0 300.0
2 bag 2 850.0 1700.0
3 bottle 7 120.0 840.0
4 eraser 30 8.0 240.0
5 bag 1 850.0 850.0
6 pen 20 15.0 300.0
7 bottle 4 120.0 480.0Zeile 0 zeigt 180.0 — den von Hand geprüften Wert. Keine Schleife, keine Index-Arithmetik.
Warum das funktioniert. orders["quantity"] ist eine Series aus acht Zahlen; ebenso orders["price"]. Setzt du ein * zwischen zwei Series, gleicht pandas deren Werte anhand des Index-Labels ab — Label 0 mit Label 0, Label 1 mit Label 1 — und multipliziert jedes Paar. Die Schleife findet immer noch statt, jedoch im kompilierten Code von pandas, nicht in Python. Deshalb ist es schnell, und deshalb kann es auch nicht die falsche Zeile lesen: Es gibt keinen Zähler, der aus dem Takt geraten könnte.
Anschließend bewirkt orders["revenue"] = ... je nach Name eines von zwei Dingen:
- wenn
revenuenicht existiert, fügt es ganz rechts eine neue Spalte hinzu; - wenn es bereits existiert, ersetzt es diese — stillschweigend. Es gibt kein „Bist du sicher?“
Der Ergebnistyp folgt der Arithmetik: int64 × float64 ergibt float64, weshalb revenue 180.0 statt 180 anzeigt.
Eine Spalte kann auch mit einer einzelnen Zahl kombiniert werden. Die Zahl wird für jede Zeile verwendet — pandas nennt dies Broadcasting:
orders["price_with_vat"] = (orders["price"] * 1.15).round(2)
print(orders[["product", "price", "price_with_vat"]].head(4))product price price_with_vat
0 pen 15.0 17.25
1 notebook 60.0 69.00
2 bag 850.0 977.50
3 bottle 120.0 138.00Die Klammern sind wichtig: .round(2) bezieht sich auf das Gesamtergebnis der Multiplikation, das wiederum eine Series ist. Jeder Schritt hier nimmt eine Spalte entgegen und gibt eine Spalte zurück, sodass du Methoden beliebig weiter verketten kannst.
Der Index entscheidet, welcher Wert wohin gehört
Dieser Abgleich nach Label gilt nicht nur für die Arithmetik. Auch das Zuweisen einer Series an eine Spalte richtet sich nach den Labels aus. Meistens bemerkt man das gar nicht, weil die neuen Werte aus derselben Tabelle stammen und die Labels exakt übereinstimmen. Du bemerkst es an dem Tag, an dem sie es nicht tun.
Angenommen, du möchtest eine Spalte, die die Menge nur für Bestellungen der Filiale north enthält:
north = orders[orders["branch"] == "north"]
orders["north_qty"] = north["quantity"]
print(orders[["branch", "quantity", "north_qty"]])branch quantity north_qty
0 north 12 12.0
1 south 5 NaN
2 north 2 2.0
3 east 7 NaN
4 north 30 30.0
5 south 1 NaN
6 east 20 NaN
7 north 4 4.0north hat die Labels 0, 2, 4, 7. Pandas ordnet jeden Wert der Zeile mit demselben Label zu, und jede andere Zeile erhält NaN — „kein Wert für dieses Label“. Und da NaN ein Float ist, wurde die Spalte zu float64, obwohl Mengen ganze Zahlen sind. Das ist vielleicht genau das, was du wolltest, oder der Beginn einer langen Suche nach fehlenden Daten, die du selbst erzeugt hast. Um das zu unterscheiden, muss man die Regel kennen: Zuweisungen gleichen Labels ab, keine Positionen.
Dieselbe Regel schützt dich auch. Das Sortieren (Kapitel sieben) ordnet eine Series neu an, behält aber jeden Wert mit seinem Label, sodass das erneute Zuweisen einer sortierten Series jeden Wert wieder in seine ursprüngliche Zeile setzt:
by_revenue = orders["revenue"].sort_values()
print(by_revenue.head(3))
orders["revenue_copy"] = by_revenue
print(orders[["product", "revenue", "revenue_copy"]].head(3))
print((orders["revenue_copy"] == orders["revenue"]).all())0 180.0
4 240.0
6 300.0
Name: revenue, dtype: float64
product revenue revenue_copy
0 pen 180.0 180.0
1 notebook 300.0 300.0
2 bag 1700.0 1700.0
TrueDie sortierte Series verläuft in der Reihenfolge der Labels 0, 4, 6 — der zweite Wert, 240.0, gehört zu Zeile 4, nicht zu Zeile 1. Dennoch ist revenue_copy Zeile für Zeile identisch mit revenue, was die letzte Zeile für alle acht Zeilen bestätigt. Pandas hat die Labels verwendet und die Reihenfolge ignoriert. Wäre es nach Positionen vorgegangen, hätte Zeile 1 (das Notizbuch, 300.0) 240.0 erhalten.
Eine reine Python-Liste hat keine Labels, daher wird sie nach Position verwendet — und dann muss ihre Länge exakt übereinstimmen:
try:
orders["rating"] = [5, 4, 3]
except ValueError as error:
print("ValueError:", error)ValueError: Length of values (3) does not match length of index (8)Drei Werte für acht Zeilen werden abgelehnt, anstatt Vermutungen anzustellen. Das ist das sicherste Verhalten überhaupt, und es lohnt sich, das einmal gesehen zu haben, damit die Meldung vertraut ist.
Wir brauchen die Testspalten nicht mehr. drop(columns=...) gibt die Tabelle ohne sie zurück:
orders = orders.drop(columns=["north_qty", "revenue_copy", "price_with_vat"])
print(list(orders.columns))['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price', 'revenue']assign(): Neue Spalten, ohne das Original zu verändern
orders["revenue"] = ... verändert orders selbst. Oft ist es genau das, was du willst. Manchmal aber nicht — du möchtest ein paar Spalten ausprobieren oder eine Berichtstabelle erstellen und das Original exakt so behalten, wie es eingelesen wurde. assign() gibt einen neuen DataFrame mit den zusätzlichen Spalten zurück und lässt das Original unberührt:
import numpy as np
import pandas as pd
orders = pd.read_csv("orders.csv")
report = orders.assign(
revenue=orders["quantity"] * orders["price"],
vat=lambda d: (d["revenue"] * 0.15).round(2),
)
print(report[["product", "revenue", "vat"]].head(3))
print("revenue" in orders.columns)product revenue vat
0 pen 180.0 27.0
1 notebook 300.0 45.0
2 bag 1700.0 255.0
FalseDrei Dinge fallen auf. (Das import numpy as np ganz oben ist für den nächsten Abschnitt.)
Die Argumentnamen werden zu den Spaltennamen — revenue=... erzeugt eine Spalte namens revenue. Deshalb müssen die Spaltennamen gültige Python-Bezeichner sein, wenn du assign() verwendest.
vat ist als lambda geschrieben. Das muss so sein: vat benötigt revenue, und revenue existiert in orders noch nicht — es existiert nur in der neu entstehenden Tabelle. Ein lambda d: ... empfängt diese neue Tabelle als d, sodass d["revenue"] bereits vorhanden ist. Innerhalb eines einzigen assign() werden Spalten von oben nach unten hinzugefügt, und jedes lambda sieht die darüberliegenden Spalten.
Und die letzte Zeile gibt False aus: orders hat keine Spalte revenue. Nichts wurde hinter deinem Rücken verändert.
np.where: Einer von zwei Werten
„big, wenn der Umsatz mindestens 500 beträgt, andernfalls small“ ist ein if/else für jede Zeile. Ein if zu schreiben würde bedeuten, zu einer Schleife zurückzukehren, denn ein if benötigt ein einzelnes True oder False, und der Vergleich einer ganzen Spalte liefert acht davon (das ist der Fehler „truth value of a Series is ambiguous“ aus Kapitel fünf).
NumPys where ist ein if/else, das auf einer ganzen Spalte arbeitet: Dort, wo (where) die Bedingung True ist, nimm den ersten Wert; andernorts den zweiten.
report["size"] = np.where(report["revenue"] >= 500, "big", "small")
print(report[["product", "revenue", "size"]])product revenue size
0 pen 180.0 small
1 notebook 300.0 small
2 bag 1700.0 big
3 bottle 840.0 big
4 eraser 240.0 small
5 bag 850.0 big
6 pen 300.0 small
7 bottle 480.0 smallZeile 0 ist small, Zeile 2 ist big — beide stimmen mit der Skizze überein. Die Bedingung ist dieselbe Art von boolescher Maske, die du in Kapitel fünf erstellt hast; np.where wandelt jedes True/False einfach in einen Wert um. Der Grenzfall lohnt eine Prüfung: >= 500 bedeutet, dass eine Bestellung von genau 500 big ist. Entscheide so etwas bewusst, nicht zufällig.
Mehr als zwei Werte: np.select
Drei Stufen nach Menge — 20 oder mehr ist "bulk", 5 oder mehr ist "normal", alles andere ist "few". np.select nimmt eine Liste von Bedingungen, eine Liste passender Werte und ein default für Zeilen entgegen, auf die keine Bedingung zutrifft:
conditions = [
report["quantity"] >= 20,
report["quantity"] >= 5,
]
labels = ["bulk", "normal"]
report["tier"] = np.select(conditions, labels, default="few")
print(report[["product", "quantity", "tier"]])product quantity tier
0 pen 12 normal
1 notebook 5 normal
2 bag 2 few
3 bottle 7 normal
4 eraser 30 bulk
5 bag 1 few
6 pen 20 bulk
7 bottle 4 fewDie erste Bedingung, die True ist, gewinnt. Deshalb steht die strengste Bedingung an erster Stelle. Vertausche die Reihenfolge und sieh, was mit der Radiergummi-Bestellung über 30 Stück passiert:
wrong = np.select(
[report["quantity"] >= 5, report["quantity"] >= 20],
["normal", "bulk"],
default="few",
)
print(wrong)['normal' 'normal' 'few' 'normal' 'normal' 'few' 'normal' 'few']Jede Bestellung ist normal oder few; nichts ist bulk, weil 30 und 20 ebenfalls >= 5 sind und diese Bedingung zuerst geprüft wurde. Kein Fehler, nur eine falsche Spalte — weshalb die von Hand geprüfte Zeile im Plan immer auch einen Extremwert enthalten sollte.
.map(): Werte nachschlagen
Die Filialleitung wird aus nichts berechnet. Es ist eine bekannte Tatsache: Asha leitet die Filiale north, Omar die south, Lina die east. Wenn die Regel „nachschlagen“ lautet, schreibe die Lookup-Tabelle als Dictionary und leite die Spalte mit .map() hindurch:
manager_of = {
"north": "Asha",
"south": "Omar",
"east": "Lina",
}
report["manager"] = report["branch"].map(manager_of)
print(report[["branch", "manager"]])
print(report["manager"].isna().sum())branch manager
0 north Asha
1 south Omar
2 north Asha
3 east Lina
4 north Asha
5 south Omar
6 east Lina
7 north Asha
0Jede Filiale wurde durch ihren Wert aus dem Dictionary ersetzt — den Namen der Filialleitung. Das zweite print ist die Prüfung aus dem Plan, und sie ist wichtiger, als es scheint, wegen des Verhaltens von .map() bei Schlüsseln, die es nicht finden kann. Stell dir vor, das Dictionary wäre geschrieben worden, bevor die Filiale east eröffnet wurde:
old_managers = {"north": "Asha", "south": "Omar"}
print(report["branch"].map(old_managers).isna().sum())2Kein Fehler. Die beiden east-Bestellungen haben einfach NaN erhalten. Bei acht Zeilen fällt das auf; bei achtzigtausend nicht, und ein späteres groupby("manager") würde diese Bestellungen stillschweigend weglassen (Kapitel acht: NaN-Schlüssel werden verworfen). Hinter jedes .map() gehört direkt ein isna().sum().
pd.cut: In welchen Bereich eine Zahl fällt
Das Geschäft möchte jede Bestellung nach Menge in Klassen einteilen: bis zu 5 Artikel ist small, 6 bis 15 ist medium, mehr als 15 ist large. Das könntest du mit np.select schreiben, aber für „in welchen Bereich“ gibt es ein eigenes Werkzeug. pd.cut nimmt die Grenzen der Bereiche und ein Label für jeden Bereich entgegen:
report["band"] = pd.cut(
report["quantity"],
bins=[0, 5, 15, np.inf],
labels=["small", "medium", "large"],
)
print(report[["product", "quantity", "band"]])product quantity band
0 pen 12 medium
1 notebook 5 small
2 bag 2 small
3 bottle 7 medium
4 eraser 30 large
5 bag 1 small
6 pen 20 large
7 bottle 4 smallVier Grenzen ergeben drei Bereiche: (0, 5], (5, 15] und (15, ∞]. Die runde Klammer bedeutet „ausschließlich“, die eckige Klammer bedeutet „einschließlich“ — jeder Bereich schließt also seine rechte Grenze ein. Deshalb ist die Notizbuch-Bestellung von genau 5 Stück small, nicht medium. np.inf steht für unendlich, eine Obergrenze, die keine Menge überschreiten kann.
Das Ergebnis hat einen Datentyp, den du bisher noch nicht gesehen hast:
print(report["band"].dtype)
print(report["band"].cat.categories.tolist())category
['small', 'medium', 'large']Eine category-Spalte merkt sich, dass small < medium < large gilt. Eine Sortierung nach band liefert daher diese logische Reihenfolge statt der alphabetischen — was man in einem Bericht meistens haben möchte.
Text und Datumsangaben: .str und .dt
Textteile
Kapitel fünf nutzte .str.contains zum Filtern. Derselbe .str-Accessor erstellt auch Spalten: Jede String-Methode, die du aus Python kennst, wird auf alle Werte gleichzeitig angewendet. Angenommen, jede Bestellung benötigt einen kurzen Code wie STA-1001 — die ersten drei Buchstaben der Kategorie in Großbuchstaben, ein Bindestrich, dann die Bestellnummer:
report["code"] = (
report["category"].str[:3].str.upper()
+ "-"
+ report["order_id"].astype(str)
)
print(report[["category", "order_id", "code"]].head(3))category order_id code
0 stationery 1001 STA-1001
1 stationery 1002 STA-1002
2 accessories 1003 ACC-1003.str[:3] schneidet jeden Wert zu (Slice), .str.upper() wandelt jeden Wert in Großbuchstaben um, und + zwischen Textspalten fügt sie Zeile für Zeile zusammen. order_id ist eine Zahl, und eine Zahl kann nicht an Text angehängt werden, daher wandelt .astype(str) sie zuerst in Text um.
Teile eines Datums
Der Plan hat bereits davor gewarnt, dass date vom Typ str ist. Wandle es mit pd.to_datetime um, und schon steht dir der .dt-Accessor zur Verfügung:
report["date"] = pd.to_datetime(report["date"])
print(report["date"].dtype)
report["weekday"] = report["date"].dt.day_name()
print(report[["date", "weekday"]].head(4))datetime64[us]
date weekday
0 2024-03-01 Friday
1 2024-03-01 Friday
2 2024-03-02 Saturday
3 2024-03-02 SaturdayDie Spalte ist nun datetime64 — ein echtes Datum, kein Text. (Das [us] ist die Genauigkeit, die pandas 3 dafür gewählt hat; du kannst es ignorieren.) Bei einem echten Datum liefert .dt dir .dt.day_name(), .dt.month, .dt.year, .dt.day und mehr. Und die Umwandlung wird einmal durchgeführt und wieder date zugewiesen, sodass jeder nachfolgende Schritt ein Datum und keinen String erhält.
transform: Eine Zeile mit ihrer Gruppe vergleichen
Der letzte Wunsch — der Anteil jeder Bestellung am Umsatz ihrer Filiale — erfordert zwei Dinge in derselben Zeile: den Umsatz der Bestellung und die Gesamtsumme ihrer Filiale. Kapitel acht lieferte uns die Gesamtsummen:
print(report.groupby("branch")["revenue"].sum())branch
east 1140.0
north 2600.0
south 1150.0
Name: revenue, dtype: float64Das sind jedoch drei Werte, einer pro Filiale, und die Tabelle hat acht Zeilen. Du kannst acht Zahlen nicht durch drei teilen. Was du brauchst, ist die Filialsumme, wiederholt in jeder Zeile dieser Filiale. Genau das gibt transform zurück:
branch_total = report.groupby("branch")["revenue"].transform("sum")
print(branch_total)0 2600.0
1 1150.0
2 2600.0
3 1140.0
4 2600.0
5 1150.0
6 1140.0
7 2600.0
Name: revenue, dtype: float64Dieselbe Gruppierung, dasselbe "sum", aber das Ergebnis hat acht Werte mit den tabelleneigenen Labels — jede north-Zeile trägt 2600, jede south-Zeile 1150. Nun ist es gewöhnliche Spaltenarithmetik:
report["branch_share"] = (report["revenue"] / branch_total).round(3)
print(report[["branch", "product", "revenue", "branch_share"]])branch product revenue branch_share
0 north pen 180.0 0.069
1 south notebook 300.0 0.261
2 north bag 1700.0 0.654
3 east bottle 840.0 0.737
4 north eraser 240.0 0.092
5 south bag 850.0 0.739
6 east pen 300.0 0.263
7 north bottle 480.0 0.185Prüfe es nach: Die Taschenbestellung in der Filiale north ergibt 1700 / 2600 = 0.654. Und die Anteile innerhalb jeder Filiale müssen sich zu 1 aufsummieren:
print(report.groupby("branch")["branch_share"].sum())branch
east 1.0
north 1.0
south 1.0
Name: branch_share, dtype: float64Die einprägsame Regel: agg/sum reduzieren eine Gruppe auf eine Zeile; transform gibt für jede Zeile einen Wert zurück. Nutze Ersteres für eine Übersichtstabelle, Letzteres für eine neue Spalte.
apply(axis=1): Der letzte Ausweg
Manchmal ist die Regel tatsächlich unregelmäßig. Die Liefergebühr lautet: in der Filiale north — der Hauptfiliale neben dem Lager — kostenlos bei einem Umsatz von mindestens 500, sonst 60; in den anderen Filialen 120, aber kostenlos über 1000. Du kannst das als gewöhnliche Python-Funktion schreiben und mit apply auf jede Zeile anwenden:
def delivery_fee(row):
if row["branch"] == "north":
return 0 if row["revenue"] >= 500 else 60
return 0 if row["revenue"] > 1000 else 120
report["fee"] = report.apply(delivery_fee, axis=1)
print(report[["branch", "revenue", "fee"]])branch revenue fee
0 north 180.0 60
1 south 300.0 120
2 north 1700.0 0
3 east 840.0 120
4 north 240.0 60
5 south 850.0 120
6 east 300.0 120
7 north 480.0 60axis=1 bedeutet „übergib der Funktion jeweils eine Zeile auf einmal“. Im Inneren ist row eine Series, deren Labels den Spaltennamen entsprechen, sodass row["branch"] funktioniert.
Es ist leicht zu lesen, und genau darin liegt sein ganzer Reiz. Der Preis dafür ist, dass es wieder die Schleife vom Anfang des Kapitels ist: pandas erstellt für jede Zeile eine Series und ruft deine Python-Funktion einmal pro Zeile auf. Bei acht Zeilen ist das nichts; bei einer Million Zeilen ist es der Unterschied zwischen einem Wimpernschlag und einer Kaffeepause.
Versuche daher, bevor du zu apply greifst, die Regel mit Werkzeugen für ganze Spalten zu formulieren. Diese Regel bedeutet eigentlich „drei Fälle, der Reihe nach geprüft“ — np.select:
in_north = report["branch"] == "north"
fee_fast = np.select(
[in_north & (report["revenue"] >= 500), in_north, report["revenue"] > 1000],
[0, 60, 0],
default=120,
)
print((fee_fast == report["fee"]).all())TrueIdentisches Ergebnis, kein Python-Aufruf pro Zeile. Behalte apply(axis=1) für Regeln vor, die sich wirklich nicht so formulieren lassen — das Aufrufen einer externen Funktion, das Parsen eines unordentlichen Strings in mehreren Schritten — und betrachte den Griff danach als Signal, noch einmal nachzudenken.
Einer gefilterten Tabelle eine Spalte hinzufügen: Copy-on-Write
Oft filterst du zuerst und fügst dem Ergebnis anschließend eine Spalte hinzu. In pandas 3 ist das unkompliziert, und es lohnt sich, den Grund dafür zu verstehen, da ältere Tutorials etwas anderes behaupten.
import pandas as pd
orders = pd.read_csv("orders.csv")
orders["revenue"] = orders["quantity"] * orders["price"]
north = orders[orders["branch"] == "north"]
north["revenue_k"] = north["revenue"] / 1000
print(north[["product", "revenue", "revenue_k"]])
print(list(orders.columns))product revenue revenue_k
0 pen 180.0 0.18
2 bag 1700.0 1.70
4 eraser 240.0 0.24
7 bottle 480.0 0.48
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price', 'revenue']Keine Warnung, und orders hat keine Spalte revenue_k erhalten. Pandas 3 verwendet Copy-on-Write: Jedes Ergebnis einer Filterung oder Auswahl verhält sich wie eine eigene, unabhängige Tabelle. Eine Änderung an north kann orders niemals verändern.
Wenn du älteren Code oder Antworten im Internet liest, wirst du genau dieses Muster gefolgt von einer SettingWithCopyWarning sehen („A value is trying to be set on a copy of a slice from a DataFrame“) sowie den Ratschlag, orders[...].copy() zu schreiben. Diese Warnung existierte, weil ältere pandas-Versionen manchmal Daten zwischen den beiden Tabellen teilten, und niemand leicht vorhersagen konnte, wann. Pandas 3 hat diese Ungewissheit beseitigt und die Warnung gleich mit. Das Schreiben von .copy() ist nun unnötig, wenn auch unschädlich.
Was Copy-on-Write jedoch nicht erlaubt, ist das Verändern von orders über eine Kette aus zwei Schritten. Wenn du Werte in der ursprünglichen Tabelle für bestimmte Zeilen ändern möchtest, bewirkt Folgendes nichts:
orders[orders["branch"] == "north"]["revenue"] = 0
print(orders["revenue"].tolist())[180.0, 300.0, 1700.0, 840.0, 240.0, 850.0, 300.0, 480.0]
ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentDie erste eckige Klammer erzeugt eine neue Tabelle, die zweite setzt Werte in dieser neuen Tabelle, und die neue Tabelle wird verworfen. Pandas warnt dich mit einem ChainedAssignmentError, und die Liste zeigt revenue unverändert — die north-Bestellungen lauten immer noch 180.0, 1700.0, 240.0 und 480.0. (Warnungen gehen in den Fehler-Stream, daher kann die Warnung in deinem Terminal über statt unter der Liste erscheinen.) Um das Original zu verändern, führe es in einem Schritt mit .loc[rows, column] aus:
orders.loc[orders["branch"] == "north", "note"] = "main branch"
print(orders[["branch", "note"]])branch note
0 north main branch
1 south NaN
2 north main branch
3 east NaN
4 north main branch
5 south NaN
6 east NaN
7 north main branchZeilen, die übereinstimmen, erhalten den Wert; die Spalte wird bei Bedarf erstellt; Zeilen, die nicht übereinstimmen, erhalten NaN. Ein Schritt, die ursprüngliche Tabelle, keine Warnung.
Ein vollständiges Beispiel
enrich.py beantwortet die gesamte Nachricht der Ladenbesitzerin und folgt dabei dem Plan: einlesen, prüfen, Spalten hinzufügen, erneut prüfen.
import numpy as np
import pandas as pd
MANAGER_OF = {
"north": "Asha",
"south": "Omar",
"east": "Lina",
}
orders = pd.read_csv("orders.csv")
rows_before = len(orders)
# 1. Conversions first, so later steps get the right types.
orders["date"] = pd.to_datetime(orders["date"])
# 2. New columns, one rule each.
orders["revenue"] = orders["quantity"] * orders["price"]
orders["size"] = np.where(orders["revenue"] >= 500, "big", "small")
orders["manager"] = orders["branch"].map(MANAGER_OF)
orders["weekday"] = orders["date"].dt.day_name()
branch_total = orders.groupby("branch")["revenue"].transform("sum")
orders["branch_share"] = (orders["revenue"] / branch_total).round(3)
# 3. Checks: same rows, nothing missing, the hand-worked row is right.
new_columns = ["revenue", "size", "manager", "weekday", "branch_share"]
assert len(orders) == rows_before
assert orders[new_columns].isna().sum().sum() == 0
assert orders.loc[0, "revenue"] == 180.0
print(orders[["order_id", "branch", "manager", "weekday", "revenue", "size", "branch_share"]])
print()
print(orders[new_columns].dtypes)order_id branch manager weekday revenue size branch_share
0 1001 north Asha Friday 180.0 small 0.069
1 1002 south Omar Friday 300.0 small 0.261
2 1003 north Asha Saturday 1700.0 big 0.654
3 1004 east Lina Saturday 840.0 big 0.737
4 1005 north Asha Sunday 240.0 small 0.092
5 1006 south Omar Sunday 850.0 big 0.739
6 1007 east Lina Monday 300.0 small 0.263
7 1008 north Asha Monday 480.0 small 0.185
revenue float64
size str
manager str
weekday str
branch_share float64
dtype: objectWarum es so geschrieben ist:
- Die Konvertierung kommt zuerst.
datewird in ein echtes Datum umgewandelt, bevor irgendetwas darauf zugreift, sodass.dtspäter nicht fehlschlagen kann und niemand in späteren Schritten an die Umwandlung denken muss. - Eine Regel pro Zeile. Jede Zeile beantwortet genau eine Zeile des Plans. Sieht eine Zahl falsch aus, weißt du sofort, welche einzelne Zeile sie erzeugt hat.
- Die Lookup-Tabelle ist eine benannte Konstante ganz oben. Wenn das Geschäft eine Filiale west eröffnet, ist die Änderung eine einzige Zeile an einer offensichtlichen Stelle, und die
isna-Prüfung schlägt unübersehbar fehl, falls jemand es vergisst. - Die Prüfungen sind
assert-Anweisungen, keine Prints. Ändert sich die Zeilenzahl, verfehlt ein.map()eine Filiale oder ist Zeile 0 nicht mehr 180, bricht das Programm ab, anstatt einen falschen Bericht auszugeben, der auf den ersten Blick gut aussieht. dtypeswird am Ende ausgegeben, weil der Typ Teil der Antwort ist:revenuesollte ein Float sein,branch_shareein Float, die Textspaltenstr.
Wenn etwas schiefgeht
KeyError: 'Price' Ein Spaltenname auf der rechten Seite ist falsch geschrieben oder hat die falsche Groß-/Kleinschreibung. Gib list(orders.columns) aus und kopiere den Namen von dort. Achte auch auf die linke Seite: orders["revenu"] = ... löst keinen Fehler aus — es erzeugt eine neue, falsch geschriebene Spalte neben der beabsichtigten. Wenn sich eine Spalte, die du „aktualisiert“ hast, nicht geändert hat, suche nach einem Zwilling mit Tippfehler.
ValueError: Length of values (3) does not match length of index (8) Du hast eine Liste (oder ein Array) zugewiesen, deren Länge nicht mit der Zeilenanzahl übereinstimmt. Eine Liste hat keine Labels, sodass pandas sie nicht abgleichen kann und die Zuweisung verweigert. Erstelle die Werte entweder aus den eigenen Spalten der Tabelle oder prüfe vorher len(values) == len(df).
Die neue Spalte ist in manchen Zeilen voller NaN, und der Typ hat sich in float64 geändert Du hast eine Series zugewiesen, deren Labels nur teilweise mit der Tabelle übereinstimmen — meist eine, die aus einer gefilterten Tabelle erstellt wurde. Zuweisungen gleichen Labels ab, keine Positionen. Erstelle die neue Spalte aus der vollständigen Tabelle (mit np.where oder .loc[mask, "col"] = ...) statt aus einer Teilmenge.
revenue enthält Dinge wie 15.015.015.0…, oder TypeError: can't multiply sequence by non-int of type 'float' Eine der Spalten enthält Text, keine Zahl. Die Multiplikation eines int mit einem String wiederholt den String — die Python-Regel 3 * "ab" == "ababab" — und pandas wendet dies Zeile für Zeile ohne Fehlermeldung an:
import pandas as pd
from io import StringIO
RAW = """product,quantity,price
pen,12,15.0
bag,2,"1,200"
"""
bad = pd.read_csv(StringIO(RAW))
print(bad.dtypes)
print(bad["quantity"] * bad["price"])product str
quantity int64
price str
dtype: object
0 15.015.015.015.015.015.015.015.015.015.015.015.0
1 1,2001,200
dtype: strDas Tausendertrennzeichen (Komma) in "1,200" hat die gesamte Spalte price zu str gemacht. Genau deshalb empfiehlt der Plan, die dtypes vor der Arithmetik zu prüfen. Behebe das Problem dort, wo die Datei eingelesen wird, mit thousands=",", oder konvertiere mit pd.to_numeric:
good = pd.read_csv(StringIO(RAW), thousands=",")
print(good.dtypes["price"])
print(good["quantity"] * good["price"])float64
0 180.0
1 2400.0
dtype: float64AttributeError: Can only use .dt accessor with datetimelike values Die Spalte ist immer noch Text. Konvertiere sie zuerst mit df["date"] = pd.to_datetime(df["date"]) und verwende erst danach .dt.
KeyError: 'quantity' von apply Du hast axis=1 vergessen. Ohne diese Angabe übergibt apply ganze Spalten an deine Funktion, sodass row["quantity"] nach einer mit quantity beschrifteten Zeile innerhalb einer Spalte sucht und fehlschlägt. Füge axis=1 hinzu — und frage dich dann, ob du apply überhaupt benötigst.
IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer Du hast .astype(int) auf einer Spalte aufgerufen, die NaN enthält. Ein NumPy-Integer kann „fehlend“ nicht speichern. Fülle die Lücken zuerst (Kapitel sechs) oder verwende den Nullable-Integer-Typ von pandas, .astype("Int64") mit großem I, der fehlende Werte enthalten kann.
ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment. Du hast df[mask]["col"] = value geschrieben. Unter Copy-on-Write erzeugt der erste Schritt eine separate Tabelle, sodass sich das Original nie ändert. Schreibe es in einem einzigen Schritt: df.loc[mask, "col"] = value.
Eine .map()-Spalte enthält NaN, wo du einen Wert erwartet hast Ein Schlüssel in der Spalte ist nicht im Dictionary enthalten — eine neue Filiale, eine andere Schreibweise, ein nachgestelltes Leerzeichen. Finde sie mit df.loc[df["new"].isna(), "key_column"].unique() und korrigiere dann das Dictionary oder die Daten.
Schritt 4 von 6 — Vorhersagen
Prüfe dein Verständnis
Eine Spalte, die aus den Bestellungen der Filiale north erstellt wurde, wird der gesamten Tabelle zugewiesen. Was wird ausgegeben?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
north = orders[orders["branch"] == "north"]
orders["north_qty"] = north["quantity"]
print(orders["north_qty"].isna().sum(), orders["north_qty"].dtype)- A4 int64
- B0 int64
- C0 float64
- D4 float64
Das Ziel ist, orders eine Spalte revenue hinzuzufügen. Was wird ausgegeben?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
orders.assign(revenue=orders["quantity"] * orders["price"])
print("revenue" in orders.columns)- A`False` — `assign` gibt eine neue Tabelle zurück und lässt `orders` unverändert, und das Ergebnis wurde nicht behalten
- BEin `KeyError: 'revenue'`
- CEin `ChainedAssignmentError`
- D`True`
Du benötigst eine Spalte, die in jeder Zeile den Anteil dieser Bestellung an der Gesamtmenge ihrer Filiale enthält. Welche Zeile liefert einen Wert pro Zeile?
- Aorders["quantity"] / orders.groupby("branch")["quantity"].sum()
- Borders["quantity"] / orders.groupby("branch")["quantity"].transform("sum")
- Corders.groupby("branch")["quantity"].sum() / orders["quantity"]
- Dorders["quantity"] / orders["quantity"].sum()
Zum Antworten brauchst du ein Konto
Melde dich an, um deine Antworten zu prüfen
Die Fragen stehen oben, und sie im Kopf durchzuarbeiten ist der wichtige Teil. Melde dich an, um die Antworten, die Erklärungen und die dreistufigen Hinweise zu sehen.
Jetzt bist du dran
Schreibe unter Verwendung derselben orders.csv das Skript enrich_task.py, das der Ladenbesitzerin einen Rabattbericht liefert. Plane zuerst — schreibe die Liste der neuen Spalten auf und rechne Zeile 0 von Hand durch — und füge dann diese Spalten in genau dieser Reihenfolge hinzu:
revenue=quantity×pricediscount= 10 % vonrevenue, wennquantity10 oder mehr beträgt, andernfalls 0net=revenue−discountmanager— anhand der Filiale unter Verwendung des Dictionarys aus diesem Kapitelband— anhand vonnet: bis zu 300 ist"low", über 300 bis 800 ist"mid", über 800 ist"high"weekday— ausdatecategory_share— dasnetjeder Bestellung als Anteil am gesamtennetihrer Kategorie, gerundet auf 2 Nachkommastellen
Gib shape vor und nach den Schritten aus, anschließend order_id, product, quantity, net, band, weekday und category_share, sortiert nach net vom höchsten zum niedrigsten Wert.
Dein Programm ist korrekt, wenn:
- Es exakt die untenstehende Ausgabe liefert.
- Weder eine
for-Schleife nochapplyvorkommt — jede Spalte wird aus ganzen Spalten auf einmal erstellt. - Falls eine Filiale im Dictionary fehlt, das Programm mit einem
AssertionErrorabbricht, anstatt mitNaNfortzufahren. - Die Zeilenanzahl sowohl vorher als auch nachher 8 beträgt.
Erwartete Ausgabe:
before: (8, 7)
after: (8, 14)
order_id product quantity net band weekday category_share
2 1003 bag 2 1700.0 high Saturday 0.44
5 1006 bag 1 850.0 high Sunday 0.22
3 1004 bottle 7 840.0 high Saturday 0.22
7 1008 bottle 4 480.0 mid Monday 0.12
1 1002 notebook 5 300.0 low Friday 0.32
6 1007 pen 20 270.0 low Monday 0.28
4 1005 eraser 30 216.0 low Sunday 0.23
0 1001 pen 12 162.0 low Friday 0.17Bevor du es ausführst, triff eine Vorhersage: Welche Bestellung hat das höchste net, und in welchem Band liegt die Radiergummi-Bestellung über 30 Stück? Überprüfe dies anschließend anhand der Ausgabe.
Lösung
Der Plan. Zeile 0 von Hand: die Stiftebestellung, 12 × 15.0 = 180.0; die Menge ist 12, also beträgt der Rabatt 18.0 und net ist 162.0; 162 ist höchstens 300, also low. Die Radiergummi-Bestellung: 30 × 8.0 = 240.0, Rabatt 24.0, net 216.0 — ebenfalls low, obwohl es nach Menge die größte Bestellung ist. Das höchste net sollte die Taschenbestellung über 2 Stück sein: 1700.0, kein Rabatt.
revenueundnet— Spaltenarithmetik: einfache Multiplikation und Subtraktion.discount—np.where: einer von zwei Werten pro Zeile.manager—.map(dict), gefolgt vonisna().sum(): ein Nachschlagen mit anschließender Prüfung.band—pd.cutmit den Grenzen[0, 300, 800, inf]: Bereiche. Jeder Bereich schließt seine rechte Grenze ein, daher ist genau 300lowund genau 800mid.weekday—pd.to_datetime, dann.dt.day_name(): Datumsteile erfordern ein echtes Datum.category_share—groupby("category")["net"].transform("sum"): ein Wert pro Zeile, verglichen mit der eigenen Gruppe.
enrich_task.py:
import numpy as np
import pandas as pd
MANAGER_OF = {
"north": "Asha",
"south": "Omar",
"east": "Lina",
}
orders = pd.read_csv("orders.csv")
print("before:", orders.shape)
orders["revenue"] = orders["quantity"] * orders["price"]
orders["discount"] = np.where(orders["quantity"] >= 10, orders["revenue"] * 0.10, 0.0)
orders["net"] = orders["revenue"] - orders["discount"]
orders["manager"] = orders["branch"].map(MANAGER_OF)
assert orders["manager"].isna().sum() == 0
orders["band"] = pd.cut(
orders["net"],
bins=[0, 300, 800, np.inf],
labels=["low", "mid", "high"],
)
orders["date"] = pd.to_datetime(orders["date"])
orders["weekday"] = orders["date"].dt.day_name()
category_total = orders.groupby("category")["net"].transform("sum")
orders["category_share"] = (orders["net"] / category_total).round(2)
print("after: ", orders.shape)
print()
columns = ["order_id", "product", "quantity", "net", "band", "weekday", "category_share"]
print(orders[columns].sort_values("net", ascending=False))before: (8, 7)
after: (8, 14)
order_id product quantity net band weekday category_share
2 1003 bag 2 1700.0 high Saturday 0.44
5 1006 bag 1 850.0 high Sunday 0.22
3 1004 bottle 7 840.0 high Saturday 0.22
7 1008 bottle 4 480.0 mid Monday 0.12
1 1002 notebook 5 300.0 low Friday 0.32
6 1007 pen 20 270.0 low Monday 0.28
4 1005 eraser 30 216.0 low Sunday 0.23
0 1001 pen 12 162.0 low Friday 0.17Der Abgleich mit dem Plan:
- Shape stieg von 7 auf 14 Spalten und blieb bei 8 Zeilen: sieben neue Spalten, keine Zeilen gewonnen oder verloren.
- Zeile 0 (Bestellung 1001) hat ein
netvon162.0und das Bandlow— die von Hand berechneten Werte. - Das höchste
nethat Bestellung 1003, die Tasche, mit1700.0, wie vorhergesagt. Die Radiergummi-Bestellung 1005 liegt mit216.0beilow: die meisten Artikel, aber günstige. np.whereakzeptierte eine Spalte als „True“-Wert —orders["revenue"] * 0.10—, sodass jede Zeile ihre eigenen 10 % erhielt, keine einzelne Zahl. Beide Zweige vonnp.wherekönnen Spalten oder feste Werte sein.- Die Grenze eines Bandes wird sichtbar. Die Notizbuch-Bestellung hat ein
netvon genau300.0und istlow, weil(0, 300]die 300 einschließt. Hätte die Ladenbesitzerin gemeint „ab 300 ist mid“, müssten die Grenzen angepasst werden — eine fachliche Entscheidung, kein Detail. - Die Anteile summieren sich innerhalb jeder Kategorie zu 1. Zubehör:
0.44 + 0.22 + 0.22 + 0.12 = 1.00. Schreibwaren:0.32 + 0.28 + 0.23 + 0.17 = 1.00. Hätten sich die Anteile einer Kategorie nicht zu 1 aufsummiert, wäre dastransformnach der falschen Spalte gruppiert gewesen.
Provoziere danach absichtlich Fehler, jeweils einmal: Entferne "east" aus dem Dictionary und beobachte, wie das assert das Programm anhält; ändere bins so, dass es bei 200 statt bei 0 beginnt, und sieh dir an, welche Zeilen in band ein NaN erhalten und warum.
Schritt 6 von 6
Herausforderung — das Kapitel-Quiz
Zehn Fragen von leicht bis schwer. Die letzten sind absichtlich knifflig.
Melde dich an, um das Quiz zu machen