Series en DataFrame — de twee bouwstenen van pandas
Wat een kolom is, wat een tabel is, en waarom elke waarde in een kolom hetzelfde type moet hebben. Inclusief het verschil tussen df["col"] en df[["col"]].
Hoe wil je leren?
Het probleem dat we oplossen
In het vorige hoofdstuk schreven we één regel, en die werkte:
print(sales["quantity"].sum())19Die regel is makkelijk te lezen, maar er schuilt een vraag in. Wat gaf sales["quantity"] eigenlijk terug? Een lijst? Lijsten hebben geen .sum() — als je [12, 5, 2].sum() schrijft in Python, levert dat een foutmelding op.
Het antwoord is goed om te weten, want in pandas werk je altijd met een van twee dingen, en bijna elke verwarring begint met dezelfde vraag: welke van de twee heb ik nu eigenlijk vast? Een kolom, of een hele tabel?
Dit hoofdstuk gaat over die twee dingen: hun namen, hun vormen en wat het verschil tussen beide is.
Aan het einde van dit hoofdstuk kun je
- In één zin vertellen wat het verschil is tussen een
Seriesen eenDataFrame - Een
Seriesmaken en de index, waarden en het type afzonderlijk inspecteren - Uitleggen waarom elke waarde in een kolom hetzelfde type moet hebben
df["col"]endf[["col"]]gebruiken terwijl je precies weet wat wat is- Een
KeyErrorlezen en precies vertellen wat er misging met een kolomnaam
Voorkennis: pandas installeren en je eerste tabel.
Series — één kolom
Begin met de kleinste eenheid. Een Series is een reeks waarden, waarbij elke waarde aan een naam is gekoppeld:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)0 12
1 5
2 2
dtype: int64Er gingen drie waarden in, en er kwamen drie paren uit. De 0, 1, 2 aan de linkerkant vormen de index — een naam voor elke waarde. Je hebt die zelf niet opgegeven; pandas begon te tellen vanaf nul.
En onderaan staat dtype: int64 — het type van de hele Series. Eén type, niet één type per waarde. Dat is wat pandas snel maakt, en het is tegelijkertijd de belangrijkste beperking.
Je kunt de drie onderdelen afzonderlijk bekijken:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity.index)
print(quantity.values)
print(quantity.dtype)RangeIndex(start=0, stop=3, step=1)
[12 5 2]
int64RangeIndex betekent dat de index niets anders is dan een telling — beginnend bij nul, oplopend met één. values bevat de waarden zonder hun namen. En dtype is het type van de hele kolom.
Een index hoeft niet uit getallen te bestaan:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity)
print(quantity["notebook"])pen 12
notebook 5
bag 2
dtype: int64
5Nu zijn de rijen vernoemd naar de producten, en kun je een waarde op die naam ophalen. Dit klinkt als een Python-dictionary, en de gelijkenis is reëel — met één groot verschil:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity * 2)pen 24
notebook 10
bag 4
dtype: int64De hele kolom werd in één keer met twee vermenigvuldigd, zonder lus. Als je dat met een dictionary wilt doen, moet je zelf met een lus elke waarde langslopen. Voer een bewerking uit op een Series en pandas past deze toe op elke waarde — en alle volgende hoofdstukken bouwen voort op dit gedrag.
DataFrame — meerdere kolommen, één index
Een DataFrame bestaat uit meerdere Series naast elkaar die één index delen.
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales)
print(sales.shape)
print(sales.columns)product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0
(3, 3)
Index(['product', 'quantity', 'price'], dtype='object')Die uitdrukking "één index delen" is de kern van de zaak. De product, quantity en price op index 0 gaan allemaal over hetzelfde ding: één pen. De index is wat de drie kolommen samenbindt tot rijen.
Eén tabel, meerdere types — maar één kolom, één type
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales.dtypes)product object
quantity int64
price float64
dtype: objectDrie kolommen, drie types. int64 staat voor gehele getallen, float64 voor decimale getallen, en object — wat meestal tekst betekent.
Merk op dat er geen str tussen staat. De types in pandas zijn afkomstig van NumPy, waar elke waarde evenveel ruimte inneemt — dat kan voor getallen, maar niet voor tekst van wisselende lengte. Om tekst op te slaan, bewaart pandas daarom pointers naar Python-objecten, en die opzet heet object.
Nu de belangrijke regel: elke waarde in een kolom heeft hetzelfde type. Wat gebeurt er dan als getallen en tekst in één kolom worden gemengd?
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed)
print(mixed.dtype)0 1
1 two
2 3.0
dtype: objectPandas geeft geen foutmelding — het maakt de hele kolom object, waarbij het terugvalt op het meest algemene beschikbare type. Het gevolg is niet spectaculair, maar geruisloos: de kolom is niet langer numeriek, waardoor .mean() erop zal mislukken en .sum() tekst aan elkaar begint te plakken. Eén verdwaalde N/A in één rij van een CSV veroorzaakt precies dit, en dat is het onderwerp van hoofdstuk zes.
Welke heb ik — een Series of een DataFrame?
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
column = sales["quantity"]
print(type(column))
print(type(sales))
print(column.mean())<class 'pandas.core.series.Series'>
<class 'pandas.core.frame.DataFrame'>
6.333333333333333En daar is het antwoord op de vraag waarmee dit hoofdstuk begon. sales["quantity"] is geen lijst, het is een Series — en een Series heeft .sum(), .mean(), .max() en de rest.
Er is hier ook een tweetal waar iedereen de eerste keer over struikelt:
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales["quantity"])
print("---")
print(sales[["quantity"]])0 12
1 5
2 2
Name: quantity, dtype: int64
---
quantity
0 12
1 5
2 2Eén extra paar haken, en het resultaat is ineens een heel ander soort ding. De eerste is een Series — één kolom, met de naam en het type eronder geprint. De tweede is een DataFrame — een tabel die toevallig uit één kolom bestaat, zodat de kolomkop erboven staat.
De regel is dat de binnenste vierkante haken een lijst van kolommen betekenen, en wanneer je om een lijst vraagt, krijg je een tabel terug. Dus sales[["product", "price"]] levert een tabel met twee kolommen op.
Waarom moet je dit onderscheid onthouden? Omdat hier de foutmeldingen uiteenlopen. EenSeriesheeft wel.mean()maar geen kolomnamen; eenDataFrameselecteert kolommen maar geeft je niet zomaar één getal. Wanneer een foutmelding zegt dat een object niet over zo'n attribuut beschikt, is de eerste vraag altijd: welke van de twee heb ik nu eigenlijk vast?
Een compleet voorbeeld
inventory.py:
import pandas as pd
# Product names become the index, so a row can be reached by name.
stock = pd.DataFrame(
{
"quantity": [12, 5, 2, 30],
"price": [15.0, 60.0, 850.0, 8.0],
},
index=["pen", "notebook", "bag", "eraser"],
)
print(stock)
print()
print("Shape :", stock.shape)
print("Columns :", list(stock.columns))
print("Index :", list(stock.index))
print()
# A column is a Series, so an operation applies to every value at once.
value = stock["quantity"] * stock["price"]
print("Value per product:")
print(value)
print()
print("Total value:", value.sum())quantity price
pen 12 15.0
notebook 5 60.0
bag 2 850.0
eraser 30 8.0
Shape : (4, 2)
Columns : ['quantity', 'price']
Index : ['pen', 'notebook', 'bag', 'eraser']
Value per product:
pen 180.0
notebook 300.0
bag 1700.0
eraser 240.0
dtype: float64
Total value: 2420.0Twee dingen vallen hier op.
Ten eerste: stock["quantity"] * stock["price"] vermenigvuldigde twee Series met elkaar en leverde opnieuw een Series op. Pandas koppelde ze op index aan elkaar — het aantal pennen aan de prijs van de pen, het aantal tassen aan de prijs van de tas. Op naam, niet op rijvolgorde. Dat gedrag vormt later de basis voor het samenvoegen van twee tabellen.
Ten tweede: het type van de resulterende kolom is float64, hoewel quantity een int64 was. Een geheel getal vermenigvuldigen met een decimaal getal levert een decimaal getal op — dezelfde regel als in gewone Python; pandas doet hier zelf niets bijzonders.
Wanneer het misgaat
KeyError: 'Quantity' Er bestaat geen kolom met die naam. Kolomnamen zijn hoofdlettergevoelig, en namen die uit een CSV-bestand komen bevatten vaak een spatie aan het begin of aan het einde. Voer print(list(df.columns)) uit om precies te zien wat er staat — door ze als een lijst te printen komen er aanhalingstekens om elke naam te staan, zodat eventuele spaties meteen opvallen.
AttributeError: 'DataFrame' object has no attribute 'mean_price' Met df.column_name kun je ook een kolom ophalen, maar dat werkt alleen voor eenvoudige namen — niet wanneer de naam een spatie bevat, met een cijfer begint of botst met een pandas-methode zoals count. df["column_name"] werkt altijd, dus maak er een gewoonte van om die notatie te gebruiken.
TypeError: unsupported operand type(s) tijdens een berekening op een numerieke kolom De kolom is waarschijnlijk niet numeriek. Controleer df.dtypes; als daar object staat, betekent dit dat er ergens tekst tussen staat.
.sum() voegt tekst samen in plaats van getallen op te tellen Dezelfde oorzaak — de kolom is object. Eén enkele N/A of één lege cel in een rij is al voldoende om het type van de hele kolom te veranderen.
ValueError: All arrays must be of the same length De lijsten die aan pd.DataFrame({...}) zijn meegegeven hebben niet allemaal dezelfde lengte. Elke kolom moet evenveel waarden bevatten, omdat de rijen moeten aansluiten op één gedeelde index.
Stap 4 van 6 — Voorspellen
Test je begrip
Er zijn drie waarden meegegeven. Wat wordt er precies geprint?
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)- A12 5 2
- B[12, 5, 2]
- C12 5 2 dtype: int64
- D0 12 1 5 2 2 dtype: int64
De twee regels verschillen alleen door één extra paar haken. Wat meldt type()?
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(type(sales["quantity"]))
print(type(sales[["quantity"]]))- ABeide een Series
- BBeide een DataFrame
- CDe eerste een Series, de tweede een DataFrame
- DDe eerste een DataFrame, de tweede een Series
Getallen en tekst zijn gemengd in één kolom. Wat doet pandas?
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed.dtype)- AHet geeft geen foutmelding — het maakt het type van de hele kolom `object`
- BEen `TypeError` — twee types kunnen niet in één kolom bestaan
- CHet verwijdert de tekst en behoudt de kolom als `float64`
- DHet behoudt een apart type voor elke waarde
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
Maak een bestand genaamd library.py met daarin een DataFrame van ten minste vijf boeken — met de titels als index, en kolommen voor het aantal exemplaren (gehele getallen), de prijs (decimale getallen) en de naam van de auteur (tekst).
Print vervolgens:
- De hele tabel, de
shapeen dedtypes - De prijskolom apart — en laat met
type()zien dat dit eenSeriesis - De prijskolom met dubbele haken — en laat zien dat dit een
DataFrameis - De totale waarde van elk boek (aantal exemplaren × prijs), en de totale waarde van alle boeken samen
Doe daarna één experiment: vervang een van de aantallen door "unknown" en voer het bestand opnieuw uit. Wat zegt dtypes nu, en wat is er met het totaal gebeurd?
Dat zijn de meest waardevolle vijf minuten van dit hoofdstuk. Eén enkel woord in één cel verandert het type van een hele kolom en breekt de berekening zonder ook maar enige foutmelding te geven — en in echte data gebeurt dat voortdurend.
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