# cityseer.metrics.networks

Centrality methods

## distance_from_beta

distance_from_beta(beta,
min_threshold_wt=checks.def_min_thresh_wt)
-> np.ndarray


Maps decay parameters $\beta$ to equivalent distance thresholds $d_{max}$ at the specified cutoff weight $w_{min}$.

##### Comment

It is generally not necessary to utilise this function directly. It will be called internally, if necessary, when invoking NetworkLayer or NetworkLayerFromNX.

##### Parameters
beta
Union[float, list, np.ndarray]

$\beta$ value/s to convert to distance thresholds $d_{max}$.

min_threshold_wt
float

An optional cutoff weight $w_{min}$ at which to set the distance threshold $d_{max}$, default of 0.01831563888873418.

##### Returns
np.ndarray

A numpy array of distance thresholds $d_{max}$.

##### Notes
from cityseer.metrics import networks
# a list of betas
betas = [0.01, 0.02]
# convert to distance thresholds
d_max = networks.distance_from_beta(betas)
print(d_max)
# prints: array([400., 200.])

Weighted measures such as the gravity index, weighted betweenness, and weighted land-use accessibilities are computed using a negative exponential decay function in the form of:

$weight = exp(-\beta \cdot distance)$

The strength of the decay is controlled by the $\beta$ parameter, which reflects a decreasing willingness to walk correspondingly farther distances. For example, if $\beta=0.005$ were to represent a person’s willingness to walk to a bus stop, then a location 100m distant would be weighted at 60\% and a location 400m away would be weighted at 13.5\%. After an initially rapid decrease, the weightings decay ever more gradually in perpetuity; thus, once a sufficiently small weight is encountered it becomes computationally expensive to consider locations any farther away. The minimum weight at which this cutoff occurs is represented by $w_{min}$, and the corresponding maximum distance threshold by $d_{max}$.

NetworkLayer and NetworkLayerFromNX can be invoked with either distances or betas parameters, but not both. If using the betas parameter, then this function will be called in order to extrapolate the distance thresholds implicitly, using:

$d_{max} = \frac{log(w_{min})}{-\beta}$

The default min_threshold_wt of $w_{min}=0.01831563888873418$ yields conveniently rounded $d_{max}$ walking thresholds, for example:

$\beta$$d_{max}$
0.02200m
0.01400m
0.005800m
0.00251600m

Overriding the default $w_{min}$ will adjust the $d_{max}$ accordingly, for example:

$\beta$$w_{min}$$d_{max}$
0.020.01230m
0.010.01461m
0.0050.01921m
0.00250.011842m

## beta_from_distance

beta_from_distance(distance,
min_threshold_wt=checks.def_min_thresh_wt)
-> np.ndarray


Maps distance thresholds $d_{max}$ to equivalent decay parameters $\beta$ at the specified cutoff weight $w_{min}$. See distance_from_beta for additional discussion.

##### Comment

It is generally not necessary to utilise this function directly. It will be called internally, if necessary, when invoking NetworkLayer or NetworkLayerFromNX.

##### Parameters
distance
Union[float, list, np.ndarray]

$d_{max}$ value/s to convert to decay parameters $\beta$.

min_threshold_wt
float

The cutoff weight $w_{min}$ on which to model the decay parameters $\beta$, default of 0.01831563888873418.

##### Returns
np.ndarray

A numpy array of decay parameters $\beta$.

##### Notes
from cityseer.metrics import networks
# a list of betas
distances = [400, 200]
# convert to betas
betas = networks.beta_from_distance(distances)
print(betas)  # prints: array([0.01, 0.02])

NetworkLayer and NetworkLayerFromNX can be invoked with either distances or betas parameters, but not both. If using the distances parameter, then this function will be called in order to extrapolate the decay parameters implicitly, using:

$\beta = -\frac{log(w_{min})}{d_{max}}$

The default min_threshold_wt of $w_{min}=0.01831563888873418$ yields conveniently rounded $\beta$ parameters, for example:

$d_{max}$$\beta$
200m0.02
400m0.01
800m0.005
1600m0.0025

## class NetworkLayer

Network layers are used for network centrality computations and provide the backbone for landuse and statistical aggregations. NetworkLayerFromNX should be used instead if converting from a NetworkXMultiGraph to a NetworkLayer.

A NetworkLayer requires either a set of distances $d_{max}$ or equivalent exponential decay parameters $\beta$, but not both. The unprovided parameter will be calculated implicitly in order to keep weighted and unweighted metrics in lockstep. The min_threshold_wt parameter can be used to generate custom mappings from one to the other: see distance_from_beta for more information. These distances and betas are used for any subsequent centrality and land-use calculations.

