seppedl 1639f05e6e Voeg Pandas bestandsformaat-converter demo toe
Programma dat CSV, Excel, JSON, vaste-breedte tekst, pickle en
Python-snippets onderling converteert via een Pandas DataFrame.
Demonstreert dtype-afleiding, datetime-parsing met tijdzonebeheer
(UTC-normalisatie) en het omzetten naar een lijst-van-dicts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 10:47:23 +02:00

Joppe — Pandas Bestandsformaat-Converter

Een klein demo-programma dat een databestand inleest in een Pandas DataFrame en het terug wegschrijft in een ander formaat. Gemaakt om een Python-ontwikkelaar te tonen hoe Pandas formaatconversie omvormt tot een tweetraps-pijplijn:

bestand op schijf  ──[lezer]──▶  DataFrame  ──[schrijver]──▶  bestand op schijf

Ondersteunde formaten (automatisch gekozen op basis van de bestandsextensie):

Extensie Formaat Pandas-lezer Pandas-schrijver
.txt Tekst met vaste breedte pd.read_fwf zelf gemaakt
.csv Kommagescheiden waarden pd.read_csv df.to_csv
.xlsx Excel-werkmap pd.read_excel df.to_excel
.json JSON (records) pd.read_json df.to_json
.pkl Python pickle pickle.loads pickle.dumps
.py Python-snippet (alleen uitvoer) zelf gemaakt

uv — Pakketbeheer voor Python

uv is een moderne, razendsnel pakketbeheerder voor Python. Het vervangt de combinatie van pip, venv en pip-tools met één enkel gereedschap.

uv installeren

Linux / macOS:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Via pip (als je al een werkende Python hebt):

pip install uv

Controleer de installatie:

uv --version

Nieuw project aanmaken

uv init mijn-project
cd mijn-project

uv init maakt automatisch aan:

Bestand Inhoud
pyproject.toml Projectnaam, Python-versie, dependencies
.python-version Gewenste Python-versie (bv. 3.14)
main.py Leeg startbestand
README.md Leeg documentatiebestand

Libraries toevoegen

uv add pandas openpyxl

uv add doet drie dingen tegelijk:

  1. Voegt de library toe aan dependencies in pyproject.toml.
  2. Bepaalt compatibele versies en schrijft ze vast in uv.lock.
  3. Installeert alles in de virtuele omgeving van het project (.venv/).

Een library verwijderen gaat met:

uv remove openpyxl

Een specifieke versie pinnen:

uv add "pandas>=2.0,<4"

pyproject.toml van dit project

[project]
name = "joppe"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
    "openpyxl>=3.1.5",
    "pandas>=3.0.2",
]

requires-python garandeert dat uv een compatibele Python-versie kiest. dependencies bevat de minimale versievereisten; de exacte vergrendelde versies staan in uv.lock.

Dit project installeren

Clone de repository en voer daarna uit:

uv sync

uv sync leest uv.lock, installeert de exacte versies en maakt .venv/ aan als die nog niet bestaat. Alle collega's krijgen zo exact dezelfde omgeving.

Scripts uitvoeren

uv run python main.py sample.txt sample.csv

uv run zorgt automatisch dat de virtuele omgeving van het project actief is. Je hoeft .venv/bin/activate nooit handmatig te roepen.

Wil je een gewone Python-REPL in de projectomgeving?

uv run python

Gebruik

uv run python main.py <invoerbestand> <uitvoerbestand>

Het programma kijkt naar de invoer-extensie om de lezer te kiezen, en naar de uitvoer-extensie om de schrijver te kiezen. Elke combinatie werkt.

Voorbeelden

Een voorbeeldbestand met vaste breedte, sample.txt, is meegeleverd om alle formaten te testen. Het bevat 5 rijen en 6 kolommen die een mix van datatypes dekken: integer (id, quantity), string (name), float (price), ISO-datetime met tijdzone (created_at) en datum in dd/mm/yyyy-formaat (delivery_date).

1. Vaste breedte → CSV

