Kolommen en rijen selecteren — op naam of op plek
Aanwijzen in een tabel: kolommen met vierkante haken, rijen op label met loc en op positie met iloc, losse cellen met at — en weten welke van de twee manieren je gebruikt, want na een selectie of sortering komen ze niet meer overeen.
Hoe wil je leren?
Het probleem dat we oplossen
Je hebt het bestelbestand van de winkel ingelezen en gecontroleerd, zoals je in hoofdstuk drie hebt geleerd. Nu beginnen mensen je vragen te stellen.
Het magazijnteam zegt: "Stuur ons gewoon het product en de hoeveelheid van elke bestelling — verder niets." De boekhouder zegt: "Wat was bestelling 1006 precies, en wat heeft die gekost?" En je manager zegt: "Laat me de laatste drie bestellingen zien die zijn binnengekomen."
Geen van deze vragen vraagt om een berekening over de hele tabel. Elk gaat over aanwijzen: een paar kolommen, één rij, de laatste paar rijen, één cel. Alles printen en met je vinger aflezen werkt bij acht rijen. Dat werkt niet bij achtduizend, en ook niet binnen een programma, want een programma heeft geen vinger.
Pandas heeft meerdere manieren om aan te wijzen, en de reden dat er meerdere zijn, is het echte onderwerp van dit hoofdstuk. Je kunt een rij op twee manieren aanwijzen: via zijn naam (zijn label in de index) of via zijn positie (eerste, tweede, laatste). Bij een vers ingelezen tabel zijn die toevallig dezelfde getallen, dus beginners merken nooit dat er twee zijn. Zodra je de laatste drie rijen neemt, sorteert of filtert, zijn ze niet meer gelijk — en code die ze door elkaar haalde, geeft de verkeerde rij terug zonder een enkele foutmelding.
Dit hoofdstuk gaat over selecteren, maar vooral over altijd weten welke van de twee je gebruikt.
Aan het einde van dit hoofdstuk kun je
- Één kolom selecteren en een
Seriesterugkrijgen, of meerdere en eenDataFrame— en weten wat je in handen hebt - Rijen selecteren op label met
.locen op positie met.iloc, en uitleggen waarom hun slices anders eindigen - Rijen en kolommen samen selecteren met
.loc[rows, columns]en de vorm van het resultaat voorspellen — tot aan één enkele waarde met.locof.at - Rijen betekenisvolle labels geven met
set_index(), en zien hoe dat de betekenis van.locverandert - Uitleggen waarom een selectie wijzigen de originele tabel niet verandert in pandas 3, en de originele tabel correct aanpassen; de foutmeldingen
KeyErrorenIndexErrordie een selectie produceert lezen en oplossen
Voorkennis: Een CSV inlezen en controleren wat er is aangekomen.
Maak het bestand eerst aan
Maak in je projectmap het bestand orders.csv aan met precies deze negen regels. Dit bestand wordt voor de rest van de cursus gebruikt, dus sla het op een plek op waar je het terugvindt:
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.0Elke regel is één bestelling: een bestelnummer, de dag, de vestiging van de winkel die het heeft verkocht, wat er is verkocht, de productcategorie, hoeveel, en de prijs per stuk.
Voordat je de code schrijft
Selecteren lijkt zo klein dat mensen meteen haakjes beginnen te typen. Neem eerst één minuut voor de volgende stappen, want elke selectiefout in dit hoofdstuk ontstaat doordat iemand er één heeft overgeslagen.
1. Formuleer de vraag in één zin en onderstreep de zelfstandige naamwoorden. Die zelfstandige naamwoorden vertellen je wat je aanwijst:
- "het product en de hoeveelheid van elke bestelling" — twee kolommen, alle rijen
- "bestelling 1006" — één rij, aangeduid via het bestelnummer
- "de laatste drie bestellingen" — rijen op positie, geteld vanaf het einde
- "de prijs van bestelling 1006" — één cel: een rij en een kolom samen
2. Bekijk de tabel voordat je erin wijst. Je kunt alleen wijzen naar namen die bestaan. Drie dingen zijn van belang bij selecteren: de grootte, de exacte kolomnamen en de index — de labels aan de linkerkant.
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(list(orders.columns))
print(orders.index)(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)De kolomnamen komen terug als een lijst, tussen aanhalingstekens — zo zie je dat 'price' kleingeschreven is en geen overbodige spaties heeft. De index is een RangeIndex van 0 tot 7: de labels zijn gewoon een teller. Onthoud dat, want het is de toevalligheid waar dit hele hoofdstuk over gaat. Op dit moment is de rij met label 2 ook de rij op positie 2. Dat is niet altijd zo.
3. Schets het antwoord voordat je het produceert. Wat moet eruit komen — één getal, één kolom, of een kleinere tabel? Hoeveel rijen en kolommen? Voor de vraag van het magazijnteam is het antwoord een tabel van 8 rijen en 2 kolommen. Voor "bestelling 1006" is het één rij. Voor "de prijs van bestelling 1006" is het één getal: 850.0. Als je de verwachte vorm kent, valt een foute selectie meteen op zodra je het resultaat ziet.
4. Kies het juiste gereedschap op basis van wat je weet. Deze lijst is het hoofdstuk in één overzicht:
- Je kent kolomnamen → vierkante haken:
orders["quantity"],orders[["product", "quantity"]] - Je kent het label van een rij, zijn naam in de index →
.loc:orders.loc[2] - Je kent de positie van een rij — eerste, laatste, vijfde →
.iloc:orders.iloc[-3:] - Je hebt rijen en kolommen samen nodig →
.loc[rows, cols]of.iloc[rows, cols]:orders.loc[2:4, ["product", "price"]] - Je hebt precies één cel nodig →
.at[row, col]of.loc[row, col]:orders.at[5, "price"]
5. Beslis hoe je het resultaat controleert. Print na elke selectie .shape of type() van het resultaat en vergelijk met je schets. Het is één regel, en het vangt de meeste fouten hieronder op voordat ze verkeerde getallen opleveren.
De rest van het hoofdstuk bespreekt deze gereedschappen één voor één, elk met een uitgewerkt voorbeeld.
Één kolom: een naam tussen vierkante haken
Zet een kolomnaam tussen vierkante haken en je krijgt die kolom:
quantity = orders["quantity"]
print(type(quantity))
print(quantity)<class 'pandas.Series'>
0 12
1 5
2 2
3 7
4 30
5 1
6 20
7 4
Name: quantity, dtype: int64Wat terugkomt is een Series — precies het object uit hoofdstuk twee: waarden, een index aan de linkerkant, een Name (de kolom waar hij vandaan komt) en één dtype. Merk op dat de index meekomt. De 0 tot 7 zijn geen decoratie: ze geven nog steeds aan bij welke rij elk getal hoort.
Omdat het een Series is, werken alle Series-methoden er direct op:
print(orders["quantity"].sum())81Dat is de gebruikelijke reden om één kolom te selecteren: om er een vraag over te stellen.
Meerdere kolommen: een lijst tussen de haken
Voor het magazijnteam heb je twee kolommen nodig. Zet een lijst van namen tussen de haken:
stock = orders[["product", "quantity"]]
print(type(stock))
print(stock)<class 'pandas.DataFrame'>
product quantity
0 pen 12
1 notebook 5
2 bag 2
3 bottle 7
4 eraser 30
5 bag 1
6 pen 20
7 bottle 4De dubbele haken zijn geen bijzondere syntaxis, en dat is de moeite waard om goed te zien. Het buitenste paar is de selectie; het binnenste paar is een gewone Python-lijst: ["product", "quantity"]. Je kunt de lijst ook eerst aanmaken en die doorgeven — wanted = ["product", "quantity"] en dan orders[wanted] — dat betekent precies hetzelfde. De kolommen komen terug in de volgorde van de lijst, dus dit is ook de manier om kolommen te herordenen.
Het resultaat is een DataFrame, want een tabel met twee kolommen is een tabel. En hier is een onderscheid dat mensen maandenlang van de wijs brengt: een lijst met maar één naam geeft toch een DataFrame.
print(orders["product"].shape)
print(orders[["product"]].shape)(8,)
(8, 1)(8,) is een Series — één dimensie, acht waarden. (8, 1) is een DataFrame met acht rijen en één kolom. Ze printen anders, ze hebben andere methoden, en als je ze naar een bestand opslaat, behoudt het DataFrame zijn kolomheader. De regel is simpel: een naam geeft een Series; een lijst geeft een DataFrame — hoeveel namen er ook in de lijst staan.
Waarom niet orders.quantity?
Je zult kolomtoegang met een punt in veel tutorials tegenkomen:
print(orders.branch.head(2))0 north
1 south
Name: branch, dtype: strDit werkt hier, en het is korter. Maar het werkt alleen als de kolomnaam toevallig een geldige Python-naam is die nog niet voor iets anders wordt gebruikt — en een DataFrame heeft honderden methoden en attributen. Hier is een kleine tabel waarvan de kolommen count en size heten, twee volkomen redelijke namen voor een winkel:
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})
print(d.size)
print(d["size"])4
0 3
1 4
Name: size, dtype: int64d.size geeft je de kolom helemaal niet. Het geeft het eigen size-attribuut van het DataFrame — het aantal cellen, 2 × 2 = 4 — zonder enige waarschuwing. Geen fout, gewoon een verkeerd antwoord. Een naam met een spatie erin, zoals unit price, kan helemaal niet met een punt worden geschreven. De haken hebben geen van deze problemen, dus de regel voor code die je bewaart is: gebruik df["col"]. De punt is prima voor een snelle blik in de terminal, meer niet.
Rijen op label: .loc
Rijen worden niet geselecteerd met gewone haken. (Gewone haken met een naam betekenen "kolom", en pandas raadt niet welke je bedoelt.) Rijen hebben hun eigen twee gereedschappen, en het eerste is .loc — label-based location. Je geeft het het label uit de index:
print(orders.loc[2])order_id 1003
date 2024-03-02
branch north
product bag
category accessories
quantity 2
price 850.0
Name: 2, dtype: objectÉén rij komt terug als een Series, op zijn kant gedraaid: de kolomnamen zijn nu de index geworden, en de Name is het label van de rij, 2.
Let op dtype: object. Een kolom heeft één type, maar een rij loopt dwars door kolommen heen — een getal, wat tekst, een decimaal — dus het enige type dat ze allemaal kan bevatten is het algemene object. Dat is één reden waarom pandas in kolommen denkt: een rij mengt types, een kolom niet.
Nu het belangrijke deel. Vraag .loc om een bereik van labels:
print(orders.loc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0
4 1005 2024-03-03 north eraser stationery 30 8.0Drie rijen: 2, 3 en 4. Een .loc-slice bevat zijn eindpunt. Dat verrast iedereen die Python kent, waar range(2, 4) en list[2:4] stoppen vóór 4. Het is geen tegenstrijdigheid; er is een reden voor.
Labels zijn niet altijd getallen. Als de labels productnamen waren, zou je iets schrijven als loc["bag":"eraser"]. Om het einde uit te sluiten, zou je het label ná "eraser" moeten noemen — en bij namen is er geen "volgende" die je kunt kennen zonder te kijken. Dus bij labels zeg je het eerste dat je wilt en het laatste dat je wilt, en je krijgt allebei. Posities zijn anders: de volgende positie na 4 is altijd 5, dus Python's regel "stop voor het einde" kost daar niets.
Rijen op positie: .iloc
Het tweede gereedschap is .iloc — integer location. Het negeert de labels volledig en telt plaatsen, vanaf 0, precies zoals een Python-lijst:
print(orders.iloc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0Twee rijen, posities 2 en 3; positie 4 is uitgesloten, net als bij elke andere slice in Python. Zet de twee uitvoeren naast elkaar: loc[2:4] gaf drie rijen en iloc[2:4] gaf er twee, op dezelfde tabel, met dezelfde getallen tussen de haken.
Omdat .iloc positioneel werkt, tellen negatieve getallen vanaf het einde — waarmee je de vraag van je manager direct beantwoordt:
print(orders.iloc[-3:])order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0"De laatste drie" is een vraag over positie, dus .iloc is het gereedschap dat past. .loc heeft geen idee wat "laatste" betekent; het kent alleen namen.
Labels en posities zijn niet hetzelfde
Tot nu toe waren label 2 en positie 2 dezelfde rij, waardoor je de twee gereedschappen aan hun resultaten niet kon onderscheiden. Neem de laatste drie bestellingen als eigen tabel, en ze gaan uiteen:
last3 = orders.tail(3)
print(last3)order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0De rijen hebben hun labels behouden: 5, 6, 7. Pandas hernummert ze niet, expres — het label is de manier waarop een rij dezelfde rij blijft nadat je de tabel hebt ingekort. Vraag nu "de eerste rij" op beide manieren:
print(last3.iloc[0])order_id 1006
date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 5, dtype: objectlast3.loc[0]KeyError: 0.iloc[0] betekent "de rij op de eerste plaats", dat is de bestelling met label 5. .loc[0] betekent "de rij waarvan het label 0 is" — en zo'n rij bestaat niet in deze tabel, dus pandas geeft een KeyError. Dit is de fout die later in de cursus toeslaat: nadat je rijen filtert of sorteert — beide komen later in de cursus — veranderen posities, maar labels niet. De regel die je meeneemt:
- Vraag naar plaats — eerste, laatste, top drie na sorteren? Gebruik
.iloc. - Vraag naar identiteit — deze bestelling, dit product? Gebruik
.loc.
En het slechte geval is niet de KeyError. Het slechte geval is wanneer label 0 wel ergens anders in de tabel bestaat: dan geeft .loc[0] stilletjes een andere rij terug dan je bedoelde.
.locvraagt om een naam,.ilocvraagt om een plaats. Op een vers ingelezen tabel komen ze toevallig overeen, zodat code die ze door elkaar haalt alle controles op de eerste dag doorstaat — en de verkeerde rij teruggeeft, zonder fout, zodra de tabel is ingekort, gefilterd of gesorteerd.
Rijen betekenisvolle labels geven: set_index
De boekhouder vroeg naar bestelling 1006. Met de standaardindex zou je moeten weten dat bestelling 1006 onder label 5 staat — een feit over het bestand, niet over de bestelling. De echte naam van een rij is zijn bestelnummer, dus maak het bestelnummer de index:
by_id = orders.set_index("order_id")
print(by_id.head(3))
print(by_id.shape)date branch product category quantity price
order_id
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
(8, 6)Drie dingen zijn veranderd. De bestelnummers zijn naar links verplaatst, naar de index. De index heeft nu een naam, order_id, die op een eigen regel boven de index staat. En de vorm is (8, 6) in plaats van (8, 7) — order_id is niet langer een kolom, maar de index. Let ook op: set_index geeft een nieuwe tabel terug, die we by_id hebben genoemd; orders zelf is ongewijzigd.
Nu spreekt .loc de taal van de vraag:
print(by_id.loc[1006])date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 1006, dtype: objectEn een labelslice is nu een bereik van bestelnummers, beide uiteinden inbegrepen:
print(by_id.loc[1003:1005, ["product", "quantity"]])product quantity
order_id
1003 bag 2
1004 bottle 7
1005 eraser 30.iloc is helemaal niet veranderd — posities zijn posities, ongeacht de labels:
print(by_id.iloc[0:2])date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0Hier zijn de twee gereedschappen duidelijk van elkaar gescheiden: by_id.loc[0] geeft nu een KeyError, want 0 is geen bestelnummer, terwijl by_id.iloc[0] bestelling 1001 is. Als je het bestelnummer ooit terug als kolom wilt, zet by_id.reset_index() het terug.
Kies een kolom waarvan de waarden uniek zijn. Een index is bedoeld om rijen te benoemen, en een naam die twee rijen delen, wijst niet naar één. Kijk wat er gebeurt met product, dat zich herhaalt:
by_product = orders.set_index("product")
print(by_product.index.is_unique)
print(by_product.loc["bag"])False
order_id date branch category quantity price
product
bag 1003 2024-03-02 north accessories 2 850.0
bag 1006 2024-03-03 south accessories 1 850.0Geen fout — maar loc["bag"] gaf een DataFrame van twee rijen terug in plaats van één rij als Series. Code die één rij verwachtte (zeg, de price lezen als één getal) krijgt er nu twee. Dat is waarom index.is_unique één regel controleren waard is telkens als je een index instelt waarop je rijen wilt opzoeken.
Rijen en kolommen samen
Zowel .loc als .iloc nemen twee dingen gescheiden door een komma: eerst rijen, dan kolommen. Elk deel kan één label, een lijst of een slice zijn:
print(orders.loc[2:4, ["product", "price"]])product price
2 bag 850.0
3 bottle 120.0
4 eraser 8.0print(orders.loc[[0, 3], ["branch", "product"]])branch product
0 north pen
3 east bottleEen slice werkt ook voor kolommen, met de inclusieve regel van .loc — "elke kolom van branch tot en met category". Een kale : betekent "alle rijen":
print(orders.loc[:, "branch":"category"])branch product category
0 north pen stationery
1 south notebook stationery
2 north bag accessories
3 east bottle accessories
4 north eraser stationery
5 south bag accessories
6 east pen stationery
7 north bottle accessories.iloc doet hetzelfde op basis van positie. Kolommen worden ook geteld, vanaf 0, dus branch is kolom 2 en product is kolom 3:
print(orders.iloc[[0, 3], [2, 3]])branch product
0 north pen
3 east bottleHetzelfde resultaat als de .loc-versie twee blokken eerder — maar merk op hoe veel moeilijker het te lezen is. [2, 3] zegt niets; ["branch", "product"] zegt alles, en werkt ook als iemand een kolom aan het begin van het bestand toevoegt. Gebruik namen wanneer je de namen kent.
Welke vorm krijg je terug?
Of je één waarde, een Series of een DataFrame terugkrijgt, hangt alleen af van wat je aan elke kant van de komma zet. Een enkel label verwijdert die dimensie; een lijst of slice behoudt hem:
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)float64
Series
Series
DataFrame- één label, één label → één waarde
- één label, een lijst of slice → een
Series(één rij) - een lijst of slice, één label → een
Series(één kolom) - een lijst of slice, een lijst of slice → een
DataFrame
Dit is stap 3 van de planning — schets het antwoord — omgezet in een regel. Als je een tabel terug wilt, zelfs van één rij, zet dan een lijst aan beide kanten: by_id.loc[[1006], ["price"]].
Één waarde: .loc en .at
De vraag van de boekhouder — wat heeft bestelling 1006 gekost? — is één cel: een rijlabel en een kolomnaam.
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])850.0
850.0Beide geven hetzelfde getal. .at accepteert alleen één rij en één kolom, verder niets, waardoor het iets sneller is en je bedoeling duidelijk is: dit is één cel. (.iat is de positionele tegenhanger: by_id.iat[5, 5].) Gebruik wat het best leest; .loc is prima.
Een enkele waarde is een gewoon getal, dus je kunt er direct mee rekenen. De waarde van bestelling 1004 — hoeveelheid maal prijs:
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])840.0Een selectie is een eigen object
Er is nog één ding dat je moet begrijpen voordat je selecties in echte programma's gebruikt: wat er gebeurt als je er één wijzigt.
import pandas as pd
orders = pd.read_csv("orders.csv")
prices = orders["price"]
prices[0] = 999.0
print(prices.head(2))
print(orders.loc[0, "price"])0 999.0
1 60.0
Name: price, dtype: float64
15.0prices is veranderd. orders niet. In pandas 3 gedraagt elke selectie zich als een aparte kopie: je kunt hem vrijelijk aanpassen en de originele tabel wordt nooit aangeraakt. (Intern vermijdt pandas het daadwerkelijk kopiëren van de data totdat je ernaar schrijft — dat is de naam van het mechanisme, Copy-on-Write — maar de regel die je nodig hebt is het gedrag, niet het mechanisme.)
Hetzelfde geldt voor een selectie van meerdere kolommen:
stock = orders[["product", "price"]]
stock["price"] = 0
print(orders["price"].head(2))0 15.0
1 60.0
Name: price, dtype: float64Dit is het veilige gedrag: een functie die een selectie neemt en bewerkt, kan je hoofdtabel niet stiekem beschadigen. Maar het heeft één gevolg dat je moet kennen. Als je werkelijk het origineel wilt wijzigen, moet je dat op het origineel doen, in één stap, met .loc:
orders.loc[0, "price"] = 16.0
print(orders.loc[0, "price"])16.0De verleidelijke tweestapsversie — selecteer een kolom, stel dan een cel in — werkt niet, en pandas 3 vertelt je dat ook:
orders["price"][0] = 1.0ChainedAssignmentError: 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-assignmentprint(orders.loc[0, "price"])16.0Nog steeds 16.0 — de toewijzing is in een tijdelijke kopie terechtgekomen en weggegooid. orders["price"] maakte een selectie, en [0] = 1.0 veranderde die, niet orders. Dat heet chained assignment: twee paar haken achter elkaar aan de linkerkant van =. De oplossing is altijd de oplossing die de waarschuwing noemt: één .loc met zowel de rij als de kolom, orders.loc[0, "price"] = 1.0.
Als je oudere tutorials leest, zul je dit gebied beschreven zien met een SettingWithCopyWarning en advies dat het origineel soms wel en soms niet werd gewijzigd. Dat was pandas 1 en 2. In pandas 3 is de regel simpel en altijd hetzelfde: een selectie verandert het origineel nooit; .loc op het origineel wel.
Een compleet voorbeeld
Alle drie de verzoeken uit het begin van het hoofdstuk, beantwoord in één programma. select_orders.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check what arrived before pointing into it
print("Shape :", orders.shape)
print("Columns:", list(orders.columns))
# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()
# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()
# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()
# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])Shape : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True
Stock view: (8, 2)
product quantity
0 pen 12
1 notebook 5
2 bag 2
product bag
quantity 1
price 850.0
Name: 1006, dtype: object
Order 1006 total: 850.0
branch product quantity
order_id
1006 south bag 1
1007 east pen 20
1008 north bottle 4Waarom het zo is geschreven:
- Het kijkt voordat het wijst.
shapeenlist(columns)worden eerst geprint, zodat een verkeerd gespelde of spatie-gevulde kolomnaam zichtbaar is voordat een selectie mislukt. - Het controleert of de index uniek is voordat hij gebruikt wordt om bestellingen op te zoeken, zodat
by_id.loc[1006]gegarandeerd één rij is enby_id.at[...]één getal. - Elk verzoek gebruikt het gereedschap dat past bij de vraag. Kolommen op naam met haken; een bestelling op identiteit met
.locen.at; "de laatste drie" op positie met.iloc. - Het print de vorm van de magazijnweergave —
(8, 2)is precies de schets: elke bestelling, twee kolommen. - De laatste selectie is de selectie om te verbeteren.
[1, 2, 4]betekent toevalligbranch,product,quantityinby_id— let op: de posities zijn met één verschoven omdatorder_idde index is geworden. Posities zijn fragiel. In je eigen code zouby_id.iloc[-3:][["branch", "product", "quantity"]]ofby_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]]hetzelfde zeggen met namen.
Wanneer het misgaat
KeyError: 'Price' Er is geen kolom met precies die naam. Kolomnamen zijn hoofdlettergevoelig: price en Price zijn verschillende namen. Print list(df.columns) en kopieer de naam van daaruit. Met een lijst van namen, orders[["product", "Price"]], wordt de melding KeyError: "['Price'] not in index" en staat er precies bij welke ontbreekt; met een punt, orders.Price, is het AttributeError: 'DataFrame' object has no attribute 'Price'. Zelfde oorzaak, zelfde oplossing.
KeyError: 'price' — hoewel price duidelijk in het bestand staat De header heeft waarschijnlijk spaties na de komma's, order_id, product, price, en die spaties zijn deel van de namen geworden:
from io import StringIO
import pandas as pd
raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))
print(list(padded.columns))
padded["price"]['order_id', ' product', ' price']
KeyError: 'price'De lijst toont de voorloopspatie in ' price', en dat is precies waarom hoofdstuk drie je vertelde de kolommen als lijst te printen. Los het op tijdens het inlezen met pd.read_csv("file.csv", skipinitialspace=True), of achteraf met df.columns = df.columns.str.strip().
KeyError: ('product', 'price') Je schreef orders["product", "price"] — één paar haken. Zonder de binnenste lijst geeft Python de twee namen door als één tuple, en pandas zoekt naar één kolom genaamd ('product', 'price'). Schrijf orders[["product", "price"]].
KeyError: 0 van .loc Je gebruikte een positie met .loc. De tabel is ingekort (tail, een filter, een sortering) zodat label 0 er niet meer in zit, of je hebt een index ingesteld en de labels zijn nu bestelnummers. Als je "de eerste rij" bedoelt, schrijf dan .iloc[0]. Op een tabel waarvan de index tekst is, geeft dezelfde fout met een slice — orders.set_index("product").loc[0:2] — een TypeError: cannot do slice indexing on Index with these indexers [0] of type int; de oplossing is hetzelfde: .iloc[0:2].
IndexError: single positional indexer is out-of-bounds .iloc kreeg een positie die niet bestaat — orders.iloc[8] op een tabel met acht rijen, waarvan de posities lopen van 0 tot 7. Controleer df.shape; de laatste rij is altijd df.iloc[-1]. Dezelfde melding verschijnt bij een kolompositie die te groot is: orders.iloc[:, 7].
ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types Je gaf .iloc een kolomnaam: orders.iloc[0, "branch"]. .iloc accepteert alleen posities. Gebruik .loc met een label — orders.loc[0, "branch"] — of zoek de positie op als je die echt nodig hebt: orders.columns.get_loc("branch") geeft 2 terug.
Stap 4 van 6 — Voorspellen
Test je begrip
Dezelfde tabel, dezelfde getallen tussen de haken. Wat wordt er geprint?
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))
print(len(orders.loc[2:5]), len(orders.iloc[2:5]))- A4 4
- B3 4
- C3 3
- D4 3
Dit is bedoeld om het product van de eerste van de laatste drie bestellingen te printen. Wat gebeurt er in werkelijkheid, en waarom?
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))
last3 = orders.tail(3)
# Wanted: the product of the first of these three orders
print(last3.loc[0, "product"])- AEr wordt `bag` geprint, het product van de eerste van de drie rijen
- B`IndexError` — de tabel heeft maar drie rijen
- C`KeyError: 0` — `tail(3)` heeft de labels 5, 6 en 7 behouden, en `.loc` zoekt naar een label 0
- DEr wordt `pen` geprint, het product in de eerste rij van de oorspronkelijke tabel
Je hebt een DataFrame nodig met alleen de kolom price — geen Series. Welke regel levert dat op?
- Aorders.loc[:, "price"]
- Borders.price
- Corders["price"]
- Dorders[["price"]]
Voor antwoorden heb je een account nodig
Log in om je antwoorden te controleren
De vragen staan hierboven, en ze in je hoofd uitwerken is waar het om draait. Log in om de antwoorden, de uitleg en de hints in drie niveaus te zien.
Jouw beurt
Schrijf met hetzelfde orders.csv het bestand select.py. Schrijf vóór alle code in een commentaar wat elke selectie moet opleveren — één waarde, een Series of een DataFrame — en hoeveel rijen. Het programma moet aan vier voorwaarden voldoen:
- Kolommen worden altijd op naam geselecteerd: print de
shapevandate,branchenquantitysamen. - Bestellingen worden op identiteit opgezocht in een tabel geïndexeerd op
order_id, na controle dat de index uniek is: print de hele rij voor bestelling 1007, danproductenbranchvoor bestellingen 1002 tot en met 1005 — alle vier. - "Eerste" en "laatste" worden op positie beantwoord: print
productenpricevoor de eerste twee en de laatste twee bestellingen; druk dan, met.at, de hoeveelheid van bestelling 1005 af en de totale waarde van bestelling 1003 (hoeveelheid × prijs). - Verander de prijs van bestelling 1001 naar
16.0in de geïndexeerde tabel met één enkele.loc, en print hem om te bewijzen dat de wijziging is doorgevoerd.
Je uitvoer moet precies dit zijn:
(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Voorspel daarna, schrijf je voorspelling op, en voer dan pas elk van deze drie regels uit:
by_id.loc[0]orders.iloc[8]by_id["price"][1001] = 99.0— en print daarna de prijs van bestelling 1001 opnieuw
Oplossing
Plan eerst. Het commentaarblok is de schets van stap 3 van "Voordat je de code schrijft" — wat elke selectie moet opleveren:
- Drie kolommen →
orders[[...]]→ eenDataFramemet vorm(8, 3) - Één bestelling, op identiteit →
by_id.loc[1007]→ eenSeries(één rij) - Vier bestellingen op identiteit, twee kolommen →
by_id.loc[1002:1005, [...]]→ een 4 × 2DataFrame, want.locbevat 1005 - Eerste twee en laatste twee, op positie →
.iloc[:2]en.iloc[-2:]→ tweeDataFrames van 2 × 2 - Enkelvoudige cellen →
.at→ gewone getallen - Het origineel wijzigen → één
.locopby_id→ de tabel zelf verandert
select.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# Columns by name -> a DataFrame of 8 rows, 3 columns
print(orders[["date", "branch", "quantity"]].shape)
# Rows named by their order number; check the names are unique first
by_id = orders.set_index("order_id")
print("unique:", by_id.index.is_unique)
# One order by identity -> a Series; four orders -> 4 x 2 (loc includes 1005)
print(by_id.loc[1007])
print(by_id.loc[1002:1005, ["product", "branch"]])
# First two and last two by place -> .iloc for rows, names for columns
print(by_id.iloc[:2][["product", "price"]])
print(by_id.iloc[-2:][["product", "price"]])
# Single cells -> plain numbers
print("qty of 1005:", by_id.at[1005, "quantity"])
print("total of 1003:", by_id.at[1003, "quantity"] * by_id.at[1003, "price"])
# Change the original, in one step
by_id.loc[1001, "price"] = 16.0
print("price of 1001:", by_id.at[1001, "price"])(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Elk resultaat klopt met het plan: (8, 3) voor drie kolommen, vier bestellingen voor 1002:1005 omdat .loc zijn eindpunt insluit, en twee rijen voor elk van de positionele selecties. In voorwaarde 3 komen de rijen op positie en de kolommen op naam — .iloc voor de rijen, dan een lijst van namen — zodat de code zegt wat hij bedoelt.
Nu de drie voorspellingen:
by_id.loc[0]KeyError: 0De labels zijn nu bestelnummers; er is geen bestelling 0. De eerste rij op positie zou by_id.iloc[0] zijn.
orders.iloc[8]IndexError: single positional indexer is out-of-boundsAcht rijen betekent posities 0 tot 7. De laatste rij is orders.iloc[7], of beter: orders.iloc[-1].
by_id["price"][1001] = 99.0ChainedAssignmentError: 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-assignmentprint(by_id.at[1001, "price"])16.0Nog steeds 16.0: by_id["price"] maakte een aparte selectie en de 99.0 ging daarin, niet in by_id. Voorwaarde 4 werkte wel en dit niet, en het verschil is één .loc versus twee paar haken achter elkaar.
Stap 6 van 6
Uitdaging — de hoofdstukquiz
Tien vragen van makkelijk naar moeilijk. De laatste zijn met opzet lastig.
Log in om de quiz te maken