<jupyter_start><jupyter_text>Stop-to-stop Running TimesThis notebook uses the Partridge library to process two GTFS feeds. It computes the stop-to-stop running times for each feed, allowing the user to see differences between two transit schedules.Author: <NAME>, MIT Transit Lab. June 2021.<jupyter_code>import partridge as ptg
import pandas as pd
import datetime
from collections import deque
# User input: choose the appropriate feed filters
route_type = ['3']
route_desc = ['Key Bus', 'Commuter Bus', 'Local Bus']
route_id = ['1', '39', '57']

# User input: specify the GTFS feeds
gtfs_inpath_base = r'cta/CTA_GTFS_SPRING2020.zip' 
gtfs_inpath_comp = r'cta/CTA_GTFS_SPRING2021.zip'

# User input: specify non-holiday weekdays within the GTFS feed time ranges that will be used for service ids
sample_date_base = datetime.date(2020, 5, 1) 
sample_date_comp = datetime.date(2021, 5, 5)

# User input: specify output file path
runtime_outpath = 'cta_runtime_comparison.csv'
# Import GTFS feeds
def get_gtfs_feed(sample_date, gtfs_inpath, route_type, route_desc, route_id):
    service_id_list = ptg.read_service_ids_by_date(gtfs_inpath)[sample_date]
    view = {'routes.txt': {'route_type': route_type, 'route_desc': route_desc, 'route_id': route_id}, 'trips.txt': {'service_id': service_id_list}}
    return ptg.load_feed(gtfs_inpath, view)

feed_base = get_gtfs_feed(sample_date_base, gtfs_inpath_base, route_type, route_desc, route_id)
feed_comp = get_gtfs_feed(sample_date_comp, gtfs_inpath_comp, route_type, route_desc, route_id)
# Get scheduled segment running times
def get_segment_times(feed):
    st = feed.stop_times
    trips = feed.trips[['route_id', 'trip_id', 'direction_id']]
    stops = feed.stops[['stop_id', 'stop_name']]
    
    st = pd.merge(st, trips, on='trip_id')
    
    st = st[['route_id', 'trip_id', 'arrival_time', 'stop_id', 'stop_sequence', 'direction_id']]
    st = st.sort_values(by=['route_id', 'direction_id', 'trip_id', 'stop_sequence'])
    
    next_stop = deque(list(st.stop_id))
    next_stop.rotate(-1)
    next_stop = list(next_stop)
    st['next_stop'] = next_stop 
    
    next_time = deque(list(st.arrival_time))
    next_time.rotate(-1)
    next_time = list(next_time)
    st['next_time'] = next_time
    
    next_trip = deque(list(st.trip_id))
    next_trip.rotate(-1)
    next_trip = list(next_trip)
    st['next_trip'] = next_trip
    
    st = st[st['trip_id'] == st['next_trip']]
    st = st[['route_id', 'trip_id', 'arrival_time', 'next_time', 'stop_id', 'next_stop', 'direction_id', 'stop_sequence']]
    
    rt_list = []
    for segment in st.values.tolist():
        start = segment[2]
        end = segment[3]
        rt_list.append((end-start)/60)
    
    st['duration'] = rt_list
    st = pd.merge(st, stops, on='stop_id')
    st = pd.merge(st, stops, left_on='next_stop', right_on='stop_id')
    
    count = st.groupby(['route_id', 'stop_id_x', 'stop_name_x', 'stop_id_y', 'stop_name_y', 'direction_id', 'stop_sequence'], as_index=False)['duration'].count()
    is_max = count.groupby(['route_id', 'stop_id_x', 'stop_name_x', 'stop_id_y', 'stop_name_y'])['duration'].transform(max) == count['duration']
    is_common = count['duration'] > 10
    mean = st.groupby(['route_id', 'stop_id_x', 'stop_name_x', 'stop_id_y', 'stop_name_y', 'direction_id', 'stop_sequence'], as_index=False)['duration'].mean()
    df = mean[is_max & is_common]
    
    return df

seg_times_base = get_segment_times(feed_base)
seg_times_comp = get_segment_times(feed_comp)
# Compare the results
comp_df = pd.merge(seg_times_base, seg_times_comp, how='outer', on=['route_id', 'stop_id_x', 'stop_id_y', 'direction_id'])
comp_df.columns = ['route_id', '1st_stop_id', '1st_stop_name_base', '2nd_stop_id', '2nd_stop_name_base',
                   'direction_id', 'sequence_base', 'duration_base', '1st_stop_name_comp', '2nd_stop_name_comp',
                   'sequence_comp', 'duration_comp']

comp_df = comp_df[['route_id', 'direction_id', 'sequence_base', 'sequence_comp',
                  '1st_stop_id', '1st_stop_name_base', '1st_stop_name_comp',
                  '2nd_stop_id', '2nd_stop_name_base', '2nd_stop_name_comp',
                  'duration_base', 'duration_comp']]
comp_df.head()
# Export to CSV
comp_df.to_csv(runtime_outpath)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13917884481558804, 'fraction_numerical': 0.011830201809324982, 'mean_word_length': 4.850746268656716, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '623182', 'license': 'MIT', 'n_tokens_mistral': 1630, 'n_tokens_neox': 1573, 'n_words': 317, 'path': 'Segment Level.ipynb', 'repo_name': 'nick-caros/gtfs-running-times', 'size': '4315'}