Gegevens groeperen met pandas

Gegevens groeperen met pandas

12 december 2024
Erwin Matijsen
Geplaatst in Tutorial
Onderdeel van Werken met pandas


In eerdere tutorials heb je geleerd hoe je met pandas gegevens kunt inlezen en bewerken. Een veelvoorkomende taak bij data-analyse is het groeperen van gegevens om daar vervolgens berekeningen op uit te voeren. Denk bijvoorbeeld aan:

  • De gemiddelde temperatuur per maand berekenen uit dagelijkse metingen
  • Het totale verkoopbedrag per klant bepalen uit individuele orders
  • Het aantal orders per product tellen

Dit is waar de groupby functie van pandas om de hoek komt kijken. Met groupby kun je gegevens groeperen op basis van één of meerdere kolommen, om vervolgens per groep berekeningen uit te voeren.

Wat is groupby?

groupby is een functie in pandas die je gebruikt om gegevens te groeperen op basis van gemeenschappelijke waarden. Het werkt eigenlijk net als een draaitabel in Excel: je kiest één of meer kolommen waarop je wilt groeperen, en vervolgens kun je per groep berekeningen uitvoeren zoals het berekenen van gemiddeldes, totalen of het tellen van waarden.

Het grote voordeel van groupby ten opzichte van een draaitabel is dat je veel flexibeler bent in de berekeningen die je kunt uitvoeren. Daarnaast kun je het eenvoudig automatiseren: schrijf één keer de code en je kunt dezelfde analyse blijven uitvoeren op nieuwe gegevens.

In deze tutorial ga je leren hoe je groupby gebruikt om gegevens te groeperen en te analyseren. Je leert:

  • Hoe je gegevens groepeert op één kolom
  • Hoe je groepeert op meerdere kolommen
  • Een aantal berekeningen die je kunt uitvoeren op gegroepeerde gegevens

Voordat je begint

Maak een nieuw project aan, met een nieuwe virtual environment. Installeer daarin ook pandas.

shell
cd projects
mkdir pandas_groupby && cd pandas_groupby
python -m venv .venv
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install pandas
python -m pip freeze > requirements.txt

In deze tutorial ga je werken met gegevens over de WOZ-waarde van het CBS. Je kunt een CSV downloaden op deze pagina. Klik op "Onbewerkte dataset" en vervolgens op "Download CSV". Sla het bestand op in je zojuist aangemaakte projectmap. Voor het gemak kun je het hernoemen naar bijvoorbeeld woz.csv.

Download CSV

Alle code en bestanden van dit project zijn ook op Github te bekijken.

Laden en opschonen

Om goed met de data te kunnen werken maak je een functie voor het laden van het CSV-bestand naar een DataFrame. Hierbij zijn twee aandachtspunten. Ten eerste gebruikt CBS in dit geval zeven spaties gevolgd door een punt voor missende waarde. Tijdens het inladen is het handig om dit om te zetten naar echte missende waarden (NaN). Ten tweede is er de kolom Perioden met gegevens als "1947JJ00" en "2006JJ00". Hier is het handig om alleen het jaartal te pakken, en er integers van te maken. Tot slot gebruikt CBS een aantal codes om regio's mee aan te duiden, het is handiger om met de regionamen te werken. De codering is te vinden via de eerdere link, onder het kopje "metadata".

Maak in je project een bestand data.py aan en plaats daarin twee functies: clean_data en load_data. De code vind je op de Github-pagina van dit project.

Plaats in main.py de volgende code om de gegevens in te laden en te inspecteren:

main.py
from data import clean_data, load_data

if __name__ == "__main__":
    # Schoon de data op
    clean_data()

    # Laad de data
    df = load_data()

    # Inspecteer de data
    print(df.head())
    print(df.info())

Elke rij in de DataFrame is een jaar. Stel dat je nu wilt groeperen op decennium, dan kan dit met groupby. Daarvoor maak je eerst wel een nieuwe kolom Decennium aan, dat voor elke rij aangeeft in welk decennium het jaar valt. De volgende regel in de functie clean_data in data.py zorgt daarvoor:

data.py
def clean_data():
    # eerdere code

    # Maak een kolom decennium
    df["decennium"] = df.loc[:, "jaar"] // 10 * 10

    # Verdere code

De // operator in Python deelt en rondt af naar beneden. Door eerst te delen door 10 en dan weer te vermenigvuldigen met 10, krijg je het begin van het decennium. Bijvoorbeeld:

