Skip to content

Usage: 5.2. Modifying Network: PT Schedule

Kasia Kozlowska edited this page Nov 24, 2022 · 2 revisions

Modifying the Schedule object

GeNet has a number of methods to change the Schedule objects. Available as a jupyter notebook or wiki page. Make sure you validate the end result.

# read example network
import pandas as pd
from genet import Network, Stop, Route, Service, read_matsim
import os
from pprint import pprint

path_to_matsim_network = '../example_data/pt2matsim_network'

network = os.path.join(path_to_matsim_network, 'network.xml')
schedule = os.path.join(path_to_matsim_network, 'schedule.xml')
vehicles = os.path.join(path_to_matsim_network, 'vehicles.xml')
n = read_matsim(
    path_to_network=network, 
    epsg='epsg:27700', 
    path_to_schedule=schedule, 
    path_to_vehicles=vehicles
)
# you don't need to read the vehicles file, but doing so ensures all vehicles
# in the schedule are of the expected type and the definition of the vehicle
# is preserved
n.print()
Graph info: Name: 
Type: MultiDiGraph
Number of nodes: 1662
Number of edges: 3166
Average in degree:   1.9049
Average out degree:   1.9049 
Schedule info: Schedule:
Number of services: 9
Number of routes: 68
Number of stops: 118

Adding Routes, Services

You can add Routes and Services. To add a Route, you need to identify which existing Service it should come under.

You can make Routes by specifying a headway parameter, that will generate explicit trips for you:

route = Route(
    route_short_name='N55',
    mode='bus',
    headway_spec={('07:00:00','08:00:00'): 15},
    arrival_offsets=['00:00:00', '00:02:00', '00:04:00', '00:06:00'],
    departure_offsets=['00:00:00', '00:02:00', '00:04:00', '00:06:00'],
    id='new_route',
#    route= ['834', '1573', '3139', '3141', '574', '3154', '979', '980', '981'],
    await_departure= [True, True, True, True],
    stops=[n.schedule.stop('490000235X'),
          Stop(id='new_stop', x=529500, y=181300,
            name='New Stop', epsg='epsg:27700'),
          Stop(id='other_new_stop', x=529502, y=181302,
            name='Other New Stop', epsg='epsg:27700'),
          n.schedule.stop('490010689KB')]
)
route.trips
{'trip_id': ['new_route_07:00:00',
  'new_route_07:15:00',
  'new_route_07:30:00',
  'new_route_07:45:00',
  'new_route_08:00:00'],
 'trip_departure_time': ['07:00:00',
  '07:15:00',
  '07:30:00',
  '07:45:00',
  '08:00:00'],
 'vehicle_id': ['veh_bus_new_route_07:00:00',
  'veh_bus_new_route_07:15:00',
  'veh_bus_new_route_07:30:00',
  'veh_bus_new_route_07:45:00',
  'veh_bus_new_route_08:00:00']}

Or you can use provide the exact trips and vehicles on this route:

route = Route(
    route_short_name='N55',
    mode='bus',
    trips={'trip_id': ['fun_trip_1', 'fun_trip_2'], 
           'trip_departure_time': ['03:53:00', '16:23:00'], 
           'vehicle_id': ['fun_bus_1', 'fun_bus_2']},
    arrival_offsets=['00:00:00', '00:02:00', '00:04:00', '00:06:00'],
    departure_offsets=['00:00:00', '00:02:00', '00:04:00', '00:06:00'],
    id='new_route',
#    route= ['834', '1573', '3139', '3141', '574', '3154', '979', '980', '981'],
    await_departure= [True, True, True, True],
    stops=[n.schedule.stop('490000235X'),
          Stop(id='new_stop', x=529500, y=181300,
            name='New Stop', epsg='epsg:27700'),
          Stop(id='other_new_stop', x=529502, y=181302,
            name='Other New Stop', epsg='epsg:27700'),
          n.schedule.stop('490010689KB')]
)

You can create and add a new Service, or add the route to an existing Service

new_service = Service(id='new_service', routes=[route])
new_service.print()
Service ID: new_service
Name: N55
Number of routes: 1
Number of stops: 4
n.schedule.add_service(Service(id='new_service', routes=[route]))
2022-07-14 15:49:53,678 - Added Services with IDs `['new_service']` and Routes: [['new_route']]
n.schedule.add_route('20274', route)
2022-07-14 15:49:54,010 - Route with ID `new_route` for Service 20274 within already exists in the Schedule. This Route will be reindexed to `20274_4`
2022-07-14 15:49:54,016 - Reindexed Route from new_route to 20274_4
2022-07-14 15:49:54,018 - Added Routes with IDs ['20274_4'], to Services `['20274']` within the Schedule
/Users/kasia.kozlowska/PycharmProjects/ABM/genet/genet/schedule_elements.py:1384: UserWarning: DataFrame columns are not unique, some columns will be omitted.
  self.vehicles = {**df.T.to_dict(), **self.vehicles}
n.schedule['new_service'].print()
Service ID: new_service
Name: N55
Number of routes: 1
Number of stops: 4
n.schedule.route('20274_4').ordered_stops
['490000235X', 'new_stop', 'other_new_stop', '490010689KB']

You can also refer to existing stops in the Schedule when creating aRoute to be added. You can either just pass Stop IDs as strings or use a stop method on the schedule to take and use that stop object. Note that in the case of the former (passing ID strings), the route will not have the spatial information for those stops until it is added to the Schedule.

