Gegevens inlezen en opslaan met pandas

Gegevens inlezen en opslaan met pandas

13 juni 2024
Erwin Matijsen
Geplaatst in Tutorial
Onderdeel van Werken met pandas


Gegevens inlezen en opslaan met pandas

In een vorige tutorial heb je kennisgemaakt met pandas, een veelgebruikte tool voor het werken met gegegevens in Python.

In pandas werk je voornamelijk met gegevens in een DataFrame (of een Series), maar je gegevens komen vaak ergens anders vandaan. In deze tutorial leer je hoe je gegevens inleest vanuit CSV, Excel en JSON. Ook leer je hoe je een DataFrame weer opslaat in één van deze formaten.

Voordat je begint

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

shell
cd projects
mkdir pandas_gegevens && cd pandas_gegevens
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

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

Series en DataFrames

Veelal zul je in pandas met Series en DataFrames werken. Maar wat is een DataFrame precies? Je kunt een DataFrame het best vergelijken met een tabel zoals je die ziet als je een spreadsheet opent (bijvoorbeeld in Excel). Je hebt dus rijen en kolommen.

DataFrame

De donkergrijze rij bevat de kolomnamen en de donkergrijze eerste kolom bevat de index (rijnummers of namen).

Series

Omdat een kolom van het type Series is, is het handig om hiermee te beginnen. Een Series is een 1-dimensionale lijst die elk type data kan bevatten, bijvoorbeeld int, str of date. Voor beginners is het soms verwarrend, maar een Series heeft ook een index, ofwel labels.

Series

De donkergrijze blokken zijn samen de index, terwijl de lichtgrijze blokken de gegevens bevatten. Dus een Series is 1-dimensionaal (het is één lijst met gegevens), maar elk gegeven heeft wel een index. Maak je een Series aan zonder de index te specificeren, dan loopt de index simpelweg van 0 tot n.

Om een Series te maken, plaats je in main.py bovenin de import import pandas as pd. Deze manier van pandas importeren is een gewoonte die is ontstaan en die je overal zult zien. Het is dus handig jezelf dit ook aan te leren.

Vervolgens kun je een eenvoudige Series maken zonder index op te geven. Plaats hiervoor een lijst met gegevens in pd.Series().

main.py
import pandas as pd

# Maak een Series aan zonder index
s = pd.Series(["aap", "noot", "mies"])
print(s)
0     aap
1    noot
2    mies
dtype: object

Je ziet dat de index nu loopt van 0 t/m 2. Verder zie je dtype: object. Dit is kort voor datatype. In dit geval is het type "object", wat elk Python-object kan omvatten, inclusief str. Standaard slaat pandas tekst op als "object". Dit kan voor fouten zorgen, want een object kan ook iets anders zijn, en je kunt dus per ongeluk verschillende objecten in je Series plaatsen.

Om dit te voorkomen kun je opgeven dat je string of pd.StringDtype() als dtype wilt:

main.py
# Maak een Series aan zonder index, met type `str`
s = pd.Series(["aap", "noot", "mies"], dtype="string")
# s = pd.Series(["aap", "noot", "mies"], dtype=pd.StringDtype())
print(s)
0     aap
1    noot
2    mies
dtype: string

Je ziet dat dtype nu wel "string" is.

Je kunt ook een eigen index meegeven:

main.py
# Maak een Series aan met index
s = pd.Series(["aap", "noot", "mies"], index=["a", "b", "c"], dtype="string")
print(s)
a     aap
b    noot
c    mies
dtype: string

En nu kun je de waarde op index "a" ophalen:

main.py
# Haal waarde op index 'a' op
aap = s["a"]
print(aap)  # aap

Tot slot kun je ook een Series aanmaken op basis van een dict, waarbij de keys de index zullen vormen. Vergeet dan niet de dtype achteraf nog in te stellen op "string".

main.py
# Maak een Serie op basis van een `dict`
d = {
    "a": "aap",
    "b": "noot",
    "c": "mies"
}