uv run python main.py sample.txt sample.csv
id,name,price,quantity,created_at,delivery_date
1,Widget,9.99,10,2026-05-08T12:30:00+0000,2026-05-15T00:00:00+0000
2,Gadget,19.95,5,2026-04-12T07:00:00+0000,2026-05-20T00:00:00+0000
3,Sprocket,0.5,250,2026-03-22T07:15:00+0000,2026-06-10T00:00:00+0000

Merk op dat created_at van +02:00 (lokale tijd Brussel) naar +0000 (UTC) is omgezet: de tijd schuift van 14:30 naar 12:30, maar het is hetzelfde absolute moment. Dat is de gouden regel — opslaan in UTC.

2. CSV → JSON

uv run python main.py sample.csv sample.json
[
  {
    "id": 1,
    "name": "Widget",
    "price": 9.99,
    "quantity": 10,
    "created_at": "2026-05-08T12:30:00.000Z",
    "delivery_date": "2026-05-15T00:00:00.000Z"
  }
]

date_format="iso" is meegegeven aan to_json zodat datums leesbaar blijven in plaats van als Unix-milliseconden te verschijnen.

3. JSON → Excel

uv run python main.py sample.json sample.xlsx

Excel-cellen kunnen geen tijdzone bevatten, dus het programma laat de tz-info vallen vlak voor het wegschrijven (de UTC-tijd zelf blijft behouden in de Excel-cel).

4. Excel → vaste breedte

uv run python main.py sample.xlsx roundtrip.txt

In de uitgevoerde .txt staat created_at weer in ISO 8601 met offset en delivery_date weer in dd/mm/yyyy — elk in zijn eigen kolomstijl.

5. Naar pickle (.pkl)

uv run python main.py sample.txt sample.pkl
# of vanuit CSV, JSON, Excel — werkt allemaal
uv run python main.py sample.csv sample.pkl

Het pickle-bestand bevat een lijst van gewone Python-dicts met standaard types (int, float, datetime). Daarna uitlezen:

import pickle

with open("sample.pkl", "rb") as f:
    records = pickle.load(f)

print(records[0])
# {'id': 1, 'name': 'Widget', 'price': 9.99, 'quantity': 10,
#  'created_at': datetime(2026, 5, 8, 12, 30, 0, tzinfo=timezone.utc), ...}

print(type(records[0]["id"]))           # <class 'int'>
print(type(records[0]["created_at"]))   # <class 'datetime.datetime'>

Een pickle opnieuw inlezen als DataFrame:

uv run python main.py sample.pkl sample.csv

6. Naar Python-snippet (.py)

uv run python main.py sample.txt sample.py
# of vanuit CSV, Excel, JSON, pickle — allemaal ondersteund
uv run python main.py sample.csv sample.py

Dit genereert een kant-en-klaar Python-bestand:

# Auto-gegenereerd door main.py
from datetime import datetime, timezone

DATA = [
    {
        'id': 1,
        'name': 'Widget',
        'price': 9.99,
        'quantity': 10,
        'created_at': datetime(2026, 5, 8, 12, 30, 0, tzinfo=timezone.utc),
        'delivery_date': datetime(2026, 5, 15, 0, 0, 0, tzinfo=timezone.utc),
    },
    ...
]

Je kunt dit bestand kopiëren in een project of direct importeren:

from sample import DATA

for row in DATA:
    print(row["name"], row["created_at"])

Datetimes zijn echte datetime-objecten (niet strings), zodat je ze meteen kunt gebruiken voor berekeningen of vergelijkingen.

7. Volledige rondreis

Je kan de conversies aaneenschakelen om te controleren dat er niets verloren gaat:

uv run python main.py sample.txt   stap1.csv
uv run python main.py stap1.csv    stap2.json
uv run python main.py stap2.json   stap3.xlsx
uv run python main.py stap3.xlsx   stap4.pkl
uv run python main.py stap4.pkl    eind.py

Van DataFrame naar lijst-van-dicts

De spil van de .pkl- en .py-uitvoer is df.to_dict(orient="records"). Dit zet elke rij van het DataFrame om naar één dict:

records = df.to_dict(orient="records")
# [{"id": 1, "name": "Widget", ...}, {"id": 2, ...}, ...]

En terug de andere kant op:

df = pd.DataFrame(records)

De orient-parameter