r = Route(
    route_short_name='N55',
    mode='bus',
    trips={'trip_id': ['some_trip_1'], 
           'trip_departure_time': ['16:23:00'], 
           'vehicle_id': ['some_bus_2']},
   arrival_offsets=['00:00:00', '00:06:00'],
   departure_offsets=['00:00:00', '00:06:00'],
   id='another_new_route',
#    route= ['834', '1573', '3139', '3141', '574', '3154', '979', '980', '981'],
   await_departure= [True, True],
   stops=['490000235X.link:834', 
          '490010689KB.link:981']
)
r.graph().nodes['490000235X.link:834']
{'routes': {'another_new_route'}}
n.schedule.add_route('20274', r)
2022-07-14 15:49:54,284 - Added Routes with IDs ['another_new_route'], to Services `['20274']` within the Schedule
r.graph().nodes['490000235X.link:834']
{'services': {'14134', '18853', '20274'},
 'routes': {'VJ12ba6089dfb2733e29c415a1a0015fef30fd5305',
  'VJ256e98df611ff48afe737ddc81cbcde82e4e81c8',
  'VJ2aba67e3ed98f2ed5f5966c1ac394cbf6d1943d7',
  'VJ375a660d47a2aa570aa20a8568012da8497ffecf',
  'VJ4e2b897edf0e7b8a8e3b5516ab43ce56f72c5cff',
  'VJa7f37392e276aeac26c7e73bbc05e6a71af38dba',
  'VJd78967364a302cf232c5139d40622dcb6c238c9e',
  'VJdf3936da1a51eb33db594ef99738802c14b19995',
  'VJf3e316e5e605bb512147dee2a989be5a82ef1b5f',
  'VJf9a22035ae6f25bb420df833474943ad76065c89',
  'another_new_route'},
 'id': '490000235X.link:834',
 'x': 529981.7958802709,
 'y': 181412.0975758662,
 'epsg': 'epsg:27700',
 'name': 'Tottenham Court Road Station (Stop X)',
 'lon': -0.12809598708996447,
 'lat': 51.51668503324075,
 's2_id': 5221390722025467597,
 'linkRefId': '834',
 'isBlocking': 'false'}
r = Route(
    route_short_name='N55',
    mode='bus',
    trips={'trip_id': ['some_trip_1'], 
           'trip_departure_time': ['16:23:00'], 
           'vehicle_id': ['some_bus_2']},
   arrival_offsets=['00:00:00', '00:06:00'],
   departure_offsets=['00:00:00', '00:06:00'],
   id='another_new_route_2',
#    route= ['834', '1573', '3139', '3141', '574', '3154', '979', '980', '981'],
   await_departure= [True, True],
   stops=[n.schedule.stop('490000235X.link:834'), 
          n.schedule.stop('490010689KB.link:981')]
)
n.schedule.add_route('20274', r)
2022-07-14 15:49:54,612 - Added Routes with IDs ['another_new_route_2'], to Services `['20274']` within the Schedule

Note that for a Schedule to be a valid MATSim network, each stop referred to by a route needs a linkRefId attribute which links the stop to the Network.

Trying to add Stops with IDs already in the Schedule will result in an error, unless the force=True is set. The added route/service will inherit the data stored under those stops. The idea is that you can either specify the Stops in the route/service to be added correctly, or if they are to be changed, you use a dedicated method - check section 'Modifying data' below.

route = Route(
    route_short_name='N55',
    mode='bus',
    trips={'trip_id': ['fun_trip_1', 'fun_trip_2'], 
           'trip_departure_time': ['03:53:00', '16:23:00'], 
           'vehicle_id': ['fun_bus_1', 'fun_bus_2']},
   arrival_offsets=['00:00:00', '00:02:00', '00:04:00', '00:06:00'],
   departure_offsets=['00:00:00', '00:02:00', '00:04:00', '00:06:00'],
   id='another_new_route_3',
#    route= ['834', '1573', '3139', '3141', '574', '3154', '979', '980', '981'],
   await_departure= [True, True, True, True],
   stops=[
       Stop(id='490000235X.link:834', x=529981, y=181412, epsg='epsg:27700'),
       Stop(id='new_stop', x=529500, y=181300, epsg='epsg:27700', name='New Stop'),
       Stop(id='other_new_stop', x=529502, y=181302, epsg='epsg:27700', name='Other New Stop'),
       Stop(id='490010689KB.link:981', x=529166, y=181256, epsg='epsg:27700')
   ]
)

n.schedule.add_route('20274', route, force=True)
2022-07-14 15:49:54,944 - The following stops will inherit the data currently stored under those Stop IDs in the Schedule: ['490000235X.link:834', '490010689KB.link:981'].
2022-07-14 15:49:54,946 - Added Routes with IDs ['another_new_route_3'], to Services `['20274']` within the Schedule

Note the message above: The following stops will inherit the data currently stored under those Stop IDs in the Schedule: ['490000235X.link:834', '490010689KB.link:981'].

NOTE: adding routes and services results in new vehicles (unless you reuse the ones already in the Schedule---beware that the same vehicle cannot service multiple trips at the same time, genet does not currently have checks for this, the user needs to be mindful of the physics of shared vehicles). New vehicles need definitions, you can add them yourself to schedule.vehicles['vehicle_id'] = {'type': 'bus'} ensuring this vehicle type is defined in schedule.vehicle_types['bus'], or you can use a genet method to generate those vehicles, the type will be derived from the mode of the route. Then you can check if all of the types that vehicles are referring to have definitions.

len(n.schedule.vehicles)
13293
n.schedule.generate_vehicles()
n.schedule.validate_vehicle_definitions()
2022-07-14 15:49:55,588 - The following vehicle types are missing from the `vehicle_types`  attribute: {'bus'}
2022-07-14 15:49:55,589 - Vehicles affected by missing vehicle types: {'fun_bus_1': {'type': 'bus'}, 'fun_bus_2': {'type': 'bus'}, 'some_bus_2': {'type': 'bus'}}





