remix
partridge
Python

A fast, forgiving GTFS reader built on pandas DataFrames

Last updated Jun 1, 2026
185
Stars
24
Forks
7
Issues
0
Stars/day
Attention Score
50
Language breakdown
Python 95.4%
Makefile 4.6%
Files click to expand
README

========= 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
As little as possible
  • 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
(TODO: document this)
  • Handle nested folders and bad data in zips
  • Predictable type conversions
Thank You

I hope you find this library useful. If you have suggestions for improving Partridge, please open an issue on GitHub `__.

🔗 More in this category

© 2026 GitRepoTrend · remix/partridge · Updated daily from GitHub