s = pd.Series(d).astype("string")  # Stel de dtype in op "string"
print(s)
a     aap
b    noot
c    mies
dtype: string

DataFrame

Een DataFrame is feitelijk een combinatie van meerdere Series, waarbij de index van de Series vervalt en de DataFrame als geheel een index krijgt.

main.py
# Maak een DataFrame op basis van een `dict` van `Series`.
data = {
    "kolom1": s,  # s is de eerder aangemaakte Serie
    "kolom2": s,  # s is de eerder aangemaakte Serie
}
df = pd.DataFrame(data)
print(df.head())
    kolom1 kolom2
a    aap    aap
b   noot   noot
c   mies   mies

Met df.head() toon je de eerste 5 rijen van een DataFrame. Je ziet dat de keys van de dict de kolomnamen zijn geworden. De index is ingesteld op "a", "b" en "c", omdat beide Series dit ook hadden. Kan pandas geen goede index bepalen, dan zal het simpelweg 0, 1, 2 worden.

Je kunt een DataFrame ook maken op basis van een dict met lijsten. Stel ook de index in.

main.py
# Maak een DataFrame op basis van een `dict` van lijsten
data = {
    "kolom1": [1, 2, 3],
    "kolom2": [4, 5, 6]
}
df = pd.DataFrame(data, index=["rij1", "rij2", "rij3"])
print(df.head())
        kolom1  kolom2
rij1       1       4
rij2       2       5
rij3       3       6

Je kunt nu een enkele kolom ophalen op basis van de kolomnaam:

main.py
# Haal een kolom op op basis van label
kolom1 = df["kolom1"]
print(kolom1)
rij1    1
rij2    2
rij3    3
Name: kolom1, dtype: int64

Met .loc[] haal je een rij op, op basis van de index.

main.py
# Haal een rij op op basis van index
rij_2 = df.loc["rij2"]
print(rij_2)
print(type(rij_2))
kolom1    2
kolom2    5
Name: rij2, dtype: int64
<class 'pandas.core.series.Series'>

Je ziet dat ook een rij een Series is, en dat de kolomnamen de index van de Series zijn geworden.

Verder lezen

Je hebt nu een basisbegrip van Series en DataFrames. Er valt nog veel meer over te vertellen, bijvoorbeeld hoe je meerdere kolommen en rijen kunt selecteren en hoe je gegevens verwijdert, toevoegt en wijzigt. Wil je meer lezen, bekijk dan de introductie op data structuren in de documentatie van pandas. Ook de documentatie over indexing is interessant.

Omdat de documentatie van pandas soms overweldigend kan zijn, zullen we in de toekomst ook aparte tutorials wijden aan het werken met Series en DataFrames, en dan met name het selecteren van gegevens.

CSV

Een CSV-bestand (Comma Separated Values) is een veelgebruikt bestandsformaat voor het uitwisselen van gegevens tussen verschillende applicaties. Het is een eenvoudig tekstbestand waarin elke regel een gegevensrecord voorstelt en de waarden in elk record door komma's (of puntkomma's) van elkaar gescheiden zijn. CSV-bestanden zijn populair omdat ze eenvoudig te openen en bewerken zijn in een teksteditor en omdat je er eenvoudig mee kunt werken in allerlei programma's en programmeertalen.

Opslaan als CSV

Maak nogmaals een eenvoudige Dataframe aan:

main.py
# Maak een DataFrame op basis van een `dict` van lijsten
data = {
    "kolom1": [1, 2, 3],
    "kolom2": [4, 5, 6]
}
df = pd.DataFrame(data, index=["rij1", "rij2", "rij3"])
print(df.head())

Deze Dataframe sla je nu eenvoudig op naar een CSV-bestand. Je gebruikt hiervoor de methode to_csv. Dit is een instance-methode, dus je roept het aan vanaf je net aangemaakte Dataframe: df.to_csv().

