import%20marimo%0A%0A__generated_with%20%3D%20%220.23.13%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Demand-Weighted%20Betweenness%3A%20Origin-Destination%20Flows%0A%0A%20%20%20%20Standard%20betweenness%20treats%20every%20pair%20of%20network%20nodes%20as%20equally%20important%3A%20each%20pair%20contributes%20one%20unit%20of%20flow%20to%20the%20nodes%20on%20the%20shortest%20path%20between%20them.%20That%20is%20a%20reasonable%20neutral%20assumption%2C%20but%20real%20movement%20is%20not%20uniform.%20Trips%20start%20where%20people%20are%20and%20end%20where%20destinations%20are.%0A%0A%20%20%20%20%60cityseer%60%20provides%20two%20functions%20that%20route%20weighted%20flows%20instead%3A%0A%0A%20%20%20%20-%20%5B%60betweenness_od%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fmetrics%2Fnetworks%23betweenness_od)%20routes%20an%20**explicit**%20origin-destination%20matrix%2C%20built%20from%20observed%20flow%20data%20with%20%5B%60build_od_matrix%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fmetrics%2Fnetworks%23build_od_matrix).%20Use%20this%20when%20you%20have%20trip%20data%2C%20for%20example%20from%20a%20travel%20survey%20or%20mobile%20data.%0A%20%20%20%20-%20%5B%60betweenness_demand%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fmetrics%2Fnetworks%23betweenness_demand)%20**models**%20the%20matrix%20from%20weighted%20origins%20and%20destinations%20using%20a%20singly%20constrained%20spatial%20interaction%20model.%20Use%20this%20when%20you%20have%20population%20and%20attractor%20data%20but%20no%20observed%20trips.%20This%20notebook%20demonstrates%20this%20function.%0A%0A%20%20%20%20The%20higher-level%20%5B%60CityNetwork%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fguide%2Ffundamentals%23citynetwork-api)%20class%20wraps%20%60betweenness_od%60%20and%20%60build_od_matrix%60%3B%20%60betweenness_demand%60%20is%20part%20of%20the%20lower-level%20functional%20API%2C%20which%20this%20recipe%20uses%20throughout.%0A%0A%20%20%20%20%23%23%20The%20singly%20constrained%20model%0A%0A%20%20%20%20%22Singly%20constrained%22%20means%20the%20model%20is%20constrained%20at%20the%20origin%20end%20only%3A%20each%20origin%20distributes%20exactly%20its%20full%20weight%20%24W_o%24%20(no%20more%2C%20no%20less)%20across%20the%20destinations%20it%20can%20reach%20within%20the%20distance%20threshold.%20Destination%20%24d%24%20receives%20a%20share%20proportional%20to%20its%20attractiveness%20%24W_d%24%20discounted%20by%20the%20cost%20of%20getting%20there%3A%0A%0A%20%20%20%20%24%24T_%7Bod%7D%20%3D%20W_o%20%5Ccdot%20%5Cfrac%7BW_d%20%5C%2C%20f(c_%7Bod%7D)%7D%7B%5Csum_%7Bd'%7D%20W_%7Bd'%7D%20%5C%2C%20f(c_%7Bod'%7D)%7D%24%24%0A%0A%20%20%20%20where%20%24f%24%20is%20a%20distance-decay%20(deterrence)%20function%20supplied%20as%20a%20%60decay_fn%60%20expression.%20With%20an%20exponential%20decay%20this%20is%20the%20classic%20gravity%20model.%20Each%20allocated%20flow%20%24T_%7Bod%7D%24%20is%20then%20routed%20along%20the%20shortest%20network%20path%2C%20and%20every%20node%20traversed%20accumulates%20the%20flow%3A%20the%20result%2C%20written%20to%20%60cc_demand_%7Bdistance%7D%60%2C%20is%20the%20modelled%20through-movement%20at%20each%20location.%20The%20allocation%20and%20routing%20happen%20in%20a%20single%20traversal%2C%20so%20no%20explicit%20matrix%20is%20materialised.%0A%0A%20%20%20%20The%20bundled%20datasets%20and%20their%20source%20attributions%20are%20documented%20on%20the%20%5Bdatasets%20page%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fdatasets).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20geopandas%20as%20gpd%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20from%20cityseer.metrics%20import%20networks%0A%20%20%20%20from%20cityseer.tools%20import%20graphs%2C%20io%0A%0A%20%20%20%20return%20gpd%2C%20graphs%2C%20io%2C%20networks%2C%20plt%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Data%3A%20population%20origins%20and%20retail%20destinations%0A%0A%20%20%20%20We%20model%20home-to-amenity%20movement%20in%20the%20city%20centre%20using%20two%20bundled%20datasets%3A%0A%0A%20%20%20%20-%20**origins**%3A%20the%20Eurostat%20census%20grid%2C%20whose%20%60T%60%20column%20is%20total%20population%20per%20cell.%20Cells%20are%20polygons%3B%20we%20use%20their%20centroids%20as%20origin%20points.%0A%20%20%20%20-%20**destinations**%3A%20the%20bundled%20premises%20census%2C%20filtered%20to%20hospitality%20(%60section_id%20%3D%3D%20%22I%22%60)%2C%20with%20each%20premise%20weighted%20equally.%0A%0A%20%20%20%20Origins%20and%20destinations%20are%20snapped%20to%20the%20nearest%20network%20node%20(within%20%60max_snap_dist%60%2C%20default%20100m).%20Weights%20of%20points%20snapping%20to%20the%20same%20node%20are%20summed%2C%20so%20total%20demand%20is%20preserved%20at%20each%20junction.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gpd%2C%20graphs%2C%20io%2C%20mo)%3A%0A%20%20%20%20from%20shapely%20import%20geometry%0A%0A%20%20%20%20data_dir%20%3D%20(mo.notebook_dir()%20%2F%20%22..%22%20%2F%20%22..%22%20%2F%20%22data%22).resolve()%0A%20%20%20%20streets_gpd%20%3D%20gpd.read_file(data_dir%20%2F%20%22madrid_streets%22%20%2F%20%22street_network.gpkg%22)%0A%20%20%20%20streets_gpd%20%3D%20streets_gpd.explode(ignore_index%3DTrue)%0A%20%20%20%20%23%20the%20source%20data%20contains%20a%20small%20number%20of%20exact%20duplicate%20geometries%0A%20%20%20%20streets_gpd%20%3D%20streets_gpd.drop_duplicates(subset%3D%22geometry%22)%0A%20%20%20%20%23%203km%20study%20area%20around%20the%20centre%2C%20with%20the%20network%20buffered%20a%20further%20800m%0A%20%20%20%20%23%20(the%20maximum%20analysis%20distance)%20to%20prevent%20edge%20rolloff%0A%20%20%20%20centre%20%3D%20gpd.GeoSeries.from_xy(%5B440300%5D%2C%20%5B4474300%5D%2C%20crs%3Dstreets_gpd.crs)%0A%20%20%20%20study_poly%20%3D%20centre.buffer(3000).iloc%5B0%5D%0A%20%20%20%20buffered_poly%20%3D%20centre.buffer(3800).iloc%5B0%5D%0A%20%20%20%20streets_clip%20%3D%20streets_gpd%5Bstreets_gpd.intersects(buffered_poly)%5D%0A%20%20%20%20G%20%3D%20io.nx_from_generic_geopandas(streets_clip)%0A%20%20%20%20for%20_idx%2C%20_data%20in%20G.nodes(data%3DTrue)%3A%0A%20%20%20%20%20%20%20%20G.nodes%5B_idx%5D%5B%22live%22%5D%20%3D%20study_poly.contains(geometry.Point(_data%5B%22x%22%5D%2C%20_data%5B%22y%22%5D))%0A%20%20%20%20G_dual%20%3D%20graphs.nx_to_dual(G)%0A%20%20%20%20nodes_gdf%2C%20_edges_gdf%2C%20network_structure%20%3D%20io.network_structure_from_nx(G_dual)%0A%20%20%20%20print(f%22%7BG_dual.number_of_nodes()%7D%20dual%20nodes%2C%20%7Bint(nodes_gdf.live.sum())%7D%20live%22)%0A%20%20%20%20return%20buffered_poly%2C%20data_dir%2C%20network_structure%2C%20nodes_gdf%0A%0A%0A%40app.cell%0Adef%20_(buffered_poly%2C%20data_dir%2C%20gpd)%3A%0A%20%20%20%20%23%20origins%3A%20census%20grid%20centroids%20with%20total%20population%0A%20%20%20%20census_gpd%20%3D%20gpd.read_file(data_dir%20%2F%20%22madrid_census%22%20%2F%20%22eu_stat_clipped.gpkg%22)%0A%20%20%20%20census_gpd%20%3D%20census_gpd.to_crs(25830)%0A%20%20%20%20origins_gpd%20%3D%20census_gpd%5B%5B%22T%22%2C%20%22geometry%22%5D%5D.copy()%0A%20%20%20%20origins_gpd%5B%22geometry%22%5D%20%3D%20origins_gpd.geometry.centroid%0A%20%20%20%20origins_gpd%20%3D%20origins_gpd%5Borigins_gpd.within(buffered_poly)%5D%0A%0A%20%20%20%20%23%20destinations%3A%20hospitality%20premises%2C%20weighted%20equally%0A%20%20%20%20premises_gpd%20%3D%20gpd.read_file(data_dir%20%2F%20%22madrid_premises%22%20%2F%20%22madrid_premises.gpkg%22)%0A%20%20%20%20dests_gpd%20%3D%20premises_gpd%5Bpremises_gpd%5B%22section_id%22%5D%20%3D%3D%20%22I%22%5D.copy()%0A%20%20%20%20dests_gpd%20%3D%20dests_gpd%5Bdests_gpd.within(buffered_poly)%5D%0A%20%20%20%20dests_gpd%5B%22weight%22%5D%20%3D%201.0%0A%20%20%20%20print(f%22%7Blen(origins_gpd)%7D%20origin%20cells%2C%20%7Blen(dests_gpd)%7D%20destination%20premises%22)%0A%20%20%20%20return%20dests_gpd%2C%20origins_gpd%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Computing%20demand-weighted%20betweenness%0A%0A%20%20%20%20The%20%60decay_fn%60%20expression%20is%20the%20deterrence%20function%20%24f%24.%20It%20uses%20the%20same%20variables%20as%20other%20cityseer%20expressions%3A%20%60c%60%20(network%20metres)%20and%20%60p%60%20(progress%20towards%20the%20threshold).%20%60%22exp(-0.002%20*%20c)%22%60%20is%20a%20gravity-style%20exponential%20with%20a%20beta%20of%200.002%2C%20a%20gentle%20decay%20suited%20to%20an%20800m%20walking%20threshold%3B%20see%20the%20%5Bdecay%20documentation%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fguide%2Fland-use%23decay-functions)%20for%20alternatives.%0A%0A%20%20%20%20Setting%20%60closest_destination%3DTrue%60%20switches%20from%20proportional%20allocation%20to%20an%20all-or-nothing%20assignment%20where%20each%20origin%20sends%20its%20full%20weight%20to%20the%20single%20best%20destination%2C%20appropriate%20for%20services%20chosen%20by%20proximity%20alone%20(for%20example%2C%20postboxes).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dests_gpd%2C%20network_structure%2C%20networks%2C%20nodes_gdf%2C%20origins_gpd)%3A%0A%20%20%20%20nodes_demand%20%3D%20networks.betweenness_demand(%0A%20%20%20%20%20%20%20%20network_structure%2C%0A%20%20%20%20%20%20%20%20nodes_gdf.copy()%2C%0A%20%20%20%20%20%20%20%20origins_gdf%3Dorigins_gpd%2C%0A%20%20%20%20%20%20%20%20destinations_gdf%3Ddests_gpd%2C%0A%20%20%20%20%20%20%20%20origin_weight_col%3D%22T%22%2C%0A%20%20%20%20%20%20%20%20destination_weight_col%3D%22weight%22%2C%0A%20%20%20%20%20%20%20%20distances%3D%5B800%5D%2C%0A%20%20%20%20%20%20%20%20decay_fn%3D%22exp(-0.002%20*%20c)%22%2C%0A%20%20%20%20)%0A%20%20%20%20print(nodes_demand%5B%22cc_demand_800%22%5D.describe())%0A%20%20%20%20return%20(nodes_demand%2C)%0A%0A%0A%40app.cell%0Adef%20_(nodes_demand%2C%20plt)%3A%0A%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3D(8%2C%208)%2C%20dpi%3D150)%0A%20%20%20%20live_nodes%20%3D%20nodes_demand%5Bnodes_demand.live%5D%0A%20%20%20%20live_nodes.plot(%0A%20%20%20%20%20%20%20%20ax%3Dax%2C%0A%20%20%20%20%20%20%20%20column%3D%22cc_demand_800%22%2C%0A%20%20%20%20%20%20%20%20cmap%3D%22magma%22%2C%0A%20%20%20%20%20%20%20%20markersize%3D2%2C%0A%20%20%20%20%20%20%20%20legend%3DTrue%2C%0A%20%20%20%20%20%20%20%20legend_kwds%3D%7B%22label%22%3A%20%22Demand-weighted%20betweenness%2C%20800%20m%22%2C%20%22shrink%22%3A%200.6%7D%2C%0A%20%20%20%20)%0A%20%20%20%20ax.set_title(%22Demand-weighted%20betweenness%20(population%20to%20hospitality%2C%20800%20m)%22)%0A%20%20%20%20ax.set_axis_off()%0A%20%20%20%20fig.tight_layout()%0A%20%20%20%20fig%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Interpretation%20and%20variations%0A%0A%20%20%20%20The%20map%20shows%20modelled%20pedestrian%20through-movement%20between%20homes%20and%20hospitality%20venues%3A%20streets%20with%20high%20values%20carry%20many%20short%20home-to-amenity%20trips%2C%20weighted%20by%20how%20many%20people%20live%20nearby%20and%20how%20attractive%20the%20reachable%20destinations%20are.%20Compare%20this%20with%20unweighted%20betweenness%20from%20the%20%5Bmetric%20centrality%20recipe%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fcentrality%2Fgpd-metric-centrality)%3A%20the%20demand-weighted%20variant%20concentrates%20flow%20where%20population%20and%20destinations%20actually%20interact.%0A%0A%20%20%20%20Variations%20to%20explore%3A%0A%0A%20%20%20%20-%20swap%20the%20destination%20set%20(parks%2C%20schools%2C%20transit%20stops)%20or%20weight%20destinations%20by%20a%20capacity%20column%20such%20as%20floorspace%3B%0A%20%20%20%20-%20adjust%20the%20deterrence%20function%3A%20a%20steeper%20decay%20(%60%22exp(-0.008%20*%20c)%22%60)%20localises%20flows%2C%20a%20flat%20function%20(%60%221%22%60)%20removes%20distance%20deterrence%20entirely%3B%0A%20%20%20%20-%20use%20%60betweenness_od%60%20with%20an%20explicit%20matrix%20when%20observed%20flows%20are%20available%3B%0A%20%20%20%20-%20for%20one-way%20street%20networks%2C%20combine%20with%20directed%20mode%20(see%20the%20%5Bdirected%20networks%20recipe%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fnetworks%2Fdirected-networks)).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
f9429300077cc3282600f067032f4421