ANALYSIS TOOL BOX

Network Analysis

CalculateNodeCentralityMeasures

Calculate degree, betweenness, closeness, harmonic, eigenvector, and PageRank centrality for every node in a network, with per-measure ranks.

networkxgraphcentralityrelational data

Introduction

Who actually matters in a network — the most connected node, or the one quietly sitting on every shortest path between everyone else? Those are different questions with different answers, and CalculateNodeCentralityMeasures computes six distinct notions of "importance" (degree, betweenness, closeness, harmonic, eigenvector, and PageRank) for every node in a graph built directly from a raw edge-list DataFrame, ranking nodes on each one so you can see where a node's standing shifts depending on what "central" means for the question at hand. It wraps BuildGraphFromEdgeList internally, so you never construct a networkx graph by hand, and any node metadata you supply is carried straight through into the output.

Teaching Note

"Centrality" is not one thing — it is a family of measures that each define "important node" differently, and picking the wrong one for the question at hand is a common analytical mistake:

  • Degree centrality (in-degree/out-degree for directed graphs) counts direct connections. It answers "who is locally active or popular?" but says nothing about a node's position in the broader network.
  • Betweenness centrality counts how often a node sits on the shortest path between other pairs of nodes. It answers "who is a broker or bottleneck?" — high-betweenness nodes can control or disrupt the flow of information, goods, or disease through a network even if they have few direct connections themselves.
  • Closeness centrality measures how few steps it takes to reach every other node. It answers "who can spread something fastest?"
  • Harmonic centrality is a variant of closeness that handles disconnected graphs gracefully (unreachable nodes contribute zero instead of an undefined infinite distance), which makes it more robust than closeness on real-world, fragmented networks.
  • Eigenvector centrality and PageRank both weigh a connection by the importance of the node on the other end, so being linked to a few highly-connected nodes counts for more than being linked to many peripheral ones. PageRank additionally dampens the effect of very high out-degree nodes flooding their neighbors with influence, which is why it tends to be more stable on directed graphs.

Reporting several of these side by side — and ranking nodes on each — is standard practice, because a node that looks unremarkable on one measure (e.g., low degree) can be critical on another (e.g., high betweenness), and that mismatch is itself often the interesting finding (for example, a low-profile intermediary that many transactions quietly route through).

Weighted graphs: strength vs. distance (a common trap). When edge_weight_column is set, networkx does NOT treat "weight" the same way across every measure, and this asymmetry silently produces misleading results if you don't account for it:

  • Degree, eigenvector centrality, and PageRank treat a bigger weight as a STRONGER connection. Degree centrality itself is replaced with weighted degree, known as "strength" in network science: the sum of a node's incident edge weights rather than a simple edge count. A node with three heavily-weighted edges will out-rank a node with ten barely-weighted ones.
  • Betweenness, closeness, and harmonic centrality treat a bigger weight as a LONGER/COSTLIER path, because they are built on shortest-path algorithms where "weight" means "distance". A node connected by high-weight edges will look FARTHER away on these measures, not closer.

If your edge_weight_column represents something strength-like (call volume, dollars transacted, number of shared meetings), betweenness, closeness, and harmonic centrality computed directly on it will treat your most active relationships as the least traversable ones. To get sensible path-based results in that case, invert the weight before calling this function (e.g., 1 / weight, or max(weight) - weight + 1) so that "more interaction" maps to "shorter distance". This function prints a reminder of which requested measures fall on which side of this divide whenever a weighted graph is used.

Parameters

ParameterTypeDefaultDescription
dataframerequiredpd.DataFrameA pandas DataFrame in long format, with one row per edge (relationship).
node_1_columnrequiredstrName of the column containing the first endpoint of each edge.
node_2_columnrequiredstrName of the column containing the second endpoint of each edge.
is_directedboolFalseWhether the relationship is directional (node_1 -> node_2). Defaults to False. When True, 'in_degree' and 'out_degree' become available (and are used in place of 'degree' by default); when False, only the undirected 'degree' measure is available.
edge_weight_columnstrNoneOptional name of a column containing edge weights, attached to each edge as a 'weight' attribute. See the Teaching Note above for how this changes each measure's interpretation. Defaults to None.
node_attributes_dataframepd.DataFrameNoneOptional DataFrame of node-level metadata (e.g., name, type, organization, country) to attach to nodes and carry through into the output. Must be used together with node_id_column. Defaults to None.
node_id_columnstrNoneName of the column in node_attributes_dataframe that identifies each node. Required if node_attributes_dataframe is provided. Defaults to None.
allow_self_loopsboolFalseWhether to keep edges where node_1 and node_2 are the same entity. Defaults to False. See BuildGraphFromEdgeList for details.
allow_multi_edgesboolFalseMust be False. Centrality algorithms in networkx are not defined for MultiGraph/MultiDiGraph, so multi-edges cannot be passed through to this function. If your data has parallel edges, aggregate them into an edge_weight_column (e.g., a count or sum) before calling this function. Defaults to False.
list_of_centrality_measureslist[str]NoneList of centrality measures to calculate. Valid values are 'degree', 'in_degree', 'out_degree' (directed graphs only), 'betweenness', 'closeness', 'eigenvector', 'pagerank', and 'harmonic' -- each maps directly to the corresponding algorithm documented at networkx.org. If None, defaults to ['degree', 'betweenness', 'closeness', 'eigenvector', 'pagerank', 'harmonic'] for undirected graphs, or ['in_degree', 'out_degree', 'betweenness', 'closeness', 'eigenvector', 'pagerank', 'harmonic'] for directed graphs. Defaults to None.
print_summaryboolTrueWhether to print the graph build report (see BuildGraphFromEdgeList), the weighted-measure semantics reminder (if applicable), and a final count of measures calculated. Defaults to True.

Returns

A pandas DataFrame with one row per node, containing: Node (the node identifier), any columns carried over from node_attributes_dataframe, one column per requested centrality measure (e.g., Degree_Centrality, Degree_Strength if weighted, Betweenness_Centrality, PageRank), and one Rank_<measure column> column per measure, ranking nodes from most central (1) to least central, with ties sharing the same rank.

Example

python
from analysistoolbox.network_analysis import CalculateNodeCentralityMeasures
import pandas as pd

# Unweighted, undirected: who brokers connections between clusters?
edges_df = pd.DataFrame({
    'person_a': ['Alice', 'Alice', 'Bob', 'Carol', 'Carol', 'Dave'],
    'person_b': ['Bob', 'Carol', 'Carol', 'Dave', 'Eve', 'Eve']
})
centrality_df = CalculateNodeCentralityMeasures(
    edges_df,
    node_1_column='person_a',
    node_2_column='person_b',
    list_of_centrality_measures=['degree', 'betweenness']
)
top_broker = centrality_df.sort_values('Rank_Betweenness_Centrality').iloc[0]

# Weighted, directed: transaction network with node metadata attached
node_lookup_df = pd.DataFrame({
    'account_id': ['Alice', 'Bob', 'Carol', 'Dave', 'Eve'],
    'account_type': ['individual', 'individual', 'business', 'business', 'individual']
})
weighted_df = CalculateNodeCentralityMeasures(
    edges_df.assign(dollar_amount=[500, 1200, 300, 8000, 150, 4200]),
    node_1_column='person_a',
    node_2_column='person_b',
    is_directed=True,
    edge_weight_column='dollar_amount',
    node_attributes_dataframe=node_lookup_df,
    node_id_column='account_id',
    list_of_centrality_measures=['in_degree', 'out_degree', 'pagerank']
)