Er zijn veel opties beschikbaar voor to_csv. In de meeste gevallen heb je er maar enkele nodig. De belangrijkste is natuurlijk het eerste argument: het pad naar een CSV-bestand. Verder zijn sep en index nog interessant. Met sep geef je aan wat het scheidingsteken moet zijn. Standaard is dit een komma (,), maar zelf geef ik altijd de voorkeur aan ;. Ook kies ik er meestal voor de index niet weg te schrijven in het CSV-bestand, zodat je een schoon bestand overhoudt met enkel de kolomnamen en de gegevens. Het geheel ziet er dan zo uit:

main.py
# Schrijf de DataFrame naar een CSV-bestand
df.to_csv("df1.csv", sep=";", index=False)

En zo eenvoudig is het: je hebt een CSV-bestand genaamd df1.csv opgeslagen! Voor alle opties van to_csv lees je de documentatie. Overigens kun je to_csv ook op een Series aanroepen.

CSV inlezen

Andersom - dus het inlezen van een CSV-bestand naar een DataFrame - werkt net zo eenvoudig. Je gebruikt hiervoor read_csv. Deze roep je aan vanaf pd (pandas). Ook hier geef je weer een bestandsnaam op en eventueel het scheidingsteken (als dat geen komma is).

main.py
# Lees een CSV-bestand in naar een DataFrame
df1 = pd.read_csv("df1.csv", sep=";")
print(df1.head())

Ook hier zijn weer veel extra opties mogelijk die helpen bij het goed inlezen van de data. Een paar verdienen extra aandacht.

Datum

Met parse_dates en date_format kun je aangeven welke kolommen een datum bevatten, en in welk formaat. Stel dat je een CSV-bestand genaamd datums.csv hebt met de volgende inhoud:

datums.csv
Datum;Aantal
2024-01-01;4
2024-01-02;6
2024-01-03;3
2024-01-04;8
2024-01-05;10

Met parse_dates en date_format kun je nu aangeven hoe pandas de kolom Datum moet inlezen.

main.py
# Lees een CSV-bestand in met een datum kolom
df_datums = pd.read_csv("datums.csv", sep=";", parse_dates=["Datum"], date_format="%Y-%m-%d")
print(df_datums.dtypes)
Datum     datetime64[ns]
Aantal             int64
dtype: object

Je ziet dat Datum nu een dtype van datetime65[ns] heeft, wat wil zeggen dat het niet meer een tekst is, maar echt een datum-formaat. Dit maakt het mogelijk om er allerlei datumafhankelijke bewerkingen mee te doen, zoals het groeperen op maand, of eenvoudig selecteren van alle vrijdagen om maar iets te noemen.

Zie de documentatie van strftime om te leren hoe de datumnotatie werkt.

Missende waarden

Een ander handigheidje is dat je kunt bepalen hoe er met missende waarden omgegaan moet worden. Standaard worden al heel wat teksten herkend als missende waarden, namelijk:

['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']

Maar heb je zelf ook waarden die als missend herkend moeten woorden, bijvoorbeeld ?, dan kan dat met na_values. Neem bijvoorbeeld het CSV-bestand missend.csv met de volgende inhoud:

missend.csv
kolom1;kolom2
1;4
2;?
3;6

Regel 2, kolom 2 bevat een vraagteken wat pandas moet herkennen als missende waarde.

main.py
# Lees een CSV-bestand met missende waarden in
df_missend = pd.read_csv("missend.csv", sep=";", na_values="?")
print(df_missend.head())
      kolom1  kolom2
0       1     4.0
1       2     NaN
2       3     6.0

Je ziet dat op regel 2, kolom 2 nu geen vraagteken meer bevat, maar NaN, waarmee pandas aangeeft dat het een missende waarde is (Not a Number).

Er zijn nog veel meer mogelijkheden met read_csv, veel heb je alleen in uitzonderlijke gevallen nodig. Bekijk de volledige documentatie om meer te leren.

Excel

Je hebt leren werken met CSV-bestanden, een formaat dat je veel tegenkomt om gegevens uit te wisselen. Een ander veel voorkomend formaat is Excel. Ook hier kan pandas goed mee omgaan, en eigenlijk werkt het vrijwel hetzelfde als werken met CSV. In plaats van to_csv heb je to_excel en in plaats van read_csv heb je read_excel. De opties zijn grotendeels hetzelfde.