False
len(n.schedule.vehicles)
13293
n.schedule.change_log().tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
0 2022-07-14 15:49:53 add service None new_service None {'id': 'new_service', 'name': 'N55'} [(add, , [('id', 'new_service'), ('name', 'N55...
1 2022-07-14 15:49:54 add route None 20274_4 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, , [('route_short_name', 'N55'), ('mode'...
2 2022-07-14 15:49:54 add route None another_new_route None {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, , [('route_short_name', 'N55'), ('mode'...
3 2022-07-14 15:49:54 add route None another_new_route_2 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, , [('route_short_name', 'N55'), ('mode'...
4 2022-07-14 15:49:54 add route None another_new_route_3 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, , [('route_short_name', 'N55'), ('mode'...

There are no methods to add Stop objects on their own. They are added to the Schedule with Route and Service objects.

Routing added Services/Routes

You can use methods in GeNet to relate the Stops of PT services and find network routes between them. First you need to know the ID of the Service you want to snap (you can also snap the entire schedule, but depending on the size and complexity of your network and schedule it might take a long time). GeNet will then relate all stops in that service to a link in the Network graph and route all of the Routes of the Service object. It will do this for directed subsets (subgraphs) of the Service (e.g. if you have a Northbound and Southboud service, the stops may have to find different links in the graph)

There are a lot of different parameters you can add to this method, that give you a bit more control.

  • solver: You can specify different mathematical solvers. For example GLPK, an open source solver which can be found here: https://www.gnu.org/software/glpk/. Another good open source choice is CBC: https://projects.coin-or.org/Cbc. You specify it as a string e.g. 'glpk', 'cbc', 'gurobi'. The solver needs to support MILP - mixed integer linear programming
  • allow_partial: Sometimes there isn't a link available for snapping within threshold for all stops. If allowed (default) an artificial self-loop link will be created as well as any connecting links to that unsnapped stop, under modal conditions. If set to False and the problem is partial, it will raise PartialMaxStableSetProblem error instead.
  • distance_threshold, step_size: You can vary the threshold for snapping stops to links. There are two parameters, the overall threshold and a step size; when snapping, the search area for links increases in steps until some links are found, we don't use the threshold right away because we don't want to have too many choices for snapping (makes it a little less heavy computationally)).
  • additional_modes: You can specify additional modes (when snapping, genet will consider a modal subset of the network that matches the pt service, e.g. bus, but you might want to use links that allow cars too).
  • allow_directional_split: You can opt for splitting the problem of snapping by direction. GeNet will then solve a series of problems for subgraphs (disjoint w.r.t. edges) of the Service. This is useful for rail type services that might share the same stop regardless of direction (in comparison to buses which have distinct bus stops, depending on which way they're travelling).

Check the methods' doc strings for up to date details.

n.schedule['new_service'].route('new_route').route
[]
n.schedule['new_service'].route('new_route').ordered_stops
['490000235X', 'new_stop', 'other_new_stop', '490010689KB']
n.route_service('new_service')
2022-07-14 15:49:57,865 - Routing Service new_service with modes = {'bus'}
2022-07-14 15:49:57,899 - Building Maximum Stable Set for PT graph with 4 stops and 3 edges
2022-07-14 15:49:58,036 - This Maximum Stable Set Problem is partially viable.
2022-07-14 15:49:58,037 - Maximum Stable Set problem to snap the PT graph to the network is partially viable, meaning not all stops have found a link to snap to within the distance_threshold.Partial snapping is ON, this problem will proceed to the solver.
2022-07-14 15:49:58,038 - Passing problem to solver
2022-07-14 15:49:58,041 - Initializing ordered Set vertices with a fundamentally unordered data source (type: set).  This WILL potentially lead to nondeterministic behavior in Pyomo
2022-07-14 15:49:58,047 - Passing problem to solver
2022-07-14 15:49:58,374 - Successfully snapped 3 stops to network links.
2022-07-14 15:49:58,390 - Stop ID changes detected for Routes: {'new_route'}
2022-07-14 15:49:58,394 - Changed Route attributes for 1 routes
2022-07-14 15:49:58,399 - Added 1 nodes
2022-07-14 15:49:58,505 - Generated 0 link ids.
2022-07-14 15:49:58,510 - Added 2 links
2022-07-14 15:49:58,513 - Changed Link attributes for 4 links
n.schedule['new_service'].route('new_route').route
['artificial_link===from:490000235X===to:490000235X',
 'artificial_link===from:490000235X===to:9521035',
 '3154',
 '979',
 '980',
 '981']
n.schedule['new_service'].route('new_route').ordered_stops
['490000235X.link:artificial_link===from:490000235X===to:490000235X',
 'new_stop.link:3154',
 'other_new_stop.link:3154',
 '490010689KB.link:981']

Reindexing

n.schedule['new_service'].reindex(new_id='more_appropriate_id')
2022-07-14 15:49:58,565 - Reindexed Service from new_service to more_appropriate_id
n.schedule.route('new_route').reindex(new_id='more_appropriate_route_id')
2022-07-14 15:49:58,583 - Reindexed Route from new_route to more_appropriate_route_id
n.schedule.change_log().tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
3 2022-07-14 15:49:54 add route None another_new_route_2 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, , [('route_short_name', 'N55'), ('mode'...
4 2022-07-14 15:49:54 add route None another_new_route_3 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, , [('route_short_name', 'N55'), ('mode'...
5 2022-07-14 15:49:58 modify route new_route new_route {'route_short_name': 'N55', 'mode': 'bus', 'ar... {'route_short_name': 'N55', 'mode': 'bus', 'ar... [(add, route, [(0, 'artificial_link===from:490...
6 2022-07-14 15:49:58 modify service new_service more_appropriate_id {'id': 'new_service'} {'id': 'more_appropriate_id'} [(change, id, (new_service, more_appropriate_i...
7 2022-07-14 15:49:58 modify route new_route more_appropriate_route_id {'id': 'new_route'} {'id': 'more_appropriate_route_id'} [(change, id, (new_route, more_appropriate_rou...

Removing Stops, Routes, Services

n.schedule.remove_service('more_appropriate_id')
/Users/kasia.kozlowska/PycharmProjects/ABM/genet/genet/schedule_elements.py:1384: UserWarning: DataFrame columns are not unique, some columns will be omitted.
  self.vehicles = {**df.T.to_dict(), **self.vehicles}
2022-07-14 15:49:58,899 - Removed Services with IDs `more_appropriate_id`, and Routes: {'more_appropriate_route_id'}
for route_id in {'another_new_route', 'another_new_route_2', 'another_new_route_3'}:
    n.schedule.remove_route(route_id)
2022-07-14 15:49:59,144 - Removed Routes with IDs ['another_new_route_2'], to Services `20274`.
2022-07-14 15:49:59,467 - Removed Routes with IDs ['another_new_route'], to Services `20274`.
2022-07-14 15:49:59,892 - Removed Routes with IDs ['another_new_route_3'], to Services `20274`.
n.schedule.change_log().tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
7 2022-07-14 15:49:58 modify route new_route more_appropriate_route_id {'id': 'new_route'} {'id': 'more_appropriate_route_id'} [(change, id, (new_route, more_appropriate_rou...
8 2022-07-14 15:49:58 remove service more_appropriate_id None {'id': 'more_appropriate_id', 'name': 'N55'} None [(remove, , [('id', 'more_appropriate_id'), ('...
9 2022-07-14 15:49:58 remove route another_new_route_2 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... None [(remove, , [('route_short_name', 'N55'), ('mo...
10 2022-07-14 15:49:59 remove route another_new_route None {'route_short_name': 'N55', 'mode': 'bus', 'ar... None [(remove, , [('route_short_name', 'N55'), ('mo...
11 2022-07-14 15:49:59 remove route another_new_route_3 None {'route_short_name': 'N55', 'mode': 'bus', 'ar... None [(remove, , [('route_short_name', 'N55'), ('mo...

You can also remove Stops. This will disconnect Routes and Services using that Stops and likely render them invalid. The method will warn you which Routes and Servces are affected.

n.schedule.remove_stop('new_stop')
2022-07-14 15:49:59,946 - Removed Stops with indices `['new_stop']`.Routes affected: {'20274_4'}. Services affected: {'20274'}.

You can also remove Stops. This will disconnect Routes and Services using that Stops and likely render them invalid. The method will warn you which Routes and Servces are affected.

n.schedule.remove_unused_stops()
2022-07-14 15:49:59,978 - Removed Stops with indices `['9400ZZLUWRR2', 'new_stop.link:3154', '490000235W1', '9400ZZLUESQ1', '9400ZZLUESQ2', '9400ZZLUGDG1', '9400ZZLUTCR1', '490000235X.link:artificial_link===from:490000235X===to:490000235X', '490015196N', '9400ZZLUWRR1', '9400ZZLUWRR4', '9400ZZLUOXC1', '9400ZZLUGPS2', '9400ZZLUOXC3', '9400ZZLURGP1', '490000235N', '9400ZZLURGP2', '9400ZZLUOXC4', '490000091E', '490000173RD', '9400ZZLUWRR3', '9400ZZLUTCR4', '490000356NE', '490010198W', '490015196R', '490019675D', '490000252R', '490000252S', 'other_new_stop.link:3154', '9400ZZLUOXC2', '490000173RF', '9400ZZLUOXC5', '490000091F', '490011126K', '9400ZZLUOXC6', '9400ZZLUTCR3', '490013600C']`.Routes affected: set(). Services affected: set().

Modifying data stored for Stops, Routes, Services

Applying known or pre-computed changes

Applying changes or new attributes to Services, Routes and Stops can be done via Schedule level methods. They all work with a dictionary where the keys are the object IDs and the values are dictionaries holding attribute names and values. The method to extract a DataFrame on attributes comes in handy here. E.g.

df = n.schedule.service_attribute_data(keys='name')
df.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
name
14134 98
20274 N55
18853 N8
15234 134
17732 N20

DataFrames are easy to work with. Youcould for exmaple manipulate the names or use other data to change these. For demonstration here, let's just set the names to something easy.

df['name'] = df['name'].apply(lambda x: f'Service_{x}')
df.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
name
14134 Service_98
20274 Service_N55
18853 Service_N8
15234 Service_134
17732 Service_N20

You can then convert this to a dictionary and pass it to the apply_attributes_to_services method.

n.schedule.apply_attributes_to_services(df.T.to_dict())
2022-07-14 15:50:00,085 - Changed Service attributes for 9 services
n.schedule.change_log().tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
54 2022-07-14 15:50:00 modify service 17732 17732 {'id': '17732', 'name': 'N20'} {'id': '17732', 'name': 'Service_N20'} [(change, name, (N20, Service_N20))]
55 2022-07-14 15:50:00 modify service 12430 12430 {'id': '12430', 'name': '205'} {'id': '12430', 'name': 'Service_205'} [(change, name, (205, Service_205))]
56 2022-07-14 15:50:00 modify service 15660 15660 {'id': '15660', 'name': '113'} {'id': '15660', 'name': 'Service_113'} [(change, name, (113, Service_113))]
57 2022-07-14 15:50:00 modify service 18915 18915 {'id': '18915', 'name': 'N5'} {'id': '18915', 'name': 'Service_N5'} [(change, name, (N5, Service_N5))]
58 2022-07-14 15:50:00 modify service 14073 14073 {'id': '14073', 'name': '94'} {'id': '14073', 'name': 'Service_94'} [(change, name, (94, Service_94))]

You can do the same for Routes and Stops. Your dictionaries cannot however hold changes to indices. You will encounter an error and should use reindex methods for such operations.

n.schedule.apply_attributes_to_routes(
    {'VJ375a660d47a2aa570aa20a8568012da8497ffecf': {
        'name': 'my_favourite_route', 
        'mode': 'piggyback'
    }}
)
2022-07-14 15:50:00,132 - Changed Route attributes for 1 routes
n.schedule.apply_attributes_to_stops(
    {'490000235YB.link:574': {'new_attribute': 'hello!'}}
)
2022-07-14 15:50:00,148 - Changed Stop attributes for 1 stops
n.schedule.change_log().tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
56 2022-07-14 15:50:00 modify service 15660 15660 {'id': '15660', 'name': '113'} {'id': '15660', 'name': 'Service_113'} [(change, name, (113, Service_113))]
57 2022-07-14 15:50:00 modify service 18915 18915 {'id': '18915', 'name': 'N5'} {'id': '18915', 'name': 'Service_N5'} [(change, name, (N5, Service_N5))]
58 2022-07-14 15:50:00 modify service 14073 14073 {'id': '14073', 'name': '94'} {'id': '14073', 'name': 'Service_94'} [(change, name, (94, Service_94))]
59 2022-07-14 15:50:00 modify route VJ375a660d47a2aa570aa20a8568012da8497ffecf VJ375a660d47a2aa570aa20a8568012da8497ffecf {'route_short_name': 'N55', 'mode': 'bus', 'ar... {'route_short_name': 'N55', 'mode': 'piggyback... [(change, mode, (bus, piggyback)), (add, , [('...
60 2022-07-14 15:50:00 modify stop 490000235YB.link:574 490000235YB.link:574 {'services': {'20274', '18853', '14134'}, 'rou... {'services': {'20274', '18853', '14134'}, 'rou... [(add, , [('new_attribute', 'hello!')])]

Trip and vehicle changes

You can use trips_to_dataframe to extract all of the trips, their departures and vehicle IDs associated with the trips in the schedule. Trip ids need not be unique, route IDs provide a secondary index. Associated service IDs are also given for convenience.

trips = n.schedule.trips_to_dataframe(gtfs_day='20210101')
trips.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
mode service_id route_id trip_id trip_departure_time vehicle_id
0 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 VJ2e4c6d83aa4dfd4b71ae27a9568cd3a4d0598f6d_05:... 2021-01-01 05:25:00 veh_2205_bus
1 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 VJ4fef3d3f34855f68dab603441550f4e487b0b9b7_03:... 2021-01-01 03:45:00 veh_2206_bus
2 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 VJ8cacca9a6722c497c413005568182ecf4d50b160_05:... 2021-01-01 05:05:00 veh_2207_bus
3 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 VJ99c16d764175d7337e6a4eb4e96b9f86d229c48f_03:... 2021-01-01 03:25:00 veh_2208_bus
4 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 VJb539a6d218dff4d1beedc043251816c61261a18b_04:... 2021-01-01 04:05:00 veh_2209_bus

Let's change all of the trip ids to something shorter

trips['trip_id'] = 'trip_' + trips.index.to_series().astype(str)
trips.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
mode service_id route_id trip_id trip_departure_time vehicle_id
0 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 trip_0 2021-01-01 05:25:00 veh_2205_bus
1 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 trip_1 2021-01-01 03:45:00 veh_2206_bus
2 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 trip_2 2021-01-01 05:05:00 veh_2207_bus
3 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 trip_3 2021-01-01 03:25:00 veh_2208_bus
4 bus 18853 VJ8cacca9a6722c497c413005568182ecf4d50b160 trip_4 2021-01-01 04:05:00 veh_2209_bus

You can set_trips_dataframe which takes this dataframe and applies changes to all route trips based on the data in the dataframe. This means you can generate this DataFrame as shown below, manipulate trips (delete them, add new ones), change their departure times or change their vehicle ids to be shared for differnt trips, perhaps on some temporal logic and as long as the dataframe has the same schema, you can use it to set new trips in the schedule. This will appear in the changelog as a route level modify event.

Nb removing all trips of the same route from the dataframe will have no effect when being applied. If there is data in the dataframe for a route, all of its trips will be replaced by the data in the dataframe, and if there is no data for a route in the frame, no changes will be applied to that route (i.e. the trips attribute for routes missing from the dataframe will not be set as empty).

n.schedule.set_trips_dataframe(trips)
n.schedule.route_attribute_data(keys=[{'trips': 'trip_id'}]).head()
2022-07-14 15:50:00,520 - Changed Route attributes for 69 routes
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
trips::trip_id
VJ8cacca9a6722c497c413005568182ecf4d50b160 [trip_0, trip_1, trip_2, trip_3, trip_4, trip_...
VJ2aba67e3ed98f2ed5f5966c1ac394cbf6d1943d7 [trip_8, trip_9, trip_10, trip_11, trip_12, tr...
VJ1a8cc306354fdc322d739ae644eb73444341d08d [trip_78, trip_79, trip_80, trip_81, trip_82, ...
VJ5b511605b1e07428c2e0a7d676d301c6c40dcca6 [trip_94, trip_95, trip_96, trip_97, trip_98, ...
VJe18efadf172576fea7989ec1f233f26854c0f66a [trip_108, trip_109, trip_110, trip_111, trip_...

Generating new trips using headway information

You can replace old trips using headway information. This is useful when creating scenario networks. You can do it to a Route or Service (by specifying the route ID to be changed within) objects.

route_id = 'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13'

n.schedule.route(route_id).trips
{'trip_id': ['trip_1358', 'trip_1359'],
 'trip_departure_time': ['07:51:00', '22:50:00'],
 'vehicle_id': ['veh_887_bus', 'veh_888_bus']}
n.schedule.generate_trips_from_headway(
    route_id=route_id, headway_spec={('07:51:00', '22:50:00'): 120}) # headway in minutes
2022-07-14 15:50:00,583 - Changed Route attributes for 1 routes
n.schedule.route(route_id).trips
{'trip_id': ['VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_07:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_09:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_11:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_13:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_15:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_17:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_19:51:00',
  'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_21:51:00'],
 'trip_departure_time': ['07:51:00',
  '09:51:00',
  '11:51:00',
  '13:51:00',
  '15:51:00',
  '17:51:00',
  '19:51:00',
  '21:51:00'],
 'vehicle_id': ['veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_07:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_09:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_11:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_13:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_15:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_17:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_19:51:00',
  'veh_Bus_VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13_21:51:00']}

Applying changes using functions or dictionary mappings

If you have some logic that can be written into a function of object's attributes, you can pass this to apply_function_to_x methods. You need to select location, which refers to the name of the attribute the result should be stored under. It can already exist and be overwritten. The function passed, is not expected to work with all objects. It will fail silently, only evaluating and generating outputs where possible.

from shapely.geometry import Point

def add_shapely_geometry_points(stop_attribs):
    return Point(stop_attribs['x'], stop_attribs['y'])

n.schedule.apply_function_to_stops(add_shapely_geometry_points, location='geometry')
2022-07-14 15:50:00,627 - Changed Stop attributes for 85 stops
n.schedule.change_log().tail(2)
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
214 2022-07-14 15:50:00 modify stop 490002124ZZ 490002124ZZ {'id': '490002124ZZ', 'x': 529737.7933655808, ... {'id': '490002124ZZ', 'x': 529737.7933655808, ... [(add, , [('geometry', <shapely.geometry.point...
215 2022-07-14 15:50:00 modify stop other_new_stop other_new_stop {'services': {'20274'}, 'routes': {'20274_4'},... {'services': {'20274'}, 'routes': {'20274_4'},... [(add, , [('geometry', <shapely.geometry.point...
n.schedule.stop_attribute_data(keys=['name', 'x', 'y', 'geometry']).head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
name x y geometry
490000235X.link:834 Tottenham Court Road Station (Stop X) 529981.795880 181412.097576 POINT (529981.7958802709 181412.0975758662)
490000235YB.link:574 Oxford Street Soho Street (Stop YB) 529570.781323 181336.281593 POINT (529570.7813227688 181336.2815925331)
490014214HE.link:3154 Wardour Street (Stop OM) 529477.750156 181314.437043 POINT (529477.7501560802 181314.4370430721)
490010689KB.link:981 Great Titchfield Street Oxford Circus Station... 529166.734973 181256.336723 POINT (529166.7349732723 181256.3367228433)
490010689OJ.link:1787 Great Titchfield Street Oxford Circus Station... 529227.773057 181280.477507 POINT (529227.7730568129 181280.4775071898)
from geopandas import GeoDataFrame
GeoDataFrame(n.schedule.stop_attribute_data(keys='geometry')).plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7fadaf468c50>

png

n.schedule.stop('490000235YB.link:574').__dict__
{'id': '490000235YB.link:574',
 'x': 529570.7813227688,
 'y': 181336.2815925331,
 'epsg': 'epsg:27700',
 'name': 'Oxford Street  Soho Street (Stop YB)',
 'lat': 51.51609803324077,
 'lon': -0.13404398709291904,
 's2_id': 5221390696959560815,
 'linkRefId': '574',
 'isBlocking': 'false',
 'new_attribute': 'hello!',
 'geometry': <shapely.geometry.point.Point at 0x7fadaf3bec10>}

For this let's say we want to reduce the number of trips. For simplicity of demonstration we don't have about which trips we delete, but logic around timings of trips can be added in this function, as the trips are saved as one of routes attributes (check out the summary methods)

# before
len(n.schedule.route_trips_to_dataframe())
2022-07-14 15:50:00,895 - `route_trips_to_dataframe` method is deprecated and will be replaced by `trips_to_dataframe`in later versions.





1570
def reduce_trips(attribs):
    # just delete any other trip
    attribs['trips']['trip_id'] = attribs['trips']['trip_id'][::2]
    attribs['trips']['trip_departure_time'] = attribs['trips']['trip_departure_time'][::2]
    attribs['trips']['vehicle_id'] = attribs['trips']['vehicle_id'][::2]
    return attribs['trips']

n.schedule.apply_function_to_routes(reduce_trips, 'trips')
2022-07-14 15:50:00,959 - Changed Route attributes for 69 routes
# after
len(n.schedule.route_trips_to_dataframe())
2022-07-14 15:50:00,965 - `route_trips_to_dataframe` method is deprecated and will be replaced by `trips_to_dataframe`in later versions.





796

Note, this could also be done using the route_trips_to_dataframe and set_route_trips_dataframe mentioned above.

Let's give an example of using a mapping. We can re-use the service name DataFrame we generated above.

df['new_name'] = 'Brand_new_name' + df['name']
df.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
name new_name
14134 Service_98 Brand_new_nameService_98
20274 Service_N55 Brand_new_nameService_N55
18853 Service_N8 Brand_new_nameService_N8
15234 Service_134 Brand_new_nameService_134
17732 Service_N20 Brand_new_nameService_N20
name_map = dict(zip(df['name'], df['new_name']))
name_map
{'Service_98': 'Brand_new_nameService_98',
 'Service_N55': 'Brand_new_nameService_N55',
 'Service_N8': 'Brand_new_nameService_N8',
 'Service_134': 'Brand_new_nameService_134',
 'Service_N20': 'Brand_new_nameService_N20',
 'Service_205': 'Brand_new_nameService_205',
 'Service_113': 'Brand_new_nameService_113',
 'Service_N5': 'Brand_new_nameService_N5',
 'Service_94': 'Brand_new_nameService_94'}

In this case, location refers to the attribute to be mapped.

n.schedule.apply_function_to_services(name_map, location='name')
2022-07-14 15:50:01,017 - Changed Service attributes for 9 services
n.schedule.change_log().tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
289 2022-07-14 15:50:01 modify service 17732 17732 {'id': '17732', 'name': 'Service_N20'} {'id': '17732', 'name': 'Brand_new_nameService... [(change, name, (Service_N20, Brand_new_nameSe...
290 2022-07-14 15:50:01 modify service 12430 12430 {'id': '12430', 'name': 'Service_205'} {'id': '12430', 'name': 'Brand_new_nameService... [(change, name, (Service_205, Brand_new_nameSe...
291 2022-07-14 15:50:01 modify service 15660 15660 {'id': '15660', 'name': 'Service_113'} {'id': '15660', 'name': 'Brand_new_nameService... [(change, name, (Service_113, Brand_new_nameSe...
292 2022-07-14 15:50:01 modify service 18915 18915 {'id': '18915', 'name': 'Service_N5'} {'id': '18915', 'name': 'Brand_new_nameService... [(change, name, (Service_N5, Brand_new_nameSer...
293 2022-07-14 15:50:01 modify service 14073 14073 {'id': '14073', 'name': 'Service_94'} {'id': '14073', 'name': 'Brand_new_nameService... [(change, name, (Service_94, Brand_new_nameSer...
{s.name for s in n.schedule.services()}
{'Brand_new_nameService_113',
 'Brand_new_nameService_134',
 'Brand_new_nameService_205',
 'Brand_new_nameService_94',
 'Brand_new_nameService_98',
 'Brand_new_nameService_N20',
 'Brand_new_nameService_N5',
 'Brand_new_nameService_N55',
 'Brand_new_nameService_N8'}

Subsetting

You can subset the Schedule object using Service IDs.

services = n.schedule.extract_service_ids_on_attributes(
    {'name': ['Brand_new_nameService_134', 'Brand_new_nameService_98']})
len(services)
2
n.schedule.print()
Schedule:
Number of services: 9
Number of routes: 69
Number of stops: 85
sub_s = n.schedule.subschedule(service_ids=services)
2022-07-14 15:50:01,252 - The following vehicle types are missing from the `vehicle_types`  attribute: {'piggyback'}
2022-07-14 15:50:01,252 - Vehicles affected by missing vehicle types: {'veh_2331_bus': {'type': 'piggyback'}, 'veh_2333_bus': {'type': 'piggyback'}, 'veh_2335_bus': {'type': 'piggyback'}, 'veh_2337_bus': {'type': 'piggyback'}, 'veh_2339_bus': {'type': 'piggyback'}}
2022-07-14 15:50:01,387 - Removed Services with IDs `20274`, and Routes: {'VJ375a660d47a2aa570aa20a8568012da8497ffecf', '20274_4', 'VJ812fad65e7fa418645b57b446f00cba573f2cdaf', 'VJ6c64ab7b477e201cae950efde5bd0cb4e2e8888e'}
2022-07-14 15:50:01,594 - Removed Services with IDs `18853`, and Routes: {'VJfc4917783c2ca3227789fa7c532c9adf47702095', 'VJ8cacca9a6722c497c413005568182ecf4d50b160', 'VJf3e316e5e605bb512147dee2a989be5a82ef1b5f'}
2022-07-14 15:50:01,718 - Removed Services with IDs `17732`, and Routes: {'VJ85c23573d670bab5485618b0c5fddff3314efc89', 'VJ0cb60de3ed229c1413abac506e770b6ab8a7c49a'}
2022-07-14 15:50:01,824 - Removed Services with IDs `12430`, and Routes: {'VJef7f20c3a9bf1419f6401e1e9131fe2c634bcb9a', 'VJ0f3c08222de16c2e278be0a1bf0f9ea47370774e', 'VJ95b4c534d7c903d76ec0340025aa88b81dba3ce4', 'VJ06cd41dcd58d947097df4a8f33234ef423210154', 'VJ948e8caa0f08b9c6bf6330927893942c474b5100', 'VJ15419796737689e742962a625abcf3fd5b3d58b1', 'VJf8e38a73359b6cf743d8e35ee64ef1f7b7914daa', 'VJ8f9aea7491080b0137d3092706f53dc11f7dba45', 'VJ235c8fca539cf931b3c673f9b056606384aff950', 'VJ06420fdab0dfe5c8e7f2f9504df05cf6289cd7d3', 'VJeae6e634f8479e0b6712780d5728f0afca964e64', 'VJeb72539d69ddf8e29f1adf74d43953def196ae41'}
2022-07-14 15:50:01,917 - Removed Services with IDs `15660`, and Routes: {'VJ1cf651142378958b52229bfe1fa552e49136e60e', 'VJf2e0de4f5dad68cb03064e6064e372dde52cc678', 'VJ3716910ec59c370d9f5c69137df7276b68cf0a08'}
2022-07-14 15:50:02,003 - Removed Services with IDs `18915`, and Routes: {'VJ8a4b1ca7dfd0a130abd1de9f55f3b756617dd4ca', 'VJ887921c00645929c5402ac46592e57c368ea63a1', 'VJ0d304b95d39f4bce48e6ff26ddd73a9c06f17f4f', 'VJb08f8a2de01a4ef99d3b7fefd9022117ac307531', 'VJ520ec0c0ca58a849349fa614b5cf9270ac5c93da'}
2022-07-14 15:50:02,084 - Removed Services with IDs `14073`, and Routes: {'VJe8cffad09738ff7b9698b333e3247918d5c45358', 'VJea6046f64f85febf1854290fb8f76e921e3ac96b', 'VJd132b905afc6c0e8e8a994142e301ca5c0f70e22', 'VJe6ba07ef9f19ae40517261ad626bf34dd656491a', 'VJdbc280077e505b4f8d66586ca51751a125cb4ef0', 'VJaa5ee0daec7529d7668c81fe7fac0c4ff545daea', 'VJf6055fdf9ef0dd6d0500b6c11adcfdd4d10655dc', 'VJe18efadf172576fea7989ec1f233f26854c0f66a', 'VJfc35884fc4f11dc408a209c19f56f3b60f634daf', 'VJb4309b7a9598539ab9942ea1bcadc60a91b978ba', 'VJd9dbeefeca6d74ef2594a17514ebc08ee2d503b2', 'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13', 'VJ93d8207ae8540b4ff59d47c9ee1ec5689084522d', 'VJ24fe211d801738b556a39f815256d7f6bc544ec5', 'VJc8cdbd902dadeebeeb4dbd7332b564ee2e4b00ce'}
2022-07-14 15:50:02,087 - Removed Stops with indices `['other_new_stop', '490000173JB.link:1663']`.Routes affected: set(). Services affected: set().
sub_s.print()
Schedule:
Number of services: 2
Number of routes: 25
Number of stops: 83

Spatial Subsetting

There is a convenience method to extract a subset schedule using spatial conditions - refer to Using Schedule notebook or docs to learn more about what spatial inputs are supported.

# pop it in https://s2.sidewalklabs.com/ to see it
region = '48761ad0b14,48761ad0b3,48761ad0b5,48761ad0b7,48761ad0b84,48761ad0d,48761ad0e04,48761ad11f4,48761ad11fc,48761ad13,48761ad145fc,48761ad147,48761ad14c,48761ad153,48761ad41df,48761ad41e4,48761ad41fc,48761ad421,48761ad427,48761ad429,48761ad42b,48761ad5d5,48761ad5d7,48761ad5d9,48761ad5df,48761ad5e1,48761ad5e3,48761ad64,48761ad6c,48761ad71,48761ad73,48761ad744,48761ad74c,48761ad751,48761ad753,48761ad757554,48761ad75c,48761ad77,48761ad79,48761ad7b,48761ad7d,48761ad7e4,48761ad7ec,48761ad7f4,48761ad7f9,48761ad7fb,48761ad7fd,48761ad827,48761ad829,48761ad82b,48761ad9d5,48761ad9d7,48761ad9d84,48761b2802c,48761b2817,48761b281c,48761b283,48761b2847fc,48761b2849,48761b284b,48761b29b5,48761b29b7,48761b29b9,48761b29d,48761b29e4,48761b29e9,48761b29ea4,48761b29ef,48761b29fb4,48761b29fd'
n.subnetwork_on_spatial_condition(region, how='intersect')
2022-07-14 15:50:03,479 - Subsetting a Network will likely result in a disconnected network graph. A cleaner will be ran that will remove links to make the resulting Network strongly connected for modes: car, walk, bike.
2022-07-14 15:50:03,543 - Schedule will be subsetted using given services: ['12430']. Links pertaining to their network routes will also be retained.
2022-07-14 15:50:03,720 - The following vehicle types are missing from the `vehicle_types`  attribute: {'piggyback'}
2022-07-14 15:50:03,721 - Vehicles affected by missing vehicle types: {'veh_2331_bus': {'type': 'piggyback'}, 'veh_2333_bus': {'type': 'piggyback'}, 'veh_2335_bus': {'type': 'piggyback'}, 'veh_2337_bus': {'type': 'piggyback'}, 'veh_2339_bus': {'type': 'piggyback'}}
2022-07-14 15:50:03,834 - Removed Services with IDs `14134`, and Routes: {'VJb93a17a405fe502c5b3a2d6544105b0311da9fe2', 'VJ323d02e117552af1565f2ff1273a612655c829c4', 'VJ5909ba51575a9459eb0013fbd31c8205455ca2fd', 'VJ2aba67e3ed98f2ed5f5966c1ac394cbf6d1943d7', 'VJ26095b8f9f9db92ca2e53d4c086a7dcd82a13be9', 'VJ2c87b2a59184888f3175b55bde7b02d024ea8607', 'VJ4e2b897edf0e7b8a8e3b5516ab43ce56f72c5cff', 'VJf9a22035ae6f25bb420df833474943ad76065c89', 'VJd78967364a302cf232c5139d40622dcb6c238c9e', 'VJ12ba6089dfb2733e29c415a1a0015fef30fd5305', 'VJdb0c128567fcbcc063d554ae1c95851cee41b909', 'VJdf3936da1a51eb33db594ef99738802c14b19995', 'VJa7f37392e276aeac26c7e73bbc05e6a71af38dba', 'VJ4c6fa387b0d4be94a6c3679b94790b183e2558ca', 'VJ256e98df611ff48afe737ddc81cbcde82e4e81c8', 'VJ4e311a625836374adf4cfaa841224840dbeb7619'}
2022-07-14 15:50:03,941 - Removed Services with IDs `20274`, and Routes: {'VJ375a660d47a2aa570aa20a8568012da8497ffecf', '20274_4', 'VJ812fad65e7fa418645b57b446f00cba573f2cdaf', 'VJ6c64ab7b477e201cae950efde5bd0cb4e2e8888e'}
2022-07-14 15:50:04,136 - Removed Services with IDs `18853`, and Routes: {'VJfc4917783c2ca3227789fa7c532c9adf47702095', 'VJ8cacca9a6722c497c413005568182ecf4d50b160', 'VJf3e316e5e605bb512147dee2a989be5a82ef1b5f'}
2022-07-14 15:50:04,215 - Removed Services with IDs `15234`, and Routes: {'VJd4cbfb092a104ac6a3164a86e9765f68734fdfcf', 'VJ28a8a6a4ab02807a4fdfd199e5c2ca0622d34d0c', 'VJ8ccf92aa0f351b2e31f1a078b968dff4c2505c02', 'VJ1a8cc306354fdc322d739ae644eb73444341d08d', 'VJ5b511605b1e07428c2e0a7d676d301c6c40dcca6', 'VJ3d50b96792ae8495dbe5a5e372849a60c48b2279', 'VJ9b58a59e3d74941586a5bca7726a8aa624da67fc', 'VJbf9d4fdb976223e6a026c0c669ed290418abefee', 'VJ652c769bc42361cc0308dff59a1fdcf0949bdade'}
2022-07-14 15:50:04,303 - Removed Services with IDs `17732`, and Routes: {'VJ85c23573d670bab5485618b0c5fddff3314efc89', 'VJ0cb60de3ed229c1413abac506e770b6ab8a7c49a'}
2022-07-14 15:50:04,367 - Removed Services with IDs `15660`, and Routes: {'VJ1cf651142378958b52229bfe1fa552e49136e60e', 'VJf2e0de4f5dad68cb03064e6064e372dde52cc678', 'VJ3716910ec59c370d9f5c69137df7276b68cf0a08'}
2022-07-14 15:50:04,428 - Removed Services with IDs `18915`, and Routes: {'VJ8a4b1ca7dfd0a130abd1de9f55f3b756617dd4ca', 'VJ887921c00645929c5402ac46592e57c368ea63a1', 'VJ0d304b95d39f4bce48e6ff26ddd73a9c06f17f4f', 'VJb08f8a2de01a4ef99d3b7fefd9022117ac307531', 'VJ520ec0c0ca58a849349fa614b5cf9270ac5c93da'}
2022-07-14 15:50:04,460 - Removed Services with IDs `14073`, and Routes: {'VJe8cffad09738ff7b9698b333e3247918d5c45358', 'VJea6046f64f85febf1854290fb8f76e921e3ac96b', 'VJd132b905afc6c0e8e8a994142e301ca5c0f70e22', 'VJe6ba07ef9f19ae40517261ad626bf34dd656491a', 'VJdbc280077e505b4f8d66586ca51751a125cb4ef0', 'VJaa5ee0daec7529d7668c81fe7fac0c4ff545daea', 'VJf6055fdf9ef0dd6d0500b6c11adcfdd4d10655dc', 'VJe18efadf172576fea7989ec1f233f26854c0f66a', 'VJfc35884fc4f11dc408a209c19f56f3b60f634daf', 'VJb4309b7a9598539ab9942ea1bcadc60a91b978ba', 'VJd9dbeefeca6d74ef2594a17514ebc08ee2d503b2', 'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13', 'VJ93d8207ae8540b4ff59d47c9ee1ec5689084522d', 'VJ24fe211d801738b556a39f815256d7f6bc544ec5', 'VJc8cdbd902dadeebeeb4dbd7332b564ee2e4b00ce'}
2022-07-14 15:50:04,462 - Removed Stops with indices `['other_new_stop', '490000173JB.link:1663']`.Routes affected: set(). Services affected: set().
2022-07-14 15:50:04,486 - Param: strongly_connected_modes is defaulting to `{'car', 'walk', 'bike'}` You can change this behaviour by passing the parameter.
2022-07-14 15:50:04,495 - The graph for mode car is not strongly connected. The largest 1 connected components will be extracted.
2022-07-14 15:50:04,509 - Extracting largest connected components resulted in mode: car being deleted from 82 edges
/Users/kasia.kozlowska/PycharmProjects/ABM/genet/genet/core.py:595: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
  df = df.loc[links & set(df.index)][df['modes'].apply(lambda x: bool(mode & x))]
2022-07-14 15:50:04,531 - Changed Link attributes for 82 links
2022-07-14 15:50:04,537 - Removed 56 links
2022-07-14 15:50:04,540 - The graph for modes: walk does not have any connected components. This method returns True because if the graph is empty for this mode there is no reason to fail this check.
2022-07-14 15:50:04,544 - The graph for modes: bike does not have any connected components. This method returns True because if the graph is empty for this mode there is no reason to fail this check.
2022-07-14 15:50:04,548 - Subsetted Network is ready - do not forget to validate and visualise your subset!





<Network instance at 140383937227472: with 
graph: Name: 
Type: MultiDiGraph
Number of nodes: 156
Number of edges: 203
Average in degree:   1.3013
Average out degree:   1.3013 and 
schedule Schedule:
Number of services: 1
Number of routes: 12
Number of stops: 83
Clone this wiki locally