Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hoofdstuk 14 — Data inlezen en schrijven: CSV, Excel, Parquet en SQL

Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.

Doel van dit hoofdstuk:

  • Data inlezen uit en wegschrijven naar veelgebruikte formaten: CSV, Excel, Parquet en SQL.
  • Leren welke parameters belangrijk zijn voor correcte parsing (scheidingsteken, encoding, datums, dtypes, missende waarden).
  • Begrijpen van voor- en nadelen per formaat en performance-/geheugentips.

Aanbevolen voorkennis: Hoofdstuk 13 (pandas‑basis), Python/NumPy.

14.1 CSV: de klassieker

CSV is een tekstformaat met kolommen gescheiden door een delimiter (vaak , of ;). Het is universeel, maar mist schema en types.

14.1.1 Lezen met pd.read_csv

import pandas as pd

# Basic
df = pd.read_csv("data.csv")

# Veelgebruikte opties
# - sep: scheidingsteken (bijv. ';' in Nederlandse exporten)
# - encoding: bronencoding (bijv. 'utf-8', 'latin-1')
# - header/names: eerste rij als header of kolomnamen expliciet aangeven
# - usecols: subset van kolommen lezen
# - dtype: kolomtypes forceren
# - parse_dates: kolommen als datums parsen
# - na_values: extra strings als NaN behandelen
# - on_bad_lines: 'error' (default), 'warn' of 'skip'
# - engine: 'c' (snel), 'python' (toleranter)
# - nrows/skiprows: subset van rijen
# - compression: 'gzip', 'zip', 'bz2', ...

sales = pd.read_csv(
    "sales_2024.csv",
    sep=';',
    encoding='utf-8',
    usecols=["date", "product", "qty", "price"],
    dtype={"product": "string", "qty": "int32", "price": "float32"},
    parse_dates=["date"],
    na_values=["", "NA", "NULL", "-"],
)

Datum parsing tips:

  • parse_dates=["date"] laat pandas zelf detecteren. Voor exotische formaten kun je dayfirst=True of date_format (nieuwe pandas versies) gebruiken.
  • Grote snelheidswinst is mogelijk met date_format="%Y-%m-%d" indien constant formaat.

Grote bestanden:

# Streamen met chunksize om geheugen te sparen
it = pd.read_csv("big.csv", chunksize=100_000)

total_qty = 0
for chunk in it:
    total_qty += chunk["qty"].sum()

14.1.2 Schrijven met DataFrame.to_csv

# Schrijf zonder indexkolom, met semicolon delimiter en UTF-8
sales.to_csv("sales_out.csv", index=False, sep=';', encoding='utf-8')

# Gecomprimeerd
sales.to_csv("sales_out.csv.gz", index=False, compression='gzip')

Valkuilen:

  • Locale notaties (decimalen met , en duizendtallen met .); gebruik decimal=',' en thousands='.' bij het lezen.
  • Automatische type-inferentie kan strings/ID’s als numeriek zien; forceer dtype.
  • CSV heeft geen schema → verlies van types bij round-trips mogelijk.

14.2 Excel: werkbladen en tabbladen

Excel-bestanden zijn binair (XLSX). Je hebt meestal een extra engine nodig.

Installatie:

pip install openpyxl  # voor .xlsx lezen/schrijven

14.2.1 Lezen met pd.read_excel

# Specifieke sheet, kolommen en dtype
orders = pd.read_excel(
    "orders.xlsx",
    sheet_name="2024-Q1",
    usecols=["order_id", "date", "amount"],
    dtype={"order_id": "string", "amount": "float32"},
    parse_dates=["date"],
    engine="openpyxl",
)

# Meerdere sheets in één keer → dict van DataFrames
all_sheets = pd.read_excel("orders.xlsx", sheet_name=None, engine="openpyxl")

14.2.2 Schrijven met DataFrame.to_excel

# Eén sheet
orders.to_excel("orders_out.xlsx", index=False, sheet_name="Q1", engine="openpyxl")

# Meerdere sheets
with pd.ExcelWriter("orders_multi.xlsx", engine="openpyxl") as xw:
    orders.to_excel(xw, index=False, sheet_name="Q1")
    orders.sample(100).to_excel(xw, index=False, sheet_name="Sample")

Valkuilen:

  • Excel kan cellen als tekst/numeriek wisselend formatteren; dwing dtype in pandas.
  • Grootte en performance minder efficiënt dan kolom-georiënteerde formaten.

14.3 Parquet: kolom-georiënteerd, snel en gecomprimeerd

Parquet is een kolomarig, gecomprimeerd, schematisch formaat. Ideaal voor analytische workloads.

Installatie:

pip install pyarrow  # aanbevolen
# of
pip install fastparquet

14.3.1 Lezen met pd.read_parquet

import pandas as pd

df = pd.read_parquet("dataset.parquet")

# Alleen bepaalde kolommen
small = pd.read_parquet("dataset.parquet", columns=["date", "amount"]) 

