Titelbild zum Beitrag. Vor dem Hintergrund einer großen Bus-Abfahrtstafel werden drei unterschiedliche Grafiken aus dem Beitrag angeteasert. Ein stilisierter Auszug aus einer Fahrplan-Datei ist allem überlagert.

Einen neuen Fahrplan mithilfe von Daten nachvollziehen

Vor einigen Jahren habe ich eine Python-Bibliothek geschrieben, mit der Soll-Fahrplandaten im Format DINO 2.1 verarbeitet werden können. Mit SQLAlchemy wird eine objekt-relationale Abbildung bereitgestellt.

Ich habe direkt einige Tools implementiert, mit denen verschiedene zusammenfassende Exports oder Berechnungen gemacht werden können. Dazu gehört die Summe an gefahrenen Kilometern im ganzen Fahrplan oder auch die Anzahl an Abfahrten ab allen Haltestellen und zwischen direkten Haltestellenpaaren. Außerdem stelle ich den Tabellenexport für Tabellen im MediaWiki-Format vor. Damit können viele Informationen über Abfahrten und Haltestellen zusammengefasst werden. Die textliche Differenz bei Aktualisierungen der Tabelle kann dabei helfen, kleine Änderungen in den Fahrplandaten nachzuvollziehen.

Der Verkehrsverbund Rhein-Ruhr (VRR) veröffentlicht seit langem Soll-Fahrplandaten im DINO-Format. Eigentlich ist GTFS das gängige Format, für die Verarbeitung gibt es auch viele Tools, doch mit den DINO-Daten ist man doch eher “näher an der Quelle”. Die Daten enthalten viele zusätzliche Attribute. Doch auch Informationen, die in GTFS abgebildet werden können, können im offiziellen Export fehlen, während sie in den DINO-Daten bereitstehen. Das, was ich im folgenden beschreibe, müsste auch mit GTFS-Daten möglich sein. Ich nutze und stelle hier aber die Tools vor, die ich früher erstellt habe.

Kilometersumme

Aus meiner Zeit in der Kommunalpolitik weiß ich noch, dass die Summe an gefahrenen Kilometern im Jahr eine der gefragtesten Statistiken war. Da das Verkehrsunternehmen diese nicht immer bereitstellen wollte oder dies nur zu selten geschah, habe ich diese anhand der Soll-Fahrplandaten auch mal selber bestimmt.

Auf Ebene einzelner Linien ergab sich bei einem früheren Vergleich mit den offiziellen Kilometerangaben oft ein Verhältnis von fast 100 %, teilweise nur von rund 97 %1. Abweichungen ergeben sich möglicherweise aus der Verwendung eines Normjahrs2, während ich mit den Fahrplandaten direkt die tatsächlichen Kalendertage nutze3.

Ein Vorteil bei der Verwendung der DINO-Daten ist, dass es sich bei den Distanzen um die gleichen handeln könnte, wie sie auch beim Verkehrsunternehmen selber im Planungssystem vorliegen. Trotzdem (oder gerade deswegen?) kann es Datenfehler geben, beispielsweise wenn sich das zugrundeliegende OpenStreetMap-Straßennetzwerk ändert und das Verkehrsunternehmen seine Routing-Zwangspunkte nicht nachbessert. Dass in den Fahrplandaten abgebildete Baustellen mit Umleitungen auch berücksichtigt werden, kann nachvollziehbarerweise aber auch unerwünscht und ein Nachteil dieser Methode sein.

Ergänzung 28.04.2023: Wenn Baustellen vorbei sind, wird der Fahrplan offenbar manchmal in einem unsauberen Zustand hinterlassen, was ja für Auskünfte egal ist, weil die Probleme nur in der Vergangenheit auftauchen. Für Auswertungen wie diese wird das zum Problem. Daher empfehle ich, einen Stand der Fahrplandaten vom Anfang der Fahrplanperiode zu verwenden.

Die Berechnung erfolgt mit der Funktion line_stats des Moduls DINO2.tools.export.csv. Beispiel4:

from DINO2 import Database
from DINO2.tools.export.csv import line_stats

db = Database()
session = db.Session()

# 9, 10: IDs der Fahrpläne siehe Datei version.din
line_stats(session, "km_x22.csv", 9)
line_stats(session, "km_x23.csv", 10)

Schauen wir uns das mal im Vergleich des Sommerfahrplans 2022 und des ganz frisch veröffentlichten Sommerfahrplans 2023 der Hagener Straßenbahn AG an. Ich erzeuge je einen Export und kann dann die Daten auf der Linienebene vergleichen.

