-
Notifications
You must be signed in to change notification settings - Fork 10
Usage: 5.1. Modifying Network: Graph
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
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'}
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
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
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()
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
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}
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
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