orient Resultaat Gebruik
"records" [{"col": val, ...}, ...] REST-API, fixtures
"dict" {"col": {0: val, 1: val, ...}} kolom-opzoektabellen
"list" {"col": [val, val, ...]} kolom-georiënteerde verwerking
"index" {0: {"col": val}, ...} rij-index als sleutel
"split" {"columns": [...], "data": [[...], ...]} compacte overdracht

Voor de meeste situaties is "records" de juiste keuze.

Opgelet: NumPy/Pandas-types in dicts

to_dict() levert soms numpy.int64 of pd.Timestamp terug — die zijn niet JSON-serialiseerbaar en gedragen zich subtiel anders dan ingebouwde Python-types. Converteer ze expliciet:

import numpy as np

def to_plain_python(row: dict) -> dict:
    result = {}
    for k, v in row.items():
        if isinstance(v, pd.Timestamp):
            result[k] = v.to_pydatetime()   # pd.Timestamp -> datetime
        elif isinstance(v, np.integer):
            result[k] = int(v)               # numpy.int64  -> int
        elif isinstance(v, np.floating):
            result[k] = float(v)             # numpy.float64 -> float
        else:
            result[k] = v
    return result

records = [to_plain_python(r) for r in df.to_dict(orient="records")]

Dit programma doet dit automatisch in df_to_records() — zodat zowel pickle als het Python-snippet correcte standaard-types bevatten.

Datums en tijdzones

Het demo-bestand mengt twee veelvoorkomende datumformaten in dezelfde rijen:

  • created_at in ISO 8601 met tijdzone-offset, bv. 2026-05-08T14:30:00+02:00 (lokale Brusselse tijd).
  • delivery_date in dag-eerst notatie, bv. 15/05/2026 (geen tijd, geen tijdzone).

Bij het inlezen normaliseert parse_datetimes() beide naar UTC met pd.to_datetime(..., format="mixed", utc=True). De truc:

  • format="mixed" laat Pandas per rij raden welk formaat van toepassing is — handig wanneer dezelfde kolom meerdere stijlen bevat.
  • utc=True zorgt dat alles tijdzone-bewust is in UTC, zodat naïeve en bewuste timestamps niet door elkaar lopen.

Voor de dd/mm/yyyy-kolom wordt extra dayfirst=True aangezet wanneer de waarden schuine strepen bevatten — anders zou Pandas "01/02/2026" als 2 januari lezen in plaats van 1 februari.

De drie nuttige tijdzone-operaties

# 1. Parse + normaliseer naar UTC in één stap (altijd veilig).
df["created_at"] = pd.to_datetime(df["created_at"], utc=True)

# 2. Plak een tijdzone op een naïeve timestamp (je weet welke zone bedoeld is).
df["created_at"] = df["created_at"].dt.tz_localize("Europe/Brussels")

# 3. Reken om naar een andere tijdzone (zelfde moment, andere weergave).
df["created_at"] = df["created_at"].dt.tz_convert("Europe/Brussels")

De gouden regel: opslaan in UTC, weergeven in lokale tijd. Roep operatie 3 alleen aan vlak voor het tonen of wegschrijven.

Wat te bestuderen in main.py

  • read_any / write_any — de keuze op basis van de bestandsextensie. Dat is het volledige idee van "formaatconversie" in 10 regels.
  • Argumenten van pd.read_fwfcolspecs, names, en vooral dtype. Vaste-breedte bestanden bevatten geen type-informatie, dus je moet Pandas vertellen wat elke kolom is.
  • parse_datetimes — laat zien hoe je gemengde datumformaten (ISO én dd/mm/yyyy) in één pass omzet naar UTC-Timestamps.
  • df_to_records + _to_plain_python — het omzetten van DataFrame naar lijst-van-dicts met schone Python-types.
  • write_fwf — een uitgewerkt voorbeeld van uitvoer bouwen vanuit een DataFrame door over kolommen en rijen te itereren. Handig wanneer er geen ingebouwde schrijver bestaat voor je doelformaat.

Bovenaan main.py staat een lange docstring die elke gebruikte lezer en schrijver doorloopt; lees die naast de code.

S
Description
No description provided
Readme 52 KiB
Languages
Python 100%