1923 // 10 = 192  
192 * 10 = 1920

Eerste berekeningen

Nu kun je berekeningen per decennium gaan uitvoeren. Om bijvoorbeeld de gemiddelde WOZ-waarde per decennium te berekenen, gebruik je de volgende code, in berekeningen.py:

berekeningen.py
def woz_per_decennium(df):
    # Groepeer op decennium en bereken de gemiddelde WOZ-waarde
    per_decennium = df.groupby("Decennium")["Gemiddelde WOZ-waarde"].mean()

    print("Gemiddelde WOZ-waarde per decennium:")
    print(per_decennium)

Importeer de functie in main.py en voer main.py uit met python main.py. Je zult iets zien als:

Gemiddelde WOZ-waarde per decennium:
Decennium
2010    216.220588
2020    272.449827
Name: Gemiddelde WOZ-waarde, dtype: float64

Feitelijk koppel je twee acties aan elkaar. Eerst maak je de groepering met df.groupby("Decennium")["Gemiddelde WOZ-waarde"]. Hiermee zeg je: maak een groepering per decennium voor de waardes uit de kolom Gemiddelde WOZ-waarde. Maar hier heb je nog niets aan, want je moet nog aangeven hoe je wilt groeperen. Dat doe je met de .mean-methode. Hiermee geef je aan dat je de gemiddelde waarde wilt per decennium. Je zou ook andere methodes kunnen toepassen, zoals sum, min, max of median.

Je kunt een soortgelijke berekening doen, maar dan per regio:

berekeningen.py
def woz_per_regio(df):

    # Groepeer op regio en bereken de gemiddelde WOZ-waarde
    per_regio = (df.groupby("Regio")["Gemiddelde WOZ-waarde"]
                    .mean()
                    .sort_values()
                    .round(2)
                    )

    print("Gemiddelde WOZ-waarde per regio:")
    print(per_regio)

Het resultaat ziet er zo uit:

Gemiddelde WOZ-waarde per regio:
Regio
Groningen          203.95
Fryslân            206.86
Noord-Nederland    208.76
Zeeland            215.48
Drenthe            216.86
Limburg            219.57
Overijssel         243.52
Flevoland          259.14
Oost-Nederland     261.95
Zuid-Nederland     263.81
Gelderland         272.90
Zuid-Holland       277.48
Noord-Brabant      284.62
Nederland          285.43
West-Nederland     318.48
Utrecht            338.95
Noord-Holland      371.81
Name: Gemiddelde WOZ-waarde, dtype: float64

Je ziet dat je nu niet alleen .mean() toepast op de groupby-functie, maar ook sort_values() en round(2). Hiermee zeg je dat je de gegevens wilt sorteren en wilt afronden op twee decimalen.

De agg-methode

Tot nu toe heb je steeds één berekening per keer uitgevoerd op de gegroepeerde gegevens, bijvoorbeeld alleen het gemiddelde of alleen de mediaan. Maar vaak wil je meerdere berekeningen tegelijk uitvoeren. Daar komt de agg-methode van pas.

De agg-methode (kort voor aggregate) biedt twee voordelen:

  1. Je kunt meerdere berekeningen in één keer uitvoeren
  2. Je kunt zelf de namen van de resultaatkolommen bepalen

Bijvoorbeeld:

berekeningen.py
def woz_per_regio_agg(df):

    # Groepereer op regio en bereken de gemiddelde WOZ-waarde
    per_regio = df.groupby("Regio").agg({
        "Gemiddelde WOZ-waarde": "mean"
    })

    # per_regio is nu een DataFrame
    per_regio = per_regio.sort_values(by="Gemiddelde WOZ-waarde").round(2)
    print("Gemiddelde WOZ-waarde per regio:")
    print(per_regio)

Het resultaat is hetzelfde als de code hiervoor, behalve dat per_regio in het eerste geval een Series oplevert en in het tweede geval een DataFrame.

Lees nog eens Gegevens inlezen en opslaan als je wilt weten hoe het ook alweer zit met Series en DataFrames.

Het volgende voorbeeld maakt duidelijk dat je agg kunt gebruiken als je meerdere berekeningen wilt toepassen op dezelfde kolom. Stel dat je niet alleen het gemiddelde per regio wilt zien, maar ook de mediaan. Dan kan dat met agg:

berekeningen.py
def gemiddelde_en_mediaan(df):
    per_regio = df.groupby("Decennium").agg(
        gemiddelde=("Gemiddelde WOZ-waarde", "mean"),
        mediaan=("Gemiddelde WOZ-waarde", "median")
    )

    per_regio = per_regio.sort_values(by="gemiddelde").round(2)
    print(per_regio)