Fahrplan Zeitraum Summe
Sommerfahrplan 2022 (x22) 12.06.2022 – 08.01.20235 (211 Tage) 5.236.996,05 km6
Sommerfahrplan 2023 (x23) 11.06.2023 – 07.01.2024 (211 Tage) 5.255.777,2 km


Hier sind die angekündigten Neuzuschnitte der Linien zu erkennen. Eine alternative Idee wäre, alle Linien in beiden Fahrplänen in der Stadtmitte zu teilen und die Zahlen der äquivalenten Abschnitte gegenüberzustellen. Bei vielen Linien liegen die Zahlen sehr nah beieinander. Die kleinen Abweichungen werden über Wochen hinweg aber immer wieder auftreten, wahrscheinlich wegen kleinen Anpassungen im zugrundeliegenden Straßennetz. Ich werde die Auswertung bis zum Fahrplanwechsel sicher nochmal wiederholen und die Daten an dieser Stelle aktualisieren.

Abfahrten am Tag

Im Rahmen der großen Netzumstellung Ende 2019 in Hagen habe ich mir schon die Anzahl an Abfahrten angeschaut. Damals war erkennbar, wie sehr die Summe der Abfahrten im ganzen Netz gestiegen ist, aber auch, welche Gebiete eher zu den Verlierern gehörten. Bei dem anstehenden Fahrplanwechsel soll es sich nicht wieder um so eine riesige Erhöhung der gesamten Verkehrsleistung handeln, sondern mehr um eine Umverteilung. Laut Aussage der Hagener Straßenbahn AG können die Änderungen übrigens kostenneutral durchgeführt und ohne negative Auswirkungen für die Kunden herbeigeführt werden.

Mit der Funktion departure_stats des Moduls DINO2.tools.export.csv werden drei Arten von Zählungen durchgeführt: Von einer Haltestelle zu einer anderen Haltestelle (damit sind direkt aufeinander folgende Halte gemeint), zwischen zwei Haltestellen (also bidirektional), und “ab” einer Haltestelle. Das wende ich auch wieder auf die Fahrpläne 2022/2023 an. Hierfür suche ich mir jeweils Beispieltage aus7.

from datetime import date
from DINO2.model.schedule import Trip
from DINO2.tools.export.csv import departure_stats

trip_query_x22_mo = Trip.query_for_date(session, date(2022, 12, 12))
trip_query_x23_mo = Trip.query_for_date(session, date(2023, 12, 11))

departure_stats(trip_query_x22_mo, "dep_stats_x22_mo.csv")
departure_stats(trip_query_x23_mo, "dep_stats_x23_mo.csv")


Ich habe außerdem mal versucht, die “zwischen”-Zahlen des Beispielmontags zu verwenden, um die Änderungen auf einer Karte zu visualisieren. Hierzu habe ich die Datensätze beider Fahrpläne zusammengeführt und um Haltestellenkoordinaten ergänzt. In QGIS konnte ich die CSV zunächst als Punktelayer mit den Koordinaten der Haltestelle A eines jeden Paars laden und dann über einen “Geometry generator”-Symbollayer eine Linie zu den Koordinaten der Haltestelle B zeichnen. Für den ersten Entwurf habe ich erst einmal eine Einfärbung eingefügt, die die relative Veränderung der Abfahrten zwischen den Haltestellen von -60% bis +60% abbildet (es gibt auch Werte außerhalb dieses Bereichs, diese bekommen jeweils die maximale Farbe), mit einem transparenten Weiß bei keiner Änderung. Die größere der beiden Vorher/Nachher-Anzahlen an Abfahrten zwischen den Haltestellen wird verwendet, um die Abschnitte unterschiedlich breit zu zeichnen und eher selten befahrene Abschnitte auch unscheinbarer wirken zu lassen.

Eine Karte mit dunklem einfarbigen Hintergrund stellt geradlinige Verknüpfungen zwischen den Haltestellen im Netz der Hagener Straßenbahn AG dar. Die Verknüpfungen sind breiter, je mehr Abfahrten zwischen dem Haltestellenpaar stattfinden. Abhängig von der Veränderung zwischen zwei Fahrplanversionen werden von -60% (dunkles Pink) bis +60% (dunkles Grün) die Verknüpfungen eingefärbt. Bei keiner Änderung der Anzahl wird die Linie in einem transparenten Weiß eingefärbt. Auf den meisten Achsen fand keine Änderung statt. Auf einigen Achsen ist ein schwaches Pink zu erkennen, auf manchen ein dunkleres. Reduzierungen und Umverteilungen fallen beispielsweise am Remberg oder rund um Haspe auf. Die Folgen der neuen Linie in Oege sind erkennbar.

