Skip to content

Usage: 5.1. Modifying Network: Graph

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

Modifying the Network object

This page goes through making basic changes to the Network. Available as a jupyter notebook or wiki page.

GeNet supports some simple modifications like adding, reindexing and removing nodes and links and some involved modifications like changing the data stored under nodes or links. All of these changes get recorded in n.change_log.

# read example network
from genet import read_matsim
import os

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 nodes/links

n.add_link(link_id='proposed_index', u='4356572310', v='5811263955')
2022-11-02 16:49:25,847 - Added Link with index proposed_index, from node:4356572310 to node:5811263955, under multi-index:1, and data={'from': '4356572310', 'to': '5811263955', 'id': 'proposed_index'}





'proposed_index'
n.add_node(node='proposed_index', attribs={'x': 1, 'y': 1, 'data':'some_data'})
2022-11-02 16:49:26,083 - Added 1 nodes





{}

The index passed is only a proposition. If a node or link under this link exists, a new, unique index will be generated.

actual_link_id_added = n.add_link(link_id='proposed_index', u='4356572310', v='5811263955')
2022-11-02 16:49:26,188 - Generated 1 link ids.
2022-11-02 16:49:26,190 - Generated link id 0.
2022-11-02 16:49:26,194 - `proposed_index` already exists. Generated a new unique_index: `0`
2022-11-02 16:49:26,207 - Added Link with index 0, from node:4356572310 to node:5811263955, under multi-index:2, and data={'from': '4356572310', 'to': '5811263955', 'id': '0'}

Reindexing

To reindex a node or link:

n.reindex_node('proposed_index', 'another_index')
2022-11-02 16:49:26,325 - Changed Link attributes for 0 links
2022-11-02 16:49:26,602 - Changed Link attributes for 0 links
2022-11-02 16:49:26,690 - Changed Node attributes under index: proposed_index
2022-11-02 16:49:26,841 - Changed Node index from proposed_index to another_index
n.reindex_link('proposed_index', 'another_index')
2022-11-02 16:49:27,065 - Changed Link attributes under index: proposed_index
2022-11-02 16:49:27,072 - Changed Link index from proposed_index to another_index

Removing nodes/links

To remove a link or node:

n.remove_links(['another_index', actual_link_id_added])
2022-11-02 16:49:27,100 - Removed 2 links
n.remove_node('another_index')
2022-11-02 16:49:27,149 - Removed Node under index: another_index

Modifying data stored on nodes or edges:

Let's say you have extracted genet.Network link ids of interest (See Section on Using Network - Accessing Data) and now you want to make changes to the network. Let's make changes to the nested OSM data stored on the links. We will replace the highway tags from 'primary' to 'SOMETHING'.

from genet import graph_operations

links = n.extract_links_on_edge_attributes(
    conditions= {'attributes': {'osm:way:highway': 'primary'}},
)

links[:5]
['1007', '1008', '1023', '1024', '103']
n.link(links[0])
{'id': '1007',
 'from': '4356572310',
 'to': '5811263955',
 'freespeed': 22.22222222222222,
 'capacity': 3000.0,
 'permlanes': 2.0,
 'oneway': '1',
 'modes': {'car'},
 's2_from': 5221390723045407809,
 's2_to': 5221390723040504387,
 'attributes': {'osm:way:highway': 'primary',
  'osm:way:id': 589660342.0,
  'osm:way:lanes': '2',
  'osm:way:name': 'Shaftesbury Avenue',
  'osm:way:oneway': 'yes'},
 'length': 13.941905154249884}

We create a dictionary which maps same changes to all links in the list using:

    {link_id: {'attributes': {'osm:way:highway': 'SOMETHING'}} for link_id in links}
n.apply_attributes_to_links({link_id: {'attributes': {'osm:way:highway': 'SOMETHING'}} for link_id in links})
2022-11-02 16:49:28,175 - Changed Link attributes for 619 links
n.link(links[0])
{'id': '1007',
 'from': '4356572310',
 'to': '5811263955',
 'freespeed': 22.22222222222222,
 'capacity': 3000.0,
 'permlanes': 2.0,
 'oneway': '1',
 'modes': {'car'},
 's2_from': 5221390723045407809,
 's2_to': 5221390723040504387,
 'attributes': {'osm:way:highway': 'SOMETHING',
  'osm:way:id': 589660342.0,
  'osm:way:lanes': '2',
  'osm:way:name': 'Shaftesbury Avenue',
  'osm:way:oneway': 'yes'},
 'length': 13.941905154249884}
