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 matching cc_ 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 graph module (see the automatic graph cleaning for examples).
  • harmonic closeness 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. CityNetwork construction removes near-duplicate edges and short self-loops automatically; when building the network manually, consolidate nearby nodes (see nx_consolidate_nodes) before computing harmonic closeness.
  • Simplest (angular) measures require a dual graph representation. CityNetwork builds the dual automatically; this step only applies to the manual method, where primal graphs must be converted with graphs.nx_to_dual before 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

centrality_shortest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
minutes : list[float] | None = None
closeness : dict[str, str] | None = None
betweenness : dict[str, str] | None = None
cycles : bool = True
postprocess : dict[str, str] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
segment_weighted : bool = False
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

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

network_structure
None

nodes_gdf
None

A GeoDataFrame representing nodes. The outputs of calculations will be written to this GeoDataFrame.

distances
list[int]

Distance thresholds in metres at which to compute centrality measures.

minutes
list[float]

Walking times in minutes; converted to distance thresholds using speed_m_s.

closeness
dict[str, str]

Closeness metric expressions. Each entry is {name: expr(c, p)}, accumulated per reached node. None uses defaults: density, farness, harmonic, decay.

betweenness
dict[str, str]

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.

cycles
bool

If True, compute circuit rank (cycle count) for each node. Default True.

postprocess
dict[str, str]

Derived metrics computed in Python from the closeness/betweenness results. None uses default: {"hillier": "density**2 / farness"}.

speed_m_s
float

Speed in metres per second for converting minutes to distance thresholds.

tolerance
float

Relative tolerance for betweenness path equality, as a percentage (e.g. 1.0 = 1%).

segment_weighted
bool

If True, weight by primal edge (street segment) lengths. Requires a dual graph.

random_seed
int

Optional seed for reproducible sampling.

sample
bool

If True, enables adaptive sampling at longer distance thresholds.

epsilon
float

Error tolerance for sampling. Defaults to sampling.HOEFFDING_EPSILON (0.05).

Returns

nodes_gdf
GeoDataFrame

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_od_matrix
(
od_df : pandas.DataFrame
zones_gdf : geopandas.geodataframe.GeoDataFrame
network_structure : NetworkStructure
origin_col : str
destination_col : str
weight_col : str
zone_id_col : str | None = None
max_snap_dist : float = 500.0
)->[ OdMatrix ]

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

od_df
pd.DataFrame

Origin-destination flow data with columns for origin zone, destination zone, and weight.

zones_gdf
gpd.GeoDataFrame

Zone boundaries (polygons) or centroids (points). Must be in a projected CRS matching the network, or in EPSG:4326 (will be auto-reprojected).

network_structure
rustalgos.graph.NetworkStructure

The network to snap zone centroids to.

origin_col
str

Column in od_df containing origin zone identifiers.

destination_col
str

Column in od_df containing destination zone identifiers.

weight_col
str

Column in od_df containing trip weights (e.g., number of bicycle commuters).

zone_id_col
str | None

Column in zones_gdf containing zone identifiers matching origin_col/destination_col. If None, uses the GeoDataFrame index.

max_snap_dist
float

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

rustalgos.centrality.OdMatrix

Sparse OD matrix ready for use with betweenness_od.

betweenness_od

betweenness_od
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
od_matrix : OdMatrix
distances : list[int] | None = None
minutes : list[float] | None = None
betweenness : dict[str, str] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
)->[ GeoDataFrame ]

Compute OD-weighted betweenness centrality using the shortest path heuristic.

Parameters

network_structure
None

nodes_gdf
None

A GeoDataFrame representing nodes. The outputs of calculations will be written to this GeoDataFrame.

od_matrix
None

An OdMatrix mapping (origin, destination) node pairs to trip weights. Build with build_od_matrix.

distances
list[int]

Distance thresholds in metres at which to compute betweenness.

minutes
list[float]

Walking times in minutes; converted to distance thresholds using speed_m_s.

betweenness
dict[str, str]

Betweenness metric expressions. None uses defaults: betweenness, betweenness_decay.

speed_m_s
float

Speed in metres per second for converting minutes to distance thresholds.

tolerance
float

Relative tolerance for path equality, as a percentage.

Returns

nodes_gdf
GeoDataFrame

The input nodes_gdf parameter is returned with additional betweenness columns.

betweenness_demand

betweenness_demand
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
origins_gdf : geopandas.geodataframe.GeoDataFrame
destinations_gdf : geopandas.geodataframe.GeoDataFrame
origin_weight_col : str
destination_weight_col : str
distances : list[int] | None = None
minutes : list[float] | None = None
decay_fn : str = 'exp(-4 * p)'
closest_destination : bool = False
participation : float = 1.0
metric_name : str = 'demand'
max_netw_assign_dist : float = 100.0
barriers_gdf : geopandas.geodataframe.GeoDataFrame | None = None
n_nearest_candidates : int = 50
speed_m_s : float = 1.33333
tolerance : float | None = None
)->[ GeoDataFrame ]

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 oo and reachable destination dd the allocated flow is

