| Index | Spalte | Datentyp | Fehlend (%) | Unique | |
|---|---|---|---|---|---|
| 0 | 1 | id | int64 | 0.0 | 3916 |
| 1 | 2 | body_key | object | 0.0 | 1 |
| 2 | 3 | fullname | object | 0.0 | 3868 |
| 3 | 4 | firstname | object | 0.0 | 1441 |
| 4 | 5 | lastname | object | 0.0 | 2470 |
| 5 | 6 | birthday | datetime64[ms] | 6.7 | 3542 |
| 6 | 7 | gender | object | 6.6 | 2 |
| 7 | 8 | party_de | object | 7.4 | 77 |
| 8 | 9 | party_harmonized_de | object | 18.5 | 21 |
| 9 | 10 | electoral_district_de | object | 7.0 | 74 |
| 10 | 11 | parliament_sector | object | 93.7 | 2 |
| 11 | 12 | active | object | 0.4 | 2 |
Datenbericht
1 Datenbericht
Der Datenbericht dokumentiert alle im Projekt verwendeten Daten, von der Rohdatenbeschaffung bis hin zu den drei abgeleiteten Analyse-Datensätzen. Ziel ist die Nachvollziehbarkeit der Befunde und die Möglichkeit, den vollen Datenfluss von Dritten reproduzieren zu lassen.
1.1 Rohdaten
1.1.1 Übersicht der Rohdatensätze
Wir beziehen die Daten vom öffentlichen Export der OpenParlData-Plattform [1]. Die Plattform bietet eine API, wir nutzen jedoch direkt die Quelldateien unter files.openparldata.ch/exports/, die wöchentlich als gzippte NDJSON-Dateien aktualisiert werden. Für unsere Fragestellung (Alter, Eintritt und Amtsdauer von Bundespolitiker:innen) reichen zwei der rund 15 verfügbaren Exports.
| Name | Quelle | Speicherort |
|---|---|---|
persons.ndjson |
OpenParlData Export: Personeneinträge inkl. Biografie, Partei, Wahlkreis. | data/raw/persons.ndjson (entpackt), bezogen von https://files.openparldata.ch/exports/persons.ndjson.gz |
memberships.ndjson |
OpenParlData Export: Mitgliedschaften in Gremien (Räte, Kommissionen, Fraktionen, Parteien). | data/raw/memberships.ndjson (entpackt), bezogen von https://files.openparldata.ch/exports/memberships.ndjson.gz |
Das Beschaffungs-Skript eda/1_gather_data.py lädt beide Dateien herunter, entpackt sie und konvertiert sie nach Parquet. Bereits vorhandene Dateien werden nicht erneut heruntergeladen.
# eda/1_gather_data.py (Auszug)
OPENPARL_BASE = "https://files.openparldata.ch/exports"
FILES = ["persons", "memberships"]
def ensure_ndjson(name: str) -> Path:
ndjson_path = RAW_DIR / f"{name}.ndjson"
gz_path = RAW_DIR / f"{name}.ndjson.gz"
if ndjson_path.exists():
return ndjson_path
if not gz_path.exists():
urllib.request.urlretrieve(f"{OPENPARL_BASE}/{name}.ndjson.gz", gz_path)
with gzip.open(gz_path, "rb") as f_in, open(ndjson_path, "wb") as f_out:
shutil.copyfileobj(f_in, f_out)
return ndjson_pathRechtliches und Governance. OpenParlData ist eine öffentliche Initiative ohne formelle Lizenzangabe; die zugrundeliegenden Daten stammen aus den offiziellen Biografien der parlament.ch Webseite und sind als öffentlich zugängliche Verwaltungsdaten zu verstehen. Die Daten sind somit als “öffentlich” einzuordnen.
1.1.2 Details persons.ndjson
Der Personen-Export enthält biografische Stammdaten zu allen je erfassten Mandatsträger:innen auf Bundes- und Kantonsebene: Name, Geburtsdatum, Geschlecht, Partei, Wahlkreis, parlamentarischer Sektor (Nationalrat oder Ständerat), aktuelle Aktivität sowie eine Vielzahl von Querverweisen auf externe IDs (Wikidata, parlament.ch). Wir filtern für unsere Analyse auf body_key == "CHE", also auf die Bundesebene.
1.1.2.1 Datenkatalog
Der vollständige Katalog mit allen 60 Spalten liegt unter eda/output/catalogue_persons_clean_CHE.csv. Hier sind die für die Altersanalyse relevanten Spalten:
1.1.2.2 Datencharakteristik und Qualität
Die zentrale Variable für unsere Analyse ist birthday. Auf Bundesebene ist die Abdeckung exzellent:
CHE-Personen total: 3,916
Davon mit Geburtsdatum: 3,655 (93.3 %)
Im plausiblen Bereich (1800–2015): 3,576
Frühester Geburtstag: 1800-01-18
Spätester Geburtstag: 1996-12-26
Die Geschlechtsangabe ist ebenfalls flächendeckend erfasst:
| Geschlecht | Anzahl | Anteil (%) | |
|---|---|---|---|
| 0 | m | 3352 | 85.6 |
| 1 | f | 307 | 7.8 |
| 2 | (fehlend) | 257 | 6.6 |
Bei der Parteizugehörigkeit fällt auf, dass viele Parteinamen über die Zeit gewandert sind (CVP, Mitte, FDP-Liberale usw.). OpenParlData stellt unter party_harmonized_de bereits konsolidierte Bezeichnungen bereit; wir nutzen sie für historische Vergleiche.
| Anzahl | |
|---|---|
| party_harmonized_de | |
| FDP.Die Liberalen | 1211 |
| Christlichdemokratische Volkspartei der Schweiz | 732 |
| None | 723 |
| Sozialdemokratische Partei | 493 |
| Schweizerische Volkspartei | 335 |
| Liberal-Demokratische Partei | 108 |
| Die Grünen | 65 |
| Landesring der Unabhängigen | 62 |
| Die Mitte | 53 |
| Grünliberale Partei | 26 |
1.1.3 Details memberships.ndjson
Der Mitgliedschafts-Export enthält pro Person eine Zeile pro Gremium, in dem die Person sass oder noch sitzt: Nationalrat, Ständerat, Kommissionen, Fraktionen, Interessengruppen, Parteien. Jede Mitgliedschaft hat ein Anfangs- und Enddatum (begin_date, end_date), einen Typ (type_harmonized_de) und einen Gruppennamen (group_name_de). Diese Doppelung erlaubt es uns, die Parlamentssitze (Typ Parlament (Legislativrat) mit Gruppe Nationalrat oder Ständerat) sauber zu isolieren.
1.1.3.1 Datenkatalog
Vollständiger Katalog: eda/output/catalogue_memberships_clean_CHE.csv. Relevant für unsere Analyse:
| Index | Spalte | Datentyp | Fehlend (%) | Unique | |
|---|---|---|---|---|---|
| 0 | 1 | id | int64 | 0.0 | 22122 |
| 1 | 2 | body_key | object | 0.0 | 1 |
| 2 | 3 | person_id | float64 | 0.0 | 3708 |
| 3 | 4 | group_name_de | object | 0.0 | 445 |
| 4 | 5 | role_name_de | object | 27.1 | 15 |
| 5 | 6 | begin_date | datetime64[ms] | 24.3 | 1544 |
| 6 | 7 | end_date | datetime64[ms] | 29.9 | 2044 |
| 7 | 8 | active | object | 2.0 | 2 |
| 8 | 9 | type_harmonized_de | object | 0.0 | 9 |
1.1.3.2 Datencharakteristik und Qualität
Auf Bundesebene existieren rund 22’000 Mitgliedschaftseinträge. Die Mehrheit entfällt auf Kommissionen und Interessengruppen; für unsere Frage interessieren ausschliesslich die Mitgliedschaften vom Typ Parlament (Legislativrat):
Innerhalb des Typs Parlament (Legislativrat) ergibt sich eine klare Zweiteilung in Nationalrat und Ständerat:
| Anzahl | |
|---|---|
| group_name_de | |
| Nationalrat | 4398 |
| Ständerat | 1219 |
Das Beobachtungsfenster reicht bis 1848 zurück:
Frühestes begin_date: 1848-11-06
Spätestes begin_date: 2026-06-01
Anteil ohne end_date (laufende Mandate): 4.4 %
1.2 Verarbeitete Daten
Aus den beiden Rohdatensätzen erzeugt eda/2_etl_politicians.py drei kompakte Analyse-Tabellen. Die Verarbeitungsschritte sind:
- Bundesebene isolieren (
body_key == "CHE"). - Geburtstage validieren (Geburtsjahr zwischen 1800 und 2015).
- Parlamentssitze isolieren (
type_harmonized_de == "Parlament (Legislativrat)"und Gruppe Nationalrat oder Ständerat). - Join Mitgliedschaften × Personen via
person_id. - Anreichern pro Mitgliedschaft mit
entry_age,exit_age,duration_years.
# eda/2_etl_politicians.py (Auszug)
parl["entry_age"] = ((parl["begin_date"] - parl["birthday"]).dt.days / 365.25).round(2)
parl["exit_age"] = ((parl["end_date_eff"] - parl["birthday"]).dt.days / 365.25).round(2)
parl["duration_years"] = ((parl["end_date_eff"] - parl["begin_date"]).dt.days / 365.25).round(2)Für laufende Mandate (kein end_date) verwenden wir das heutige Datum als provisorisches Ende; die betroffenen Zeilen werden über is_active = True markiert, sodass nachgelagerte Auswertungen sie bei Bedarf ausschliessen können.
1.2.1 Übersicht der verarbeiteten Datensätze
| Name | Beschreibung | Speicherort |
|---|---|---|
politicians.parquet |
Eine Zeile pro Bundespolitiker:in mit aggregierten Karriere-Kennzahlen. | data/processed/politicians.parquet |
parliament_memberships.parquet |
Eine Zeile pro NR- oder SR-Mitgliedschaft mit Alters- und Dauer-Berechnungen. | data/processed/parliament_memberships.parquet |
parliament_yearly.parquet |
Jahres-Snapshot per 1. Juli: wer war im Parlament, wie alt? | data/processed/parliament_yearly.parquet |
1.2.2 Details politicians.parquet
Eine Zeile pro Person, die mindestens einmal in Nationalrat oder Ständerat sass und ein erfasstes Geburtsdatum hat.
| Index | Spalte | Datentyp | Fehlend (%) | Unique | |
|---|---|---|---|---|---|
| 0 | 1 | person_id | float64 | 0.0 | 3546 |
| 1 | 2 | firstname | object | 0.0 | 1307 |
| 2 | 3 | lastname | object | 0.0 | 2256 |
| 3 | 4 | birthday | datetime64[ms] | 0.0 | 3437 |
| 4 | 5 | gender | object | 0.0 | 2 |
| 5 | 6 | party_de | object | 0.5 | 77 |
| 6 | 7 | party_harmonized_de | object | 11.8 | 21 |
| 7 | 8 | canton | object | 0.0 | 74 |
| 8 | 9 | first_entry_date | datetime64[ms] | 0.0 | 680 |
| 9 | 10 | last_seat_end | datetime64[ms] | 0.0 | 1146 |
| 10 | 11 | entry_age | float64 | 0.0 | 2156 |
| 11 | 12 | total_tenure_years | float64 | 0.0 | 1365 |
| 12 | 13 | num_memberships | int64 | 0.0 | 9 |
| 13 | 14 | chambers | object | 0.0 | 3 |
| 14 | 15 | currently_active | bool | 0.0 | 2 |
| 15 | 16 | age_today | float64 | 0.0 | 1572 |
Anzahl Bundespolitiker:innen in der Analyse: 3,546
Davon aktuell im Amt: 237
Univariate Statistiken (Jahre):
| entry_age | total_tenure_years | age_today | |
|---|---|---|---|
| count | 3546.0 | 3546.0 | 3546.0 |
| mean | 46.3 | 10.7 | 134.7 |
| std | 8.7 | 7.8 | 51.6 |
| min | 21.3 | -0.0 | 29.4 |
| 10% | 34.9 | 2.4 | 67.4 |
| 50% | 46.3 | 9.0 | 133.7 |
| 90% | 57.7 | 21.0 | 207.2 |
| max | 81.4 | 48.5 | 226.3 |
1.2.3 Details parliament_memberships.parquet
Eine Zeile pro einzelne Mitgliedschaft im Nationalrat oder Ständerat.
| Index | Spalte | Datentyp | Fehlend (%) | Unique | |
|---|---|---|---|---|---|
| 0 | 1 | person_id | float64 | 0.0 | 3546 |
| 1 | 2 | firstname | object | 0.0 | 1307 |
| 2 | 3 | lastname | object | 0.0 | 2256 |
| 3 | 4 | birthday | datetime64[ms] | 0.0 | 3437 |
| 4 | 5 | gender | object | 0.0 | 2 |
| 5 | 6 | party_de | object | 0.4 | 77 |
| 6 | 7 | party_harmonized_de | object | 10.1 | 21 |
| 7 | 8 | electoral_district_de | object | 0.0 | 74 |
| 8 | 9 | chamber | object | 0.0 | 2 |
| 9 | 10 | begin_date | datetime64[ms] | 0.0 | 757 |
| 10 | 11 | end_date | datetime64[ms] | 4.4 | 1279 |
| 11 | 12 | end_date_eff | datetime64[ms] | 0.0 | 1280 |
| 12 | 13 | entry_age | float64 | 0.0 | 2776 |
| 13 | 14 | exit_age | float64 | 0.0 | 2892 |
| 14 | 15 | duration_years | float64 | 0.0 | 1140 |
| 15 | 16 | is_active | bool | 0.0 | 2 |
1.2.4 Details parliament_yearly.parquet
Jahres-Snapshot: für jedes Jahr von 1850 bis 2025 eine Zeile pro Person, die am 1. Juli dieses Jahres in NR oder SR sass, plus ihr Alter zu jenem Stichtag. Dieser Datensatz ist Kernstück der Haupt-Visualisierung.
| Index | Spalte | Datentyp | Fehlend (%) | Unique | |
|---|---|---|---|---|---|
| 0 | 1 | person_id | float64 | 0.0 | 3478 |
| 1 | 2 | firstname | object | 0.0 | 1274 |
| 2 | 3 | lastname | object | 0.0 | 2224 |
| 3 | 4 | chamber | object | 0.0 | 2 |
| 4 | 5 | gender | object | 0.0 | 2 |
| 5 | 6 | party_de | object | 0.5 | 76 |
| 6 | 7 | party_harmonized_de | object | 9.8 | 21 |
| 7 | 8 | age | float64 | 0.0 | 4559 |
| 8 | 9 | year | int64 | 0.0 | 176 |
1.3 Schlüsselbefunde
| Befund | Quelle |
|---|---|
| Median-Alter NR in den 2020ern: 51.6 Jahre, so jung wie seit 100 Jahren nicht mehr. | parliament_yearly Median pro Jahrzent |
| Median-Alter SR liegt aktuell bei rund 60 Jahren und ist konstant höher als NR. | parliament_yearly Median pro Jahrzent |
| Höhepunkt der Alterung war nicht heute, sondern in den 1950ern (NR 56, SR 61). | parliament_yearly Median pro Jahrzent |
| Eintrittsalter ist seit 1880 stabil bei 46 bis 49 Jahren. | politicians.entry_age Median pro Eintrittsjahrzent |
1.4 Datenqualität und Limitierungen
- Geburtstagslücke: Rund 7 % der Bundespolitiker:innen haben kein erfasstes Geburtsdatum. Die Lücke konzentriert sich auf historische Datensätze. Für moderne Mandate ist die Abdeckung praktisch vollständig.
- Aktive Mandate: Für laufende Mandate ist
end_dateleer; für Berechnungen wird das heutige Datum als provisorisches Ende verwendet. - Parteibezeichnungen: Parteinamen ändern sich (CVP zu Die Mitte, FDP zu FDP-Liberale). Wir nutzen die harmonisierten Bezeichnungen
party_harmonized_de. - Stichtag des Snapshots: Wir messen die Altersverteilung jeweils am 1. Juli. Mandate, die zwischen Januar und Juli beginnen oder enden, sind in dem jeweiligen Jahr je nach Datum enthalten oder nicht.
1.5 Reproduzierbarkeit
uv sync
uv run python eda/1_gather_data.py # Download + NDJSON zu Parquet
uv run python eda/2_etl_politicians.py # ETL für die Altersanalyse
uv run python eda/3_eda.py # EDA-Dump in eda/output/
uv run streamlit run deployment/app.py # Interaktives VisualisierungsproduktDie vollständigen EDA-Outputs befinden sich im Ordner eda/output/.