14.3.2 Schrijven met DataFrame.to_parquet

# Compressie en engine instellen
orders.to_parquet("orders.parquet", engine="pyarrow", compression="snappy", index=False)

14.3.3 Partitionering en mappen

Veel data‑engineering workflows schrijven partities per sleutel (bijv. jaar/maand) als directorystructuur:

/transactions/
  year=2024/
    month=01/
      part-0000.parquet
    month=02/
      part-0000.parquet
  year=2025/
    month=01/

Lezen met partitiekolommen (pyarrow nodig):

# Vereist pyarrow.dataset onder water
# df = pd.read_parquet("transactions/", engine="pyarrow")

Voordelen Parquet:

  • Snellere I/O, kolomselectie, compressie (snappy/zstd/gzip), behoud van dtypes en datums. Nadelen:
  • Vereist extra dependency; minder universeel deelbaar dan CSV.

14.4 SQL: relationele databases

pandas kan lezen/schrijven via SQLAlchemy of DB-API connecties naar o.a. SQLite, PostgreSQL, MySQL.

Installatie voorbeeld (SQLite inbegrepen bij Python):

pip install sqlalchemy

14.4.1 Verbinden (SQLAlchemy engine)

from sqlalchemy import create_engine
engine = create_engine("sqlite:///example.db")

14.4.2 Lezen: pd.read_sql, read_sql_query, read_sql_table

import pandas as pd

# Volledige tabel
users = pd.read_sql_table("users", con=engine)

# Query (met parameter binding voor veiligheid)
query = "SELECT * FROM users WHERE country = :country AND age >= :min_age"
users_de = pd.read_sql_query(query, con=engine, params={"country": "DE", "min_age": 18})

14.4.3 Schrijven: DataFrame.to_sql

# Maak een DataFrame en schrijf naar SQL
import pandas as pd
new_users = pd.DataFrame({
    "user_id": [1, 2, 3],
    "name": ["Alice", "Bob", "Charlie"],
    "age": [25, 32, 29],
    "country": ["NL", "DE", "BE"],
})

# if_exists: 'fail' (default), 'replace', 'append'
new_users.to_sql("users", con=engine, if_exists="append", index=False)

Prestatietips:

  • Gebruik chunksize bij to_sql en read_sql voor grote datasets.
  • Stel dtype expliciet in bij to_sql voor kolomtypes (SQLAlchemy types).

Veiligheid:

  • Gebruik nooit string‑concatenatie voor parameters in SQL; gebruik params/bound parameters.

14.5 Encodings, NA’s, en locale issues

  • Encodings: probeer altijd UTF‑8; als het niet lukt, test latin-1 of bron‑encoding van de export.
  • Missende waarden: specificeer na_values (CSV), controleer na_filter, en pas fillna/dropna toe.
  • Decimalen en duizendtallen: decimal=',', thousands='.' bij lezen.
  • Dtypes: voorkom ongewenste object‑kolommen; gebruik dtype of achteraf astype.

14.6 Performance en geheugen

  • Gebruik kolomsubset (usecols voor CSV/Excel, columns voor Parquet) om minder te lezen.
  • Voor zeer grote CSV’s: overweeg conversie naar Parquet voor sneller hergebruik.
  • Werk met chunksize en verwerk streamend.
  • Let op low_memory bij read_csv: standaard True kan type‑inconsistenties geven; zet uit en specificeer dtype als mogelijk.

14.7 Mini‑workflow: CSV → schoonmaken → Parquet → SQL

import pandas as pd
from sqlalchemy import create_engine

# 1) Lees CSV met opties
raw = pd.read_csv(
    "transactions.csv",
    sep=';',
    parse_dates=["date"],
    dtype={"product_id": "string", "qty": "int32", "price": "float32"},
    na_values=["", "NA", "NULL"]
)

# 2) Eenvoudige schoonmaak
raw = raw.dropna(subset=["product_id", "qty"])  # vereiste velden
raw["amount"] = raw["qty"] * raw["price"]

# 3) Schrijf naar Parquet voor snel hergebruik
raw.to_parquet("transactions.parquet", compression="snappy", index=False)

# 4) Laad subset kolommen en schrijf naar SQL
subset = pd.read_parquet("transactions.parquet", columns=["date", "product_id", "amount"]) 
engine = create_engine("sqlite:///analytics.db")
subset.to_sql("transactions", con=engine, if_exists="replace", index=False)

14.8 Samenvatting

Je leerde:

  • CSV/Excel lezen en schrijven met cruciale parameters voor delimiter, encoding, dtypes en datums.
  • Parquet gebruiken voor performance, compressie en schema‑behoud.
  • SQL‑bronnen aan te spreken via SQLAlchemy met veilige parameterbinding en to_sql/read_sql. Met deze I/O‑basis kun je betrouwbare data‑pijplijnen opzetten en datasets efficiënt hergebruiken.