Wod=WoWdf(cod)K+dWdf(cod)W_{od} = W_o \cdot \frac{W_d \cdot f(c_{od})}{K + \sum_{d'} W_{d'} \cdot f(c_{od'})}

where ff is decay_fn, codc_{od} is the network distance, and KK is a stay-home alternative in the destination choice set, derived from the participation share. At full participation (K=0K = 0, 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 Ao/(K+Ao)A_o / (K + A_o), where AoA_o is its accessibility dWdf(cod)\sum_{d'} W_{d'} f(c_{od'}), 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

network_structure
None

nodes_gdf
None

A nodes GeoDataFrame; flow betweenness columns are written to it and it is returned.

origins_gdf
None

A GeoDataFrame of demand origins (points or centroids).

destinations_gdf
None

A GeoDataFrame of demand destinations / attractors (points or centroids).

origin_weight_col
None

Column in origins_gdf giving each origin’s weight (e.g. population).

destination_weight_col
None

Column in destinations_gdf giving each destination’s attractiveness weight.

distances
list[int]

Distance thresholds in metres at which to compute flow betweenness.

minutes
list[float]

Walking times in minutes; converted to distance thresholds using speed_m_s.

decay_fn
str

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.

closest_destination
bool

If True, each origin routes its participating weight to its single nearest reachable destination instead of allocating across all of them.

participation
float

The share of people at a typical location who make a trip, in (0,1](0, 1]. 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 (K=Amed(1s)/sK = A_{med} \cdot (1 - s) / s, 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.

metric_name
str

Name used for the output column (cc_{metric_name}_{distance}). Defaults to "demand".

max_netw_assign_dist
float

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.

barriers_gdf
GeoDataFrame

Optional barriers to respect during assignment, as in the data layers.

n_nearest_candidates
int

The number of nearest candidate edges to consider when assigning points to the network, as in the data layers.

speed_m_s
float

Speed in metres per second for converting minutes to distance thresholds.

tolerance
float

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

nodes_gdf
GeoDataFrame

The input nodes_gdf with a flow-betweenness column added per distance threshold.

centrality_simplest

centrality_simplest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
minutes : list[float] | None = None
closeness : dict[str, str] | None = None
betweenness : dict[str, str] | None = None
postprocess : dict[str, str] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
segment_weighted : bool = False
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

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

network_structure
None

nodes_gdf
None

A GeoDataFrame representing nodes. The outputs of calculations will be written to this GeoDataFrame.

distances
list[int]

Distance thresholds in metres at which to compute centrality measures.

minutes
list[float]

Walking times in minutes; converted to distance thresholds using speed_m_s.

closeness
dict[str, str]

Closeness metric expressions. None uses defaults: density, farness, harmonic.

betweenness
dict[str, str]

Betweenness metric expressions. None uses defaults: betweenness.

postprocess
dict[str, str]

Derived metrics. None uses default: {"hillier": "density**2 / farness"}.

speed_m_s
float

Speed in metres per second for converting minutes to distance thresholds.

tolerance
float

Relative tolerance for angular betweenness path equality, as a percentage.

segment_weighted
bool

If True, weight by primal edge (street segment) lengths. Requires a dual graph.

random_seed
int

Optional seed for reproducible sampling.

sample
bool

If True, enables adaptive sampling at longer distance thresholds.

epsilon
float

Error tolerance for sampling. Defaults to sampling.HOEFFDING_EPSILON (0.05).

Returns

nodes_gdf
GeoDataFrame

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

closeness_shortest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
minutes : list[float] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

Compute closeness centrality using shortest paths. Wraps centrality_shortest with betweenness disabled.

closeness_simplest

closeness_simplest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
minutes : list[float] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

Compute closeness centrality using simplest (angular) paths. Wraps centrality_simplest with betweenness disabled.

betweenness_shortest

betweenness_shortest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
minutes : list[float] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

Compute betweenness centrality using shortest paths. Wraps centrality_shortest with closeness disabled.

betweenness_simplest

betweenness_simplest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
minutes : list[float] | None = None
speed_m_s : float = 1.33333
tolerance : float | None = None
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

Compute betweenness centrality using simplest (angular) paths. Wraps centrality_simplest with closeness disabled.

node_centrality_shortest

node_centrality_shortest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
betas : list[float] | None = None
minutes : list[float] | None = None
compute_closeness : bool = True
compute_betweenness : bool = True
min_threshold_wt : float = 0.01831563888873418
speed_m_s : float = 1.33333
tolerance : float | None = None
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

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

node_centrality_simplest
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
distances : list[int] | None = None
betas : list[float] | None = None
minutes : list[float] | None = None
compute_closeness : bool = True
compute_betweenness : bool = True
min_threshold_wt : float = 0.01831563888873418
speed_m_s : float = 1.33333
angular_scaling_unit : float = 90
farness_scaling_offset : float = 1
tolerance : float | None = None
random_seed : int | None = None
sample : bool = False
epsilon : float | None = None
)->[ GeoDataFrame ]

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

segment_centrality
(
*_args
**_kwargs
)->[ GeoDataFrame ]

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.