Je ziet dat je in plaats van een dict met kolom:functie toepast, nu twee parameters opgeeft (gemiddelde en mediaan) met een tuple van een kolomnaam en de te gebruiken functie. Dit is nodig omdat je twee functies op dezelfde kolom wilt toepassen. Dit werkt daarom niet:

berekeningen.py
def gemiddelde_en_mediaan(df):
    result = df.groupby("Decennium").agg({
        "Gemiddelde WOZ-waarde": "mean",
        "Gemiddelde WOZ-waarde": "median"
    })

    print(result)

Je mag immers geen dict hebben met twee keer dezelfde sleutel.

Voer je gemiddelde_en_mediaan nu uit, dan krijg je een DataFrame met de twee opgegeven kolommen / berekeningen:

           gemiddelde  mediaan
Decennium
2010           216.22    211.5
2020           272.45    263.0

Groeperen op meerdere kolommen

Tot dusver heb je gegroepeerd op één kolom (Decennium of Regio), maar soms wil je groeperen op meerdere kolommen. Dat is gelukkig geen probleem.

berekeningen.py
def per_regio_en_decennium(df):

    result = df.groupby(["Regio", "Decennium"])["Gemiddelde WOZ-waarde"].mean().round(2)
    print(result)

Je ziet dat je in plaats van een kolomnaam in groupby meegeeft, je een list met twee kolomnamen meegeeft. De rest blijft hetzelfde! Het resultaat is een multidimensionale Series:

Regio            Decennium
Drenthe          2010         181.25
                 2020         225.24
Flevoland        2010         204.75
                 2020         271.94
Fryslân          2010         171.75
                 2020         215.12
...
Zuid-Holland     2010         221.25
                 2020         290.71
Zuid-Nederland   2010         221.50
                 2020         273.76
Name: Gemiddelde WOZ-waarde, dtype: float64

Let op dat de volgorde waarop je de lijst in groupby opstelt van belang is. In dit geval is Regio eerst, gevolgd door Decennium. Draai je ze om, dan zal het resultaat er zo uitzien:

Decennium  Regio
2010       Drenthe            181.25
           Flevoland          204.75
           Fryslân            171.75
           Gelderland         225.75
            ...
           Zuid-Holland       221.25
           Zuid-Nederland     221.50
2020       Drenthe            225.24
           Flevoland          271.94
           Fryslân            215.12
           Gelderland         284.00
            ...
           Zuid-Holland       290.71
           Zuid-Nederland     273.76
Name: Gemiddelde WOZ-waarde, dtype: float64

Conclusie

Je hebt gezien dat je met groupby gegevens kunt groeperen om er vervolgens berekeningen op uit te voeren. Dit kan op één kolom, maar ook op meerdere kolommen tegelijk. Met de agg-methode voer je verschillende berekeningen uit op de gegroepeerde gegevens.

In veel gevallen zul je groupby gebruiken als alternatief voor een draaitabel. Het voordeel is dat je de code eenmalig schrijft en daarna steeds opnieuw kunt gebruiken, ook als er nieuwe gegevens bijkomen. Bovendien kun je de berekeningen precies afstemmen op wat je nodig hebt.

Voor meer informatie over groupby kun je terecht in de documentatie van pandas.

Alle code voorbeelden en voorbeeldbestanden van dit project zijn ook op Github te bekijken.

Lees alle tutorials in deze reeks: Werken met pandas.

Over de auteur


Erwin Matijsen

Erwin Matijsen

Erwin is de oprichter van python-cursus.nl. In allerlei rollen heeft hij Python ingezet, van het eenvoudiger maken van zijn werk tot het opleveren van complete (web)applicaties. Met vrouw en kinderen woont hij in Havelte (Drenthe), midden in de prachtige natuur. Daar wandelt hij graag, zeker ook omdat de beste ingevingen tijdens een wandeling - weg van de computer - lijken te komen.


Contact

Training volgen?

Heb je behoefte aan een in-person training voor jezelf of je team? Dat kan! Laat je gegevens achter en dan nemen we zo snel mogelijk contact met je op de mogelijkheden te bespreken.


Contact

Vragen, opmerkingen?

Heb je vragen, opmerkingen, suggesties of tips naar aanleiding van deze blog? Neem dan contact met ons op, of laat het weten via Mastodon of LinkedIN.