n.change_log.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>
timestamp change_event object_type old_id new_id old_attributes new_attributes diff
0 2022-11-02 16:49:25 add link None proposed_index None {'from': '4356572310', 'to': '5811263955', 'id... [(add, , [('from', '4356572310'), ('to', '5811...
1 2022-11-02 16:49:26 add node None proposed_index None {'x': 1, 'y': 1, 'lon': -7.557147012910978, 'l... [(add, , [('x', 1), ('y', 1), ('lon', -7.55714...
2 2022-11-02 16:49:26 add link None 0 None {'from': '4356572310', 'to': '5811263955', 'id... [(add, , [('from', '4356572310'), ('to', '5811...
3 2022-11-02 16:49:26 modify node proposed_index another_index {'x': 1, 'y': 1, 'lon': -7.557147012910978, 'l... {'x': 1, 'y': 1, 'lon': -7.557147012910978, 'l... [(change, id, (proposed_index, another_index))...
4 2022-11-02 16:49:26 modify node proposed_index proposed_index {'x': 1, 'y': 1, 'lon': -7.557147012910978, 'l... {'x': 1, 'y': 1, 'lon': -7.557147012910978, 'l... [(change, id, (proposed_index, another_index))]
n.change_log.loc[618, :]['old_attributes']
"{'id': '959', 'from': '300501141', 'to': '294158420', 'freespeed': 22.22222222222222, 'capacity': 1500.0, 'permlanes': 1.0, 'oneway': '1', 'modes': {'car'}, 's2_from': 5221390337863745429, 's2_to': 5221390337351379383, 'attributes': {'osm:relation:route': 'bus', 'osm:way:highway': 'primary', 'osm:way:id': 26785612.0, 'osm:way:name': 'Albany Street'}, 'length': 90.65828423645875}"
n.change_log.loc[618, :]['new_attributes']
"{'id': '959', 'from': '300501141', 'to': '294158420', 'freespeed': 22.22222222222222, 'capacity': 1500.0, 'permlanes': 1.0, 'oneway': '1', 'modes': {'car'}, 's2_from': 5221390337863745429, 's2_to': 5221390337351379383, 'attributes': {'osm:relation:route': 'bus', 'osm:way:highway': 'SOMETHING', 'osm:way:id': 26785612.0, 'osm:way:name': 'Albany Street'}, 'length': 90.65828423645875}"
n.change_log.loc[618, :]['diff']
[('change', 'attributes.osm:way:highway', ('primary', 'SOMETHING'))]

Another useful method is the apply_function_to_links/nodes. This function takes a user-defined function with the variable that is the data dictionary stored on the links or nodes respectively. This function does not support conditions, but this is something that can be encoded in you function, for example:

def divide_capacity(link_attributes):
    if 'car' in link_attributes['modes']:
        return link_attributes['capacity']/link_attributes['permlanes']
    
n.apply_function_to_links(divide_capacity, 'base_capacity')
2022-11-02 16:49:29,593 - Changed Link attributes for 3166 links

This method will apply results of this function to links for which a value can be computed. For use here, that means that for any link which has a 'car' value in 'modes' a value of capacity divided by the number of lanes will be computed. We specify that the results will be stored under base_capacity in the links attribute dictionary.

n.link(links[0])
{'id': '1007',
 'from': '4356572310',
 'to': '5811263955',
 'freespeed': 22.22222222222222,
 'capacity': 3000.0,
 'permlanes': 2.0,
 'oneway': '1',
 'modes': {'car'},
 's2_from': 5221390723045407809,
 's2_to': 5221390723040504387,
 'attributes': {'osm:way:highway': 'SOMETHING',
  'osm:way:id': 589660342.0,
  'osm:way:lanes': '2',
  'osm:way:name': 'Shaftesbury Avenue',
  'osm:way:oneway': 'yes'},
 'length': 13.941905154249884,
 'base_capacity': 1500.0}

Subsetting

You can subset the Network object using link IDs.

n = read_matsim(path_to_network=network, epsg='epsg:27700', path_to_schedule=schedule, path_to_vehicles=vehicles)

links = n.extract_links_on_edge_attributes(
    conditions= {'attributes': {'osm:way:highway': ['primary', 'primary_link']}},
)
sub_n = n.subnetwork(links=links)
2022-11-02 16:49:40,100 - 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-11-02 16:49:40,596 - Param: strongly_connected_modes is defaulting to `{'car', 'walk', 'bike'}` You can change this behaviour by passing the parameter.
2022-11-02 16:49:40,656 - The graph for mode car is not strongly connected. The largest 1 connected components will be extracted.
2022-11-02 16:49:40,771 - Extracting largest connected components resulted in mode: car being deleted from 180 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-11-02 16:49:41,056 - Changed Link attributes for 180 links
2022-11-02 16:49:41,086 - Removed 152 links
2022-11-02 16:49:41,098 - 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-11-02 16:49:41,111 - 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-11-02 16:49:41,115 - Subsetted Network is ready - do not forget to validate and visualise your subset!
sub_n
<Network instance at 140197221048080: with 
graph: Name: 
Type: MultiDiGraph
Number of nodes: 426
Number of edges: 472
Average in degree:   1.1080
Average out degree:   1.1080 and 
schedule Schedule:
Number of services: 0
Number of routes: 0
Number of stops: 0
n
<Network instance at 140197233469968: with 
graph: Name: 
Type: MultiDiGraph
Number of nodes: 1662
Number of edges: 3166
Average in degree:   1.9049
Average out degree:   1.9049 and 
schedule Schedule:
Number of services: 9
Number of routes: 68
Number of stops: 118

Spatial Subsetting

There is a convenience method to extract a subset network using spatial conditions - refer to Using Network 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='within')
2022-11-02 16:49:46,551 - 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-11-02 16:49:46,732 - Param: strongly_connected_modes is defaulting to `{'car', 'walk', 'bike'}` You can change this behaviour by passing the parameter.
2022-11-02 16:49:46,743 - The graph for mode car is not strongly connected. The largest 1 connected components will be extracted.
2022-11-02 16:49:46,765 - Extracting largest connected components resulted in mode: car being deleted from 72 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-11-02 16:49:46,836 - Changed Link attributes for 72 links
2022-11-02 16:49:46,872 - Removed 61 links
2022-11-02 16:49:46,876 - 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-11-02 16:49:46,893 - 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-11-02 16:49:46,900 - Subsetted Network is ready - do not forget to validate and visualise your subset!





<Network instance at 140197221315792: with 
graph: Name: 
Type: MultiDiGraph
Number of nodes: 110
Number of edges: 134
Average in degree:   1.2182
Average out degree:   1.2182 and 
schedule Schedule:
Number of services: 0
Number of routes: 0
Number of stops: 0
n.subnetwork_on_spatial_condition(region, how='intersect')
2022-11-02 16:49:50,537 - 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-11-02 16:49:50,668 - Schedule will be subsetted using given services: ['12430']. Links pertaining to their network routes will also be retained.
2022-11-02 16:49:52,002 - Removed Services with IDs `20274`, and Routes: {'VJ375a660d47a2aa570aa20a8568012da8497ffecf', 'VJ6c64ab7b477e201cae950efde5bd0cb4e2e8888e', 'VJ812fad65e7fa418645b57b446f00cba573f2cdaf'}
2022-11-02 16:49:52,557 - Removed Services with IDs `17732`, and Routes: {'VJ85c23573d670bab5485618b0c5fddff3314efc89', 'VJ0cb60de3ed229c1413abac506e770b6ab8a7c49a'}
2022-11-02 16:49:53,077 - Removed Services with IDs `14134`, and Routes: {'VJ2aba67e3ed98f2ed5f5966c1ac394cbf6d1943d7', 'VJ4e2b897edf0e7b8a8e3b5516ab43ce56f72c5cff', 'VJ323d02e117552af1565f2ff1273a612655c829c4', 'VJ12ba6089dfb2733e29c415a1a0015fef30fd5305', 'VJd78967364a302cf232c5139d40622dcb6c238c9e', 'VJdb0c128567fcbcc063d554ae1c95851cee41b909', 'VJ2c87b2a59184888f3175b55bde7b02d024ea8607', 'VJf9a22035ae6f25bb420df833474943ad76065c89', 'VJ4e311a625836374adf4cfaa841224840dbeb7619', 'VJ4c6fa387b0d4be94a6c3679b94790b183e2558ca', 'VJb93a17a405fe502c5b3a2d6544105b0311da9fe2', 'VJdf3936da1a51eb33db594ef99738802c14b19995', 'VJ5909ba51575a9459eb0013fbd31c8205455ca2fd', 'VJa7f37392e276aeac26c7e73bbc05e6a71af38dba', 'VJ256e98df611ff48afe737ddc81cbcde82e4e81c8', 'VJ26095b8f9f9db92ca2e53d4c086a7dcd82a13be9'}
2022-11-02 16:49:53,518 - Removed Services with IDs `15234`, and Routes: {'VJ9b58a59e3d74941586a5bca7726a8aa624da67fc', 'VJ652c769bc42361cc0308dff59a1fdcf0949bdade', 'VJ5b511605b1e07428c2e0a7d676d301c6c40dcca6', 'VJ8ccf92aa0f351b2e31f1a078b968dff4c2505c02', 'VJ1a8cc306354fdc322d739ae644eb73444341d08d', 'VJ28a8a6a4ab02807a4fdfd199e5c2ca0622d34d0c', 'VJ3d50b96792ae8495dbe5a5e372849a60c48b2279', 'VJd4cbfb092a104ac6a3164a86e9765f68734fdfcf', 'VJbf9d4fdb976223e6a026c0c669ed290418abefee'}
2022-11-02 16:49:54,151 - Removed Services with IDs `15660`, and Routes: {'VJ1cf651142378958b52229bfe1fa552e49136e60e', 'VJf2e0de4f5dad68cb03064e6064e372dde52cc678', 'VJ3716910ec59c370d9f5c69137df7276b68cf0a08'}
2022-11-02 16:49:54,548 - Removed Services with IDs `18853`, and Routes: {'VJf3e316e5e605bb512147dee2a989be5a82ef1b5f', 'VJ8cacca9a6722c497c413005568182ecf4d50b160', 'VJfc4917783c2ca3227789fa7c532c9adf47702095'}
2022-11-02 16:49:54,870 - Removed Services with IDs `14073`, and Routes: {'VJe18efadf172576fea7989ec1f233f26854c0f66a', 'VJd132b905afc6c0e8e8a994142e301ca5c0f70e22', 'VJdbc280077e505b4f8d66586ca51751a125cb4ef0', 'VJd9dbeefeca6d74ef2594a17514ebc08ee2d503b2', 'VJ24fe211d801738b556a39f815256d7f6bc544ec5', 'VJfc35884fc4f11dc408a209c19f56f3b60f634daf', 'VJf6055fdf9ef0dd6d0500b6c11adcfdd4d10655dc', 'VJe6ba07ef9f19ae40517261ad626bf34dd656491a', 'VJc8cdbd902dadeebeeb4dbd7332b564ee2e4b00ce', 'VJe8cffad09738ff7b9698b333e3247918d5c45358', 'VJ6cf76a4c03cca468cb6954db7f7aad5ae189df13', 'VJaa5ee0daec7529d7668c81fe7fac0c4ff545daea', 'VJ93d8207ae8540b4ff59d47c9ee1ec5689084522d', 'VJb4309b7a9598539ab9942ea1bcadc60a91b978ba', 'VJea6046f64f85febf1854290fb8f76e921e3ac96b'}
2022-11-02 16:49:55,094 - Removed Services with IDs `18915`, and Routes: {'VJ8a4b1ca7dfd0a130abd1de9f55f3b756617dd4ca', 'VJb08f8a2de01a4ef99d3b7fefd9022117ac307531', 'VJ0d304b95d39f4bce48e6ff26ddd73a9c06f17f4f', 'VJ520ec0c0ca58a849349fa614b5cf9270ac5c93da', 'VJ887921c00645929c5402ac46592e57c368ea63a1'}
2022-11-02 16:49:55,103 - Removed Stops with indices `['490000356NE', '9400ZZLUOXC3', '9400ZZLURGP1', '9400ZZLUOXC2', '490000173RF', '9400ZZLUESQ1', '490000091F', '490000252R', '490000173JB.link:1663', '490011126K', '490013600C', '490019675D', '490000235W1', '490000091E', '490015196N', '9400ZZLUOXC6', '9400ZZLUTCR1', '9400ZZLUGPS2', '9400ZZLUTCR3', '490000173RD', '9400ZZLUWRR1', '9400ZZLUGDG1', '490000235N', '490015196R', '9400ZZLURGP2', '9400ZZLUOXC4', '9400ZZLUESQ2', '9400ZZLUTCR4', '490010198W', '9400ZZLUWRR2', '9400ZZLUWRR3', '9400ZZLUOXC1', '9400ZZLUOXC5', '9400ZZLUWRR4', '490000252S']`.Routes affected: set(). Services affected: set().
2022-11-02 16:49:55,221 - Param: strongly_connected_modes is defaulting to `{'car', 'walk', 'bike'}` You can change this behaviour by passing the parameter.
2022-11-02 16:49:55,241 - The graph for mode car is not strongly connected. The largest 1 connected components will be extracted.
2022-11-02 16:49:55,271 - 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-11-02 16:49:55,360 - Changed Link attributes for 82 links
2022-11-02 16:49:55,432 - Removed 56 links
2022-11-02 16:49:55,452 - 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-11-02 16:49:55,459 - 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-11-02 16:49:55,461 - Subsetted Network is ready - do not forget to validate and visualise your subset!





<Network instance at 140197111884688: 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