from cityseer.metrics import networks
from cityseer.tools import mock, graphs

# prepare a mock graph
G = mock.mock_graph()
G = graphs.nX_simple_geoms(G)

# if initialised with distances:
# betas for weighted metrics will be generated implicitly
N = networks.NetworkLayerFromNX(G, distances=[200, 400, 800, 1600])
print(N.distances)  # prints: [200, 400, 800, 1600]
print(N.betas)  # prints: [0.02, 0.01, 0.005, 0.0025]

# if initialised with betas:
# distances for non-weighted metrics will be generated implicitly
N = networks.NetworkLayerFromNX(G, betas=[0.02, 0.01, 0.005, 0.0025])
print(N.distances)  # prints: [200, 400, 800, 1600]
print(N.betas)  # prints: [0.02, 0.01, 0.005, 0.0025]

There are two network centrality methods available depending on whether you’re using a node-based or segment-based approach:

These methods wrap the underlying numba optimised functions for computing centralities, and provides access to all of the underlying node-based or segment-based centrality methods. Multiple selected measures and distances are computed simultaneously to reduce the amount of time required for multi-variable and multi-scalar strategies.

##### Comment

The reasons for picking one approach over another are varied:

• Node based centralities compute the measures relative to each reachable node within the threshold distances. For this reason, they can be susceptible to distortions caused by messy graph topologies such redundant and varied concentrations of degree=2 nodes (e.g. to describe roadway geometry) or needlessly complex representations of street intersections. In these cases, the network should first be cleaned using methods such as those available in the graph module (see the graph cleaning guide for examples). If a network topology has varied intensities of nodes but the street segments are less spurious, then segmentised methods can be preferable because they are based on segment distances: segment aggregations remain the same regardless of the number of intervening nodes, however, are not immune from situations such as needlessly complex representations of roadway intersections or a proliferation of walking paths in greenspaces;
• Node-based harmonic centrality can be problematic on graphs where nodes are erroneously placed too close together or where impedances otherwise approach zero, as may be the case for simplest-path measures or small distance thesholds. This happens because the outcome of the division step can balloon towards $\infty$ once impedances decrease below 1.
• Note that cityseer’s implementation of simplest (angular) measures work on both primal (node or segment based) and dual graphs (node only).
• Measures should only be directly compared on the same topology because different topologies can otherwise affect the expression of a measure. Accordingly, measures computed on dual graphs cannot be compared to measures computed on primal graphs because this does not account for the impact of differing topologies. Dual graph representations can have substantially greater numbers of nodes and edges for the same underlying street network; for example, a four-way intersection consisting of one node with four edges translates to four nodes and six edges on the dual. This effect is amplified for denser regions of the network.
• Segmentised versions of centrality measures should not be computed on dual graph topologies because street segment lengths would be duplicated for each permutation of dual edge spanning street intersections. By way of example, the contribution of a single edge segment at a four-way intersection would be duplicated three times.
• Global closeness is strongly discouraged because it does not behave suitably for localised graphs. Harmonic closeness or improved closeness should be used instead. Note that Global closeness ($\frac{nodes}{farness}$) and improved closeness ($\frac{nodes}{farness / nodes}$) can be recovered from the available metrics, if so desired, through additional (manual) steps.
• Network decomposition can be a useful strategy when working at small distance thresholds, and confers advantages such as more regularly spaced snapshots and fewer artefacts at small distance thresholds where street edges intersect distance thresholds. However, the regular spacing of the decomposed segments will introduce spikes in the distributions of node-based centrality measures when working at very small distance thresholds. Segmentised versions may therefore be preferable when working at small thresholds on decomposed networks.

The computed metrics will be written to a dictionary available at the NetworkLayer.metrics property and will be categorised by the respective centrality and distance keys:

NetworkLayer.metrics['centrality'][<<measure key>>][<<distance key>>][<<node idx>>]

For example, if node_density, and node_betweenness_beta centrality keys are computed at 800m and 1600m, then the dictionary would assume the following structure:

# example structure
NetworkLayer.metrics = {
'centrality': {
'node_density': {
800: [<numpy array>],
1600: [<numpy array>]
},
'node_betweenness_beta': {
800: [<numpy array>],
1600: [<numpy array>]
}
}
}

A worked example:

from cityseer.metrics import networks
from cityseer.tools import mock, graphs

# prepare a mock graph
G = mock.mock_graph()
G = graphs.nX_simple_geoms(G)