Ich belasse es an dieser Stelle bei diesem Entwurf als erste Idee. Fast hätte ich eine interaktive Karte gemacht, aber dafür müsste ich mir ernsthaftere Gedanken über die zu verwendenden Farben und Skalierungen machen. Außerdem gibt es diverse Schwierigkeiten, beispielsweise sind die sich überlappenden Verknüpfungen (z. B. bei übersprungenen Haltestellen) nicht so schön. Wenn man statt der Luftlinie das Straßennetz verwenden würde, hätte man wieder andere Überlappungen und müsste alles schlau zusammenrechnen.

Fahrten im Tagesverlauf

Spontan ist mir noch eingefallen, dass es interessant sein könnte, für jede Minute eines Tages die aktuelle Anzahl an Fahrten zu ermitteln. Das auch wieder im Vergleich verschiedener Fahrpläne und an verschiedenen Tagen. Beispielsweise werden gerne mal vormittags Takte ausgedünnt, um mehr Verteilungsmasse zu haben. Auf der Bonusseite gibt es diese Auswertung auch für die große Netzumstellung Ende 2019. Dort ist beispielsweise, was die Vormittagsausdünnung angeht, das Gegenteil zu erkennen.

Für die Auswertung verwende ich die gleichen Beispieltage wie zuvor. Da ich das Tool noch nicht als Funktion in der Bibliothek eingebaut habe, gibt es hier den ganzen komischen Code zu sehen..

dates = (date(2022, 12, 12), date(2023, 12, 11), date(2022, 12, 17), date(2023, 12, 16), date(2022, 12, 11), date(2023, 12, 10))
date_trip_queries = {d: Trip.query_for_date(session, d) for d in dates}
date_trips = {d: tq.all() for d, tq in date_trip_queries.items()}
trip_ranges = {d: tuple((int((secs := t.departure_time.total_seconds()) / 60), int((secs + t.duration.total_seconds()) / 60)) for t in trips) for d, trips in date_trips.items()}
date_minutes = {d.strftime("%Y-%m-%d"): tuple(sum(_start <= _min < _end for _start, _end in d_ranges) for _min in range(60 * 27)) for d, d_ranges in trip_ranges.items()}

>>> date_minutes
{datetime.date(2022, 12, 12): (0, 0, ..., 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, ..., 108, 111, 115, 117, 115, 117, 116, 115, 115, 113, 116, 117, 119, 115, 115, 113, ...), ...}
# Jeder Eintrag entspricht der Anzahl an Fahrten zu einer der 1620 Minuten von 0:00 bis 26:59 Uhr


Da sich die Rahmenbedingungen des Fahrplans kaum geändert haben, ist das Ergebnis für 2022/2023 nicht so interessant. Ich empfehle einen Blick auf den zuvor erwähnten Vergleich der Fahrpläne 2019/2020 auf der Bonusseite.

Wikipedia-Tabelle

Mit dem Modul DINO2.tools.export.wikitable lässt sich ein Text-Output zur Verwendung in MediaWiki generieren. Die Tabelle zum Liniennetz enthält zu jeder Linie jede Linienvariante mit der Angabe der Haltestellenanzahl und Distanz, jedem Fahrzeitenprofil und zu jeder zugeordneten Kalenderbeschränkung die dazugehörigen Wochentage und schließlich die Abfahrtszeiten, die von den zuvor genannten Eigenschaften umfasst sind. Es kann auch eine Tabelle mit den Haltestellen generiert werden. Sie enthält die Tarifwaben, Kürzel, Bereiche und Steige. Zu allem jeweils die IFOPT/DHID und Koordinaten. Über einen Link oben kann eine Karte mit allen Koordinaten des Abschnitts aufgerufen werden.

Beispiel für eine Tabelle der Linie 522 mit drei Linienvarianten zwischen Hohenlimburg Bahnhof und Hauptbahnhof. Alle Varianten haben 19 Haltestellen und sind 8,7 bis 9,4 km lang. Es gibt verschiedene Fahrzeitprofile von 24 bis 26 min. Je nach Wochentag gibt es einen 30- bis 60-Minuten-Takt, der unterschiedlich anläuft und endet.

