A fast, forgiving GTFS reader built on pandas DataFrames
========= Partridge =========
.. image:: https://img.shields.io/pypi/v/partridge.svg :target: https://pypi.python.org/pypi/partridge
.. image:: https://img.shields.io/travis/remix/partridge.svg :target: https://travis-ci.org/remix/partridge
Partridge is a Python 3.6+ library for working with GTFS <https://developers.google.com/transit/gtfs/> feeds using pandas <https://pandas.pydata.org/> DataFrames.
Partridge is heavily influenced by our experience at Remix <https://www.remix.com/>__ analyzing and debugging every GTFS feed we could find.
At the core of Partridge is a dependency graph rooted at `trips.txt. Disconnected data is pruned away according to this graph when reading the contents of a feed.
Feeds can also be filtered to create a view specific to your needs. It's most common to filter a feed down to specific dates (serviceid) or routes (routeid), but any field can be filtered.
.. figure:: dependency-graph.png :alt: dependency graph
Philosophy
The design of Partridge is guided by the following principles:
As much as possible
- Favor speed
- Allow for extension
- Succeed lazily on expensive paths
- Fail eagerly on inexpensive paths
- Do anything other than efficiently read GTFS files into DataFrames
- Take an opinion on the GTFS spec
Installation
.. code:: console
pip install partridge
GeoPandas support
.. code:: console
pip install partridge[full]
Usage
Setup
.. code:: python
import partridge as ptg
inpath = 'path/to/caltrain-2017-07-24/'
Examples
The following is a collection of gists containing Jupyter notebooks with transformations to GTFS feeds that may be useful for intake into software applications.
- Find the busiest week in a feed and reduce its file size
_ - Combine routes by routeshortname
_ - Merge GTFS with shapefile geometries
_ - Merge multiple agencies into one
_ - Rewrite a feed to clean up formatting issues
_ - If a feed has stopcode, replace the contents of stopid with the contents of stopcode
- Diff the number of service hours in two feeds
_ - Investigate the the distance in meters of each stop to the closest point on a shape
_ - Convert frequencies.txt to an equivalent trips.txt
_ - Calculate headway for a stop
_
Inspecting the calendar ~~~~~~~
The date with the most trips
.. code:: python
date, serviceids = ptg.readbusiest_date(inpath) # datetime.date(2017, 7, 17), frozenset({'CT-17JUL-Combo-Weekday-01'})
The week with the most trips
.. code:: python
serviceidsbydate = ptg.readbusiest_week(inpath) # {datetime.date(2017, 7, 17): frozenset({'CT-17JUL-Combo-Weekday-01'}), # datetime.date(2017, 7, 18): frozenset({'CT-17JUL-Combo-Weekday-01'}), # datetime.date(2017, 7, 19): frozenset({'CT-17JUL-Combo-Weekday-01'}), # datetime.date(2017, 7, 20): frozenset({'CT-17JUL-Combo-Weekday-01'}), # datetime.date(2017, 7, 21): frozenset({'CT-17JUL-Combo-Weekday-01'}), # datetime.date(2017, 7, 22): frozenset({'CT-17JUL-Caltrain-Saturday-03'}), # datetime.date(2017, 7, 23): frozenset({'CT-17JUL-Caltrain-Sunday-01'})}
Dates with active service
.. code:: python
serviceidsbydate = ptg.readserviceidsby_date(path)
date, serviceids = min(serviceidsbydate.items()) # datetime.date(2017, 7, 15), frozenset({'CT-17JUL-Caltrain-Saturday-03'})
date, serviceids = max(serviceidsbydate.items()) # datetime.date(2019, 7, 20), frozenset({'CT-17JUL-Caltrain-Saturday-03'})
Dates with identical service
.. code:: python
datesbyserviceids = ptg.readdatesbyservice_ids(inpath)
busiestdate, busiestservice = ptg.readbusiestdate(inpath) dates = datesbyserviceids[busiestservice]
min(dates), max(dates) # datetime.date(2017, 7, 17), datetime.date(2019, 7, 19)
Reading a feed ~~~~~~
.. code:: python
date, serviceids = ptg.readbusiestdate(inpath)
view = { 'trips.txt': {'serviceid': serviceids}, 'stops.txt': {'stop_name': 'Gilroy Caltrain'}, }
feed = ptg.load_feed(path, view)
Read shapes and stops as GeoDataFrames
.. code:: python
serviceids = ptg.readbusiest_date(inpath)[1] view = {'trips.txt': {'serviceid': serviceids}}
feed = ptg.loadgeofeed(path, view)
feed.shapes.head() # shape_id geometry # 0 calgilsf LINESTRING (-121.5661454200744 37.003512297983... # 1 calsfgil LINESTRING (-122.3944115638733 37.776439059278... # 2 calsfsj LINESTRING (-122.3944115638733 37.776439059278... # 3 calsftam LINESTRING (-122.3944115638733 37.776439059278... # 4 calsjsf LINESTRING (-121.9031703472137 37.330157067882...
minlon, minlat, maxlon, maxlat = feed.stops.total_bounds # -122.412076, 37.003485, -121.566088, 37.77639
Extracting a new feed ~~~~~
.. code:: python
outpath = 'gtfs-slim.zip'
serviceids = ptg.readbusiest_date(inpath)[1] view = {'trips.txt': {'serviceid': serviceids}}
ptg.extract_feed(inpath, outpath, view) feed = ptg.load_feed(outpath)
assert serviceids == set(feed.trips.serviceid)
Features
- Surprisingly fast :)
- Load only what you need into memory
- Built-in support for resolving service dates
- Easily extended to support fields and files outside the official spec
- Handle nested folders and bad data in zips
- Predictable type conversions
I hope you find this library useful. If you have suggestions for improving Partridge, please open an issue on GitHub