# generate the network layer and compute some metrics
N = networks.NetworkLayerFromNX(G, distances=[200, 400, 800, 1600])
# compute a centrality measure
N.node_centrality(measures=['node_density', 'node_betweenness_beta'])

# fetch node density for 400m threshold for the first 5 nodes
print(N.metrics['centrality']['node_density'][400][:5])
# prints [15, 13, 10, 11, 12]

# fetch betweenness beta for 1600m threshold for the first 5 nodes
print(N.metrics['centrality']['node_betweenness_beta'][1600][:5])
# prints [76.01161, 45.261307, 6.805982, 11.478158, 33.74703]

The data can be handled using the underlying numpy arrays, and can also be unpacked to a dictionary using NetworkLayer.metrics_to_dict or transposed to a networkX graph using NetworkLayer.to_networkX.

## NetworkLayer

NetworkLayer(node_uids,
node_data,
edge_data,
node_edge_map,
distances=None,
betas=None,
min_threshold_wt=checks.def_min_thresh_wt)

##### Parameters
node_uids
Union[list, tuple]

A list or tuple of node identifiers corresponding to each node. This list must be in the same order and of the same length as the node_data.

node_data
np.ndarray

A 2d numpy array representing the graph’s nodes. The indices of the second dimension correspond as follows:

idxproperty
0x coordinate
1y coordinate
2bool describing whether the node is live. Metrics are only computed for live nodes.

The x and y node attributes determine the spatial coordinates of the node, and should be in a suitable projected (flat) coordinate reference system in metres. nX_wgs_to_utm can be used for converting a networkX graph from WGS84 lng, lat geographic coordinates to the local UTM x, y projected coordinate system.

When calculating local network centralities or land-use accessibilities, it is best-practice to buffer the network by a distance equal to the maximum distance threshold to be considered. This prevents problematic results arising due to boundary roll-off effects.

The live node attribute identifies nodes falling within the areal boundary of interest as opposed to those that fall within the surrounding buffered area. Calculations are only performed for live=True nodes, thus reducing frivolous computation while also cleanly identifying which nodes are in the buffered roll-off area. If some other process will be used for filtering the nodes, or if boundary roll-off is not being considered, then set all nodes to live=True.

edge_data
np.ndarray

A 2d numpy array representing the graph’s edges. Each edge will be described separately for each direction of travel. The indices of the second dimension correspond as follows:

idxproperty
0start node idx
1end node idx
2the segment length in metres
3the sum of segment’s angular change
4an ‘impedance factor’ which can be applied to magnify or reduce the effect of the edge’s impedance on shortest-path calculations. e.g. for gradients or other such considerations. Use with caution.
5the edge’s entry angular bearing
6the edge’s exit angular bearing

The start and end edge idx attributes point to the corresponding node indices in the node_data array.

The length edge attribute (index 2) should always correspond to the edge lengths in metres. This is used when calculating the distances traversed by the shortest-path algorithm so that the respective $d_{max}$ maximum distance thresholds can be enforced: these distance thresholds are based on the actual network-paths traversed by the algorithm as opposed to crow-flies distances.

The angle_sum edge bearing (index 3) should correspond to the total angular change along the length of the segment. This is used when calculating angular impedances for simplest-path measures. The start_bearing (index 5) and end_bearing (index 6) attributes respectively represent the starting and ending bearing of the segment. This is also used when calculating simplest-path measures when the algorithm steps from one edge to another.

The imp_factor edge attribute (index 4) represents an impedance multiplier for increasing or diminishing the impedance of an edge. This is ordinarily set to 1, therefor not impacting calculations. By setting this to greater or less than 1, the edge will have a correspondingly higher or lower impedance. This can be used to take considerations such as street gradients into account, but should be used with caution.

node_edge_map
Dict

A numbaDict with node_data indices as keys and numbaList types as values containing the out-edge indices for each node.

distances
Union[list, tuple, np.ndarray]

A distance, or list, tuple, or numpy array of distances corresponding to the local $d_{max}$ thresholds to be used for centrality (and land-use) calculations. The $\beta$ parameters (for distance-weighted metrics) will be determined implicitly. If the distances parameter is not provided, then the beta parameter must be provided instead. Use a distance of np.inf where no distance threshold should be enforced.

betas
Union[list, tuple, np.ndarray]

A $\beta$, or list, tuple, or numpy array of $\beta$ to be used for the exponential decay function for weighted metrics. The distance parameters for unweighted metrics will be determined implicitly. If the betas parameter is not provided, then the distance parameter must be provided instead.