Bei Aktualisierungen der Fahrplandaten kann die Versionsgeschichte verwendet werden, um zu versuchen, die Änderung nachzuvollziehen. Das ist nicht so schön, aber zumindest einfacher, als sich die vielen einzelnen Dateien/Tabellen des DINO-Formats einzeln vorzunehmen.

Die Tabelle kann wie im folgenden Beispiel erzeugt werden. Einige Inhalte sind hardgecodet, so dass bei Einsatz in anderen Orten ggf. nachgebessert werden muss.

from DINO2.tools.export.wikitable import wikitable
wikitable(session, "wikitable-x22.txt", version_id=9)
wikitable(session, "wikitable-x23.txt", version_id=10)

Ergebnis auf Wikipedia: x22, x23

Schlusswort

Der Blick auf die absolute oder relative Veränderung kann nicht ausreichen, um die Qualität der Veränderung des Angebots zu bewerten, aber es kann als erster Einstieg einen interessanten Einblick bieten. Denkbar wären weitere Analysen unter Berücksichtigung der geographischen Lage der Haltestellen und Daten zur Bevölkerung, oder auch unter Betrachtung der Fahrtverläufe der Abfahrten.

Ich hoffe, dass ich hiermit zu eigenen Experimentierereien und Analysen inspirieren konnte. Vielleicht wird so etwas in der Art ja auch wo anders mal in Beratungen von Kommunal­politiker*innen über den ÖPNV eingesetzt?

Verkehrsunternehmen verfolgen vielleicht am liebsten ihre eigenen Ideen und zeigen sich nicht immer kooperativ, selbst wenn sie als interne Betreiber eigentlich der öffentlichen Kontrolle unterliegen8. Eine mündige Kommunalpolitik, die auch selber in Aktion treten und nicht nur abnicken kann, könnte aus meiner Sicht einen positiven Beitrag zur Entwicklung des öffentlichen Personennahverkehrs leisten9.


Verwendete DINO-Daten: Stand 27.04.2023 (Lizenz: CC BY in aktuellster Fassung, Verkehrsverbund Rhein-Ruhr AöR10)

Python-Bibliothek DINO2: GitHub-Repo, Doku

  1. Die schlechteste Übereinstimmung war bei einer Linie vorzufinden, auf der es eine lang anhaltende Baustelle gab, die in den von mir verwendeten Daten berücksichtigt war. 

  2. Zum Beispiel 190 Werktage mit Schule, 61 ohne, 53 Samstage, 61 Sonn- und Feiertage. 

  3. Möglicherweise ergeben sich weitere Besonderheiten wie Freitage oder Vorfeiertage bei Nachtnetzen, die je nach Berücksichtigung zu Unterschieden führen können. 

  4. Das grundlegende Setup: Repo herunterladen, pipenv install ausführen, in ein Verzeichnis namens dino das DINO-Archiv entpacken.
    Import: pipenv run python -m DINO2.tools.imp "sqlite:///./DINO2.db" ./dino 9 (Angabe bspw. 9,10 auch möglich)
    Python mit pipenv run python ausführen. 

  5. Eigentlich ist der Sommerfahrplan 2022 bis zum 10.06.2023 gültig, zur Vergleichbarkeit habe ich ihn bei der Verarbeitung beschränkt. 

  6. In den aktuellsten Daten gibt es einen Fehler unter anderem bei Linien 521 und 542 im Oktober 2022. Daher habe ich bei dieser Auswertung rund um die Kilometer die Fahrplandaten mit Stand 13.06.2022 verwendet (für Einsatzwagen 05.09.2022 und für Nachtexpress den aktuellen Stand). So ganz passend kriege ich es nicht hin und gebe fürs erste auf. Der jetzige Stand ist schonmal deutlich besser als bei ausschließlicher Verwendung des aktuellen Fahrplanstands. 

  7. x22 Mo: 12.12.2022, Sa: 17.12.2022, So: 11.12.2022.
    x23 Mo: 11.12.2023, Sa: 16.12.2023, So: 10.12.2023.
    Dezember, weil da in beiden Fahrplänen die Baustelle in Wetter berücksichtigt ist. 

  8. Alles nur so mein Gefühl, wenn ich auf Hagen zurückblicke. 

  9. In diesem Zusammenhang blicke ich tatsächlich positiv auf Hagen zurück! 

  10. und – was sich niemand traut zu sagen? – basierend auf OpenStreetMap-Daten