networks
Compute network centralities. If you are using cityseer for the first time, use the CityNetwork class instead of this module: it builds the network automatically (including cleaning and the dual graph) and exposes the same centrality methods. The functions here are the lower-level functional API, for direct control over the NetworkStructure and nodes GeoDataFrame.
Two centrality functions are available, using shortest-path (metric) or simplest-path (angular) heuristics:
node_centrality_shortest, node_centrality_simplest, and segment_centrality are deprecated. They are backwards-compatibility shims for pre-5.0 code and will be removed in a future major release; do not use them in new work.
Metrics are specified as {name: expression} dicts using variables c (cost) and p (normalised progress). For shortest paths, c is metric distance and p = c / threshold. For simplest paths, c is angular cost and p is normalised time progress.
Four categories of metrics are supported:
- closeness: per-reached-node accumulation (e.g.
{"harmonic": "1/c", "density": "1"}) - betweenness: target seed weight in Brandes backpropagation (e.g.
{"betweenness": "1"}) - cycles: circuit rank (boolean flag)
- postprocess: derived from computed columns in Python (e.g.
{"hillier": "density**2 / farness"})
Pass None for defaults or {} to skip a category.
Per-node weight values (default 1.0, set on the nodes GeoDataFrame or read from NetworkX node attributes) apply gravity-style weighting to centrality: closeness weights each reachable node by its destination weight (so density becomes sum_j w_j rather than a plain count), and betweenness weights each origin-destination pair by the product of its endpoint weights. The same weighting is applied identically whether or not sampling is used. Land-use, mixed-use, and statistical aggregations are intentionally not node-weighted.
When segment_weighted=True, node weights are temporarily set to the primal edge (street segment) lengths so that centrality measures reflect total reachable street length rather than node counts (closeness by destination length, betweenness by the product of endpoint lengths). This is a convenience preset over the per-node weight mechanism and requires a dual graph representation.
When sample=True, only a subset of nodes are used as sources for centrality computation, with results corrected to approximate the full computation.
Cautions that apply when computing centralities with these lower-level functions:
- Columns prefixed
cc_are managed by cityseer: recomputing a metric for the same distance overwrites the matchingcc_columns in place (intended for re-runs). Don’t store your own data under this prefix. - Centralities can be distorted by messy graph topologies such as unnecessary intermediate points along streets
(used to describe road curvature) or overly complex representations of street intersections. Clean the network
first using the
graphmodule (see the automatic graph cleaning for examples). harmoniccloseness sums inverse distances (1/c), so a pair of nodes separated by only a few metres contributes a very large value, and a pair below 1 m can inflate a node’s score severely.CityNetworkconstruction removes near-duplicate edges and short self-loops automatically; when building the network manually, consolidate nearby nodes (seenx_consolidate_nodes) before computing harmonic closeness.- Simplest (angular) measures require a dual graph representation.
CityNetworkbuilds the dual automatically; this step only applies to the manual method, where primal graphs must be converted withgraphs.nx_to_dualbefore ingestion. - Metrics should only be compared across networks that use the same graph representation (both primal or both dual), because the differing number of nodes and edges between representations affects the metric values. For example, a four-way intersection consisting of one node with four edges on a primal graph translates to four nodes and six edges on the dual. This effect is amplified for denser regions of the network.
- Standard closeness and normalised closeness do not work well with distance-bounded analysis. Use harmonic closeness or Hillier normalisation instead.
centrality_shortest
Compute centrality using shortest paths with a single Dijkstra per source. Metrics are specified as {name: expression} dicts. Expressions use two variables:
c: the raw cost (metric distance in metres for shortest-path analysis)p: normalised progress from 0 at the source to 1 at the distance threshold (p = c / threshold)
Pass None for defaults or {} to skip a category.
Tip: compute only what you need — a smaller closeness / betweenness dict, {} to skip a whole category, or cycles=False — evaluates fewer expressions and emits fewer columns.
Parameters
A GeoDataFrame representing nodes. The outputs of calculations will be written to this GeoDataFrame.
Distance thresholds in metres at which to compute centrality measures.
Walking times in minutes; converted to distance thresholds using speed_m_s.
Closeness metric expressions. Each entry is {name: expr(c, p)}, accumulated per reached node. None uses defaults: density, farness, harmonic, decay.
Betweenness metric expressions. Each entry is {name: expr(c, p)}, used as the weight assigned to each destination when accumulating betweenness contributions along shortest paths. None uses defaults: betweenness, betweenness_decay.
If True, compute circuit rank (cycle count) for each node. Default True.
Derived metrics computed in Python from the closeness/betweenness results. None uses default: {"hillier": "density**2 / farness"}.
Speed in metres per second for converting minutes to distance thresholds.
Relative tolerance for betweenness path equality, as a percentage (e.g. 1.0 = 1%).
If True, weight by primal edge (street segment) lengths. Requires a dual graph.
Optional seed for reproducible sampling.
If True, enables adaptive sampling at longer distance thresholds.
Error tolerance for sampling. Defaults to sampling.HOEFFDING_EPSILON (0.05).
Returns
The input nodes_gdf parameter is returned with additional centrality columns.
Notes
from cityseer.tools import mock, graphs, io
from cityseer.metrics import networks
G = mock.mock_graph()
G = graphs.nx_simple_geoms(G)
nodes_gdf, edges_gdf, network_structure = io.network_structure_from_nx(G)
nodes_gdf = networks.centrality_shortest(
network_structure,
nodes_gdf,
distances=[400, 800],
)
print(nodes_gdf[["cc_harmonic_400", "cc_betweenness_800"]])
build_od_matrix
Build an OdMatrix from OD flow data and zone boundaries. Computes zone centroids, snaps them to the nearest network nodes, and constructs a sparse OD weight matrix for use with betweenness_od.
Parameters
Origin-destination flow data with columns for origin zone, destination zone, and weight.
Zone boundaries (polygons) or centroids (points). Must be in a projected CRS matching the network, or in EPSG:4326 (will be auto-reprojected).
The network to snap zone centroids to.
Column in od_df containing origin zone identifiers.
Column in od_df containing destination zone identifiers.
Column in od_df containing trip weights (e.g., number of bicycle commuters).
Column in zones_gdf containing zone identifiers matching origin_col/destination_col. If None, uses the GeoDataFrame index.
Maximum distance (in CRS units, typically metres) for snapping a centroid to a network node. Centroids beyond this distance are excluded with a warning.
Returns
Sparse OD matrix ready for use with betweenness_od.
betweenness_od
Compute OD-weighted betweenness centrality using the shortest path heuristic.
Parameters
A GeoDataFrame representing nodes. The outputs of calculations will be written to this GeoDataFrame.
An OdMatrix mapping (origin, destination) node pairs to trip weights. Build with build_od_matrix.
Distance thresholds in metres at which to compute betweenness.
Walking times in minutes; converted to distance thresholds using speed_m_s.
Betweenness metric expressions. None uses defaults: betweenness, betweenness_decay.
Speed in metres per second for converting minutes to distance thresholds.
Relative tolerance for path equality, as a percentage.
Returns
The input nodes_gdf parameter is returned with additional betweenness columns.
betweenness_demand
Compute demand-weighted (flow) betweenness from a spatial interaction model. Trips are allocated between weighted origins (e.g. population) and weighted destinations (e.g. attractors) using a singly (origin-)constrained spatial interaction model, then routed along shortest network paths so that intermediate nodes accumulate the flow that passes through them. For each origin and reachable destination the allocated flow is
where is decay_fn, is the network distance, and is a stay-home alternative in the destination choice set, derived from the participation share. At full participation (, the default) each origin’s full weight is conserved and distributed across reachable destinations (destination totals are not constrained — that would require a doubly-constrained / Furness model), and the gravity model is the classic instance of this form, recovered with an exponential decay_fn. Below full participation each origin participates at rate , where is its accessibility , so trip generation falls where accessibility is low.
This is the modelled-matrix counterpart to betweenness_od: rather than supplying an explicit OD matrix, the per-pair weights are derived from the network distances revealed during routing, computed in a single traversal per origin.
Parameters
A nodes GeoDataFrame; flow betweenness columns are written to it and it is returned.
A GeoDataFrame of demand origins (points or centroids).
A GeoDataFrame of demand destinations / attractors (points or centroids).
Column in origins_gdf giving each origin’s weight (e.g. population).
Column in destinations_gdf giving each destination’s attractiveness weight.
Distance thresholds in metres at which to compute flow betweenness.
Walking times in minutes; converted to distance thresholds using speed_m_s.
Distance-decay expression for the allocation, using c (metric cost) and p (normalised progress = c / threshold). Defaults to "exp(-4 * p)" (scale-free, re-normalised per threshold). For a classic gravity model on absolute distance use e.g. "exp(-0.002 * c)". Because the allocation is normalised per origin, this expression only shapes destination choice; it cannot scale an origin’s total outflow. Use betweenness expressions for that.
If True, each origin routes its participating weight to its single nearest reachable destination instead of allocating across all of them.
The share of people at a typical location who make a trip, in . The default 1.0 is full participation: every origin’s full weight travels (the classic conserved model, at no extra cost). Below 1.0, a stay-home option enters the destination choice set — think of staying home as one phantom destination competing with everything an origin can reach: participation=0.2 means “at a location of median accessibility, one in five people travels”, and locations with better or worse access participate proportionately more or less, so trip generation becomes accessibility-elastic. The underlying stay-home weight is derived internally per distance threshold from the run’s own median origin accessibility (, logged per run), so the setting transfers across datasets and thresholds. For pedestrian flows, walking mode shares suggest starting around 0.2 (European cities range roughly 0.15 to 0.3); use a local travel survey’s share when available. Results are not knife-edge in this setting. Costs one extra traversal sweep when below 1.0, and note that output flows are then participating weights rather than total weights.
Name used for the output column (cc_{metric_name}_{distance}). Defaults to "demand".
Maximum assignment distance for origin/destination points. Points are assigned to the network with the same workflow as the data layers (build_data_map: representation-aware nearest-street assignment, with assignment offsets included in all routed distances — allocation and radius cutoffs alike); points with no valid assignment within this distance are dropped.
Optional barriers to respect during assignment, as in the data layers.
The number of nearest candidate edges to consider when assigning points to the network, as in the data layers.
Speed in metres per second for converting minutes to distance thresholds.
Relative tolerance for shortest-path equality, as a percentage. Paths within this margin of the shortest are treated as ties and flow splits across them, so this is the multipath control — the counterpart of a detour ratio in other tools (a 5% tolerance corresponds to a 1.05 detour ratio). Small tolerances can improve conserved-flow fits by spreading flow off knife-edge shortest paths; large ones blur the routing.
Returns
The input nodes_gdf with a flow-betweenness column added per distance threshold.
centrality_simplest
Compute centrality using simplest (angular) paths with a single Dijkstra per source. Expressions use c (angular cost) and p (normalised time progress).
Tip: compute only what you need — pass a smaller closeness / betweenness dict, or {} to skip a whole category — to evaluate fewer expressions and emit fewer columns.
Parameters
A GeoDataFrame representing nodes. The outputs of calculations will be written to this GeoDataFrame.
Distance thresholds in metres at which to compute centrality measures.
Walking times in minutes; converted to distance thresholds using speed_m_s.
Closeness metric expressions. None uses defaults: density, farness, harmonic.
Betweenness metric expressions. None uses defaults: betweenness.
Derived metrics. None uses default: {"hillier": "density**2 / farness"}.
Speed in metres per second for converting minutes to distance thresholds.
Relative tolerance for angular betweenness path equality, as a percentage.
If True, weight by primal edge (street segment) lengths. Requires a dual graph.
Optional seed for reproducible sampling.
If True, enables adaptive sampling at longer distance thresholds.
Error tolerance for sampling. Defaults to sampling.HOEFFDING_EPSILON (0.05).
Returns
The input nodes_gdf parameter is returned with additional centrality columns.
Notes
from cityseer.tools import mock, graphs, io
from cityseer.metrics import networks
G = mock.mock_graph()
G = graphs.nx_simple_geoms(G)
G_dual = graphs.nx_to_dual(G)
nodes_gdf, edges_gdf, network_structure = io.network_structure_from_nx(G_dual)
nodes_gdf = networks.centrality_simplest(
network_structure,
nodes_gdf,
distances=[400, 800],
)
print(nodes_gdf[["cc_harmonic_400_ang", "cc_betweenness_800_ang"]])
closeness_shortest
Compute closeness centrality using shortest paths. Wraps centrality_shortest with betweenness disabled.
closeness_simplest
Compute closeness centrality using simplest (angular) paths. Wraps centrality_simplest with betweenness disabled.
betweenness_shortest
Compute betweenness centrality using shortest paths. Wraps centrality_shortest with closeness disabled.
betweenness_simplest
Compute betweenness centrality using simplest (angular) paths. Wraps centrality_simplest with closeness disabled.
node_centrality_shortest
Deprecated 4.24 alias for centrality_shortest.
Deprecated
Since version 5.0. Use centrality_shortest with closeness / betweenness expression dicts. This shim preserves
the 4.24 output (columns cc_density, cc_farness, cc_harmonic, cc_beta, cc_cycles,
cc_hillier, cc_betweenness, cc_betweenness_beta) and will be removed in a future major release.
See COMPATIBILITY.md.
node_centrality_simplest
Deprecated 4.24 alias for centrality_simplest.
Deprecated
Since version 5.0. Use centrality_simplest with closeness / betweenness expression dicts. This shim preserves the
4.24 output (angular columns cc_density_ang, cc_farness_ang, cc_harmonic_ang, cc_hillier_ang,
cc_betweenness_ang) and will be removed in a future major release. See COMPATIBILITY.md.
segment_centrality
Removed in 5.0; raises with guidance.
Deprecated
Since version 5.0. The continuous-segment engine (segment_density / harmonic / beta / betweenness) was removed
at the low level, so the old numbers cannot be reproduced. The nearest equivalent is
centrality_shortest(..., segment_weighted=True) — a different calculation. See COMPATIBILITY.md.