Er is echter één belangrijk verschil: voor het werken met Excel heb je nog een andere package nodig die het daadwerkelijk lezen/schrijven doet. Er zijn enkele pakketen beschikbaar:

Pakket Formaat
openpyxl .xlsx
xlrd .xls
pyxlsb .xlsb
calamine alle

Installeer één van deze pakketten in je venv, zodat pandas ermee kan werken. Je hoeft verder niets te importeren, dat handelt pandas voor je af.

Een belangrijk verschil tussen CSV en Excel is dat je in een Excel-bestand meerdere werkbladen (sheets) kunt hebben. Met de parameter sheet_name kun je aangeven welke sheet(s) je wilt inlezen. Standaard staat dit op 0, het eerste werkblad. Je kunt de index gebruiken, of de naam van een werkblad. Je kunt ook een lijst met meerdere werkbladen opgeven. Het resultaat is dan een dict met de namen als sleutel en de DataFrames als waarde.

Een kort voorbeeld. Installeer eerst openpyxl:

shell
python -m pip install openpyxl

Vervolgens kun je het tweede werkblad van een Excel-bestand genaamd excel.xlxs inlezen.

main.py
# Lees het tweede werkblad van een Excel-bestand in
df_excel = pd.read_excel("excel.xlsx", sheet_name=1)
print(df_excel.head())
    7   10
0   8  11
1   9  12

Om een DataFrame naar Excel te schrijven, gebruik je to_excel, wat vrijwel hetzelfde werkt als to_csv. Ook hiervoor heb je een extra package nodig. Je kunt hiervoor xlsxwriter of openpyxl gebruiken.

Lees de documentatie om meer te leren over het werken met Excel.

JSON

Ook het werken met JSON is redelijk vergelijkbaar als met CSV-bestanden en Excel-bestanden. Maar daar waar CSV en Excel erg lijken op een DataFrame (rijen en kolommen), is dit bij JSON niet het geval.

Een extra optie bij to_json en read_json is dan ook orient, waarmee je bepaalt hoe de vertaalslag van rijen/kolommen naar of van een meer dict-achtige structuur gemaakt moet worden. De standaard bij DataFrames is columns.

Stel je hebt het volgende JSON-object, opgeslagen in json-bestand.json:

json-bestand.json
{
  "kolom1": {
    "rij1": 1,
    "rij2": 2,
    "rij3": 3
  },
  "kolom2": {
    "rij1": 4,
    "rij2": 5,
    "rij3": 6
  }
}

De volgende code leest het bestand in en zet het om naar een DataFrame:

main.py
# Lees een JSON bestand in
df_json = pd.read_json("json-bestand.json")
print(df_json.head())

Het resultaat is dan dat de eerste sleutels ("kolom1", "kolom2") de kolom-koppen worden. De tweede-niveau sleutels ("rij1", "rij2", "rij3") vormen de index.

        kolom1  kolom2
rij1       1       4
rij2       2       5
rij3       3       6

Zie de documentatie om meer te leren over de verschillende oriëntatiemogelijkheden.

Andere bestanden

Naast het werken met CSV, Excel en JSON zijn er nog tal van andere mogelijkheden om gegevens naar op te slaan of vanuit te openen. Een aantal voorbeelden zijn HTML, XML, Parquet en SQL. Zie de documentatie voor een volledige lijst en alle mogelijkheden.

Conclusie

Gegevens kunnen overal vandaan komen en veel verschillende formaten hebben. In pandas zul je echter meestal met een DataFrame of een Series werken. Gelukkig heeft pandas vele methoden om eenvoudig gegevens in te lezen of juist weg te schrijven naar bijvoorbeeld CSV, Excel en JSON.

Alle methodes werken ongeveer hetzelfde. Gebruik to_* om naar een bepaald formaat te schrijven en gebruik read_* om vanuit een bepaald formaat in te lezen. Voor de details kun je altijd de documentatie erbij pakken!

Succes!

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

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

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.