min_threshold_wt
float

The default min_threshold_wt parameter can be overridden to generate custom mappings between the distance and beta parameters. See distance_from_beta for more information.

##### Returns
NetworkLayer

A NetworkLayer.

##### Comment

It is possible to represent unlimited $d_{max}$ distance thresholds by setting one of the specified distance parameter values to np.inf. Note that this may substantially increase the computational time required for the completion of the algorithms on large networks.

#### NetworkLayer.uids

Unique ids corresponding to each node in the graph’s node_map.

#### NetworkLayer.distances

The distance threshold/s at which the class has been initialised.

#### NetworkLayer.betas

The distance decay $\beta$ thresholds (spatial impedance) at which the class is initialised.

#### NetworkLayer.networkX_multigraph

If initialised with NetworkLayerFromNX, the networkXMultiGraph from which the graph is derived.

## NetworkLayer.metrics_to_dict

NetworkLayer.metrics_to_dict()


Unpacks all calculated metrics from the NetworkLayer.metrics property into a python dictionary. The dictionary keys will correspond to the node uids.

##### Notes
from cityseer.metrics import networks
from cityseer.tools import mock, graphs

# prepare a mock graph
G = mock.mock_graph()
G = graphs.nX_simple_geoms(G)

# generate the network layer and compute some metrics
N = networks.NetworkLayerFromNX(G, distances=[200, 400, 800, 1600])
N.node_centrality(measures=['node_harmonic'])

# let's select a random node id
random_idx = 6
random_uid = N.uids[random_idx]

# the data is directly available at N.metrics
# in this case the data is stored in arrays corresponding to the node indices
print(N.metrics['centrality']['node_harmonic'][200][random_idx])
# prints: 0.023120252

# let's convert the data to a dictionary
# the unpacked data is now stored by the uid of the node identifier
data_dict = N.metrics_to_dict()
print(data_dict[random_uid]['centrality']['node_harmonic'][200])
# prints: 0.023120252

## NetworkLayer.to_networkX

NetworkLayer.to_networkX()
-> nx.MultiGraph


Transposes a NetworkLayer into a networkXMultiGraph. This method calls nX_from_graph_maps internally.

##### Returns
nx.MultiGraph

A networkXMultiGraph.

x, y, and live node attributes will be copied from node_data to the MultiGraph nodes. length, angle_sum, imp_factor, start_bearing, and end_bearing attributes will be copied from the edge_data to the MultiGraph edges.

If a metrics_dict is provided, all derived data will be copied to the MultiGraph nodes based on matching node identifiers.

##### Notes
from cityseer.metrics import networks
from cityseer.tools import mock, graphs

# prepare a mock graph
G = mock.mock_graph()
G = graphs.nX_simple_geoms(G)

# generate the network layer and compute some metrics
N = networks.NetworkLayerFromNX(G, distances=[200, 400, 800, 1600])
# compute some-or-other metrics
N.node_centrality(measures=['node_harmonic'])
# convert back to networkX
G_post = N.to_networkX()

# let's select a random node id
random_idx = 6
random_uid = N.uids[random_idx]

print(N.metrics['centrality']['node_harmonic'][200][random_idx])
# prints: 0.023120252

# the metrics have been copied to the new networkX graph
print(G_post.nodes[random_uid]['metrics']['centrality']['node_harmonic'][200])
# prints: 0.023120252

A networkX graph before conversion to a NetworkLayer.A graph after conversion back to networkX.

## NetworkLayer.compute_centrality

NetworkLayer.compute_centrality(**kwargs)


This method is deprecated and, if invoked, will raise a DeprecationWarning. Please use node_centrality or segment_centrality instead.

## NetworkLayer.node_centrality

NetworkLayer.node_centrality(measures=None,
angular=False)

##### Parameters
measures
Union[list, tuple]

A list or tuple of strings, containing any combination of the following key values, computed within the respective distance thresholds of $d_{max}$.

angular
bool

A boolean indicating whether to use shortest or simplest path heuristics, by default False

##### Notes

The following keys use the shortest-path heuristic, and are available when the angular parameter is set to the default value of False:

keyformulanotes
node_density$\scriptstyle\sum_{j\neq{i}}^{n}1$A summation of nodes.
node_farness$\scriptstyle\sum_{j\neq{i}}^{n}d_{(i,j)}$A summation of distances in metres.
node_cycles$\scriptstyle\sum_{j\neq{i}j=cycle}^{n}1$A summation of network cycles.
node_harmonic$\scriptstyle\sum_{j\neq{i}}^{n}\frac{1}{Z_{(i,j)}}$Harmonic closeness is an appropriate form of closeness centrality for localised implementations constrained by the threshold $d_{max}$.
node_beta$\scriptstyle\sum_{j\neq{i}}^{n}\\exp(-\beta\cdot d[i,j])$Also known as the ’gravity index’. This is a spatial impedance metric differentiated from other closeness centralities by the use of an explicit $\beta$ parameter, which can be used to model the decay in walking tolerance as distances increase.
node_betweenness$\scriptstyle\sum_{j\neq{i}}^{n}\sum_{k\neq{j}\neq{i}}^{n}1$Betweenness centrality summing all shortest-paths traversing each node $i$.
node_betweenness_beta$\scriptstyle\sum_{j\neq{i}}^{n}\sum_{k\neq{j}\neq{i}}^{n}\\exp(-\beta\cdot d[j,k])$Applies a spatial impedance decay function to betweenness centrality. $d$ represents the full distance from any $j$ to $k$ node pair passing through node $i$.

The following keys use the simplest-path (shortest-angular-path) heuristic, and are available when the angular parameter is explicitly set to True:

keyformulanotes
node_harmonic_angular$\scriptstyle\sum_{j\neq{i}}^{n}\frac{1}{Z_{(i,j)}}$The simplest-path implementation of harmonic closeness uses angular-distances for the impedance parameter. Angular-distances are normalised by 180 and added to 1 to avoid division by zero: $\scriptstyle{Z = 1 + (angularchange/180)}$.
node_betweenness_angular$\scriptstyle\sum_{j\neq{i}}^{n}\sum_{k\neq{j}\neq{i}}^{n}1$The simplest-path version of betweenness centrality. This is distinguished from the shortest-path version by use of a simplest-path heuristic (shortest angular distance).

## NetworkLayer.segment_centrality

NetworkLayer.segment_centrality(measures=None,
angular=False)


A list or tuple of strings, containing any combination of the following key values, computed within the respective distance thresholds of $d_{max}$.

##### Parameters
measures
Union[list, tuple]

A list or tuple of strings, containing any combination of the following key values, computed within the respective distance thresholds of $d_{max}$.

angular
bool

A boolean indicating whether to use shortest or simplest path heuristics, by default False

##### Notes

The following keys use the shortest-path heuristic, and are available when the angular parameter is set to the default value of False:

keyformulanotes
segment_density$\scriptstyle\sum_{(a, b)}^{edges}d_{b} - d_{a}$A summation of edge lengths.
segment_harmonic$\scriptstyle\sum_{(a, b)}^{edges}\int_{a}^{b}\ln(b) -\ln(a)$A continuous form of harmonic closeness centrality applied to edge lengths.
segment_beta$\scriptstyle\sum_{(a, b)}^{edges}\int_{a}^{b}\frac{\exp(-\beta\cdot b) -\exp(-\beta\cdot a)}{-\beta}$A continuous form of beta-weighted (gravity index) centrality applied to edge lengths.
segment_betweennessA continuous form of betweenness: Resembles segment_beta applied to edges situated on shortest paths between all nodes $j$ and $k$ passing through $i$.

The following keys use the simplest-path (shortest-angular-path) heuristic, and are available when the angular parameter is explicitly set to True.

keyformulanotes
segment_harmonic_hybrid$\scriptstyle\sum_{(a, b)}^{edges}\frac{d_{b} - d_{a}}{Z}$Weights angular harmonic centrality by the lengths of the edges. See node_harmonic_angular.
segment_betweeness_hybridA continuous form of angular betweenness: Resembles segment_harmonic_hybrid applied to edges situated on shortest paths between all nodes $j$ and $k$ passing through $i$.

## NetworkLayerFromNX

NetworkLayerFromNX(networkX_multigraph,
distances=None,
betas=None,
min_threshold_wt=checks.def_min_thresh_wt)
-> NetworkLayer


Directly transposes a networkXMultiGraph into a NetworkLayer. This class simplifies the conversion of a NetworkXMultiGraph by calling graph_maps_from_nX internally. Methods and properties are inherited from the parent NetworkLayer class.

##### Parameters
networkX_multigraph
nx.MultiGraph

A networkXMultiGraph.

x and y node attributes are required. The live node attribute is optional, but recommended. See NetworkLayer for more information about what these attributes represent.

distances
Union[list, tuple, np.ndarray]
betas
Union[list, tuple, np.ndarray]
min_threshold_wt
float
##### Returns
NetworkLayer

A NetworkLayer.