Yog.Centrality (YogEx v0.97.0)

Copy Markdown View Source

Centrality measures for identifying important nodes in graphs.

Provides degree, closeness, harmonic, betweenness, and PageRank centrality. All functions return a map of Node IDs to their scores.

Overview

MeasureFunctionBest For
Degreedegree/2Local connectivity
Closenesscloseness/2Distance to all others
Harmonicharmonic/2Disconnected graphs
Betweennessbetweenness/2Bridge/gatekeeper detection
PageRankpagerank/2Link-quality importance
HITShits/2Hub and authority scores
Eigenvectoreigenvector/2Influence based on neighbor importance
Katzkatz/2Attenuated influence with base score
Alphaalpha/2Directed graph influence

Centrality Visualization (Betweenness)

Betweenness centrality identifies "bridge" nodes that act as gatekeepers between different parts of a network.

graph G { bgcolor="transparent"; node [shape=circle, fontname="inherit"]; edge [fontname="inherit", fontsize=10]; // High betweenness node (Bridge/Broker) Broker [label="Broker", color="#6366f1", penwidth=2.5, style=bold]; // Community A Broker -- A1; Broker -- A2; Broker -- A3; A1 -- A2; A2 -- A3; A3 -- A1; // Community B Broker -- B1; Broker -- B2; Broker -- B3; B1 -- B2; B2 -- B3; B3 -- B1; }
iex> alias Yog.Centrality
iex> graph = Yog.from_edges(:undirected, [
...>   {"Broker", "A1", 1}, {"Broker", "A2", 1}, {"Broker", "A3", 1},
...>   {"A1", "A2", 1}, {"A2", "A3", 1}, {"A3", "A1", 1},
...>   {"Broker", "B1", 1}, {"Broker", "B2", 1}, {"Broker", "B3", 1},
...>   {"B1", "B2", 1}, {"B2", "B3", 1}, {"B3", "B1", 1}
...> ])
iex> scores = Centrality.betweenness(graph)
iex> # Broker should have the highest score
...> scores["Broker"] > scores["A1"]
true

Summary

Types

A mapping of Node IDs to their calculated centrality scores.

Specifies which edges to consider for directed graphs.

Functions

Calculates Alpha Centrality for all nodes.

Calculates Betweenness Centrality for all nodes.

Calculates Closeness Centrality for all nodes.

Calculates the Degree Centrality for all nodes in the graph.

Degree centrality with default options for undirected graphs. Uses :total_degree mode.

Calculates Eigenvector Centrality for all nodes.

Calculates Harmonic Centrality for all nodes.

Calculates HITS hub and authority scores for all nodes.

Calculates Katz Centrality for all nodes.

Calculates PageRank centrality for all nodes.

Types

centrality_scores()

@type centrality_scores() :: %{required(Yog.node_id()) => float()}

A mapping of Node IDs to their calculated centrality scores.

degree_mode()

@type degree_mode() :: :in_degree | :out_degree | :total_degree

Specifies which edges to consider for directed graphs.

  • :in_degree - Consider only incoming edges (Prestige)
  • :out_degree - Consider only outgoing edges (Gregariousness)
  • :total_degree - Consider both incoming and outgoing edges

Functions

alpha(graph, opts \\ [])

@spec alpha(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates Alpha Centrality for all nodes.

Alpha centrality is a generalization of Katz centrality for directed graphs. It measures the total number of paths from a node, weighted by path length with attenuation factor alpha.

Unlike Katz, alpha centrality does not include a constant beta term and is particularly useful for analyzing influence in directed networks.

Time Complexity: O(max_iterations * (V + E))

Interpreting Alpha Centrality

ValueMeaning
HighThe node has many paths from other central nodes
LowThe node is at the edge of the network with few incoming paths
0.0Isolated node — no incoming paths to accumulate influence

Unlike Katz, alpha centrality has no baseline beta term, so isolated nodes converge to 0.0 rather than retaining a minimum score.

Options

  • :alpha - Attenuation factor (typically 0.1-0.5)
  • :initial - Initial centrality value for all nodes
  • :max_iterations - Maximum number of iterations (default: 100)
  • :tolerance - Convergence threshold (default: 0.0001)

Example

iex> graph =
...>   Yog.directed()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {1, 3, 1}, {2, 3, 1}])
iex> scores = Yog.Centrality.alpha(graph, alpha: 0.3, initial: 1.0)
iex> map_size(scores)
3

betweenness(graph, opts \\ [])

@spec betweenness(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates Betweenness Centrality for all nodes.

Betweenness centrality of a node v is the sum of the fraction of all-pairs shortest paths that pass through v.

Time Complexity: O(VE) for unweighted, O(VE + V²logV) for weighted.

Interpreting Betweenness Centrality

ValueMeaning
HighThe node is a bridge or gatekeeper — many shortest paths go through it
LowThe node is peripheral — most paths bypass it
0.0The node lies on no shortest paths between any other pair

A high betweenness node is critical for network connectivity: removing it can fragment the graph or severely increase path lengths.

Options

  • :zero - The identity element for distances
  • :add - Function to add two distances
  • :compare - Function to compare two distances (returns :lt, :eq, or :gt)
  • :to_float - Function to convert distance type to Float

Example

iex> graph =
...>   Yog.directed()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}])
iex> scores = Yog.Centrality.betweenness(graph,
...>   zero: 0,
...>   add: &Kernel.+/2,
...>   compare: fn a, b ->
...>     cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...>   end,
...>   to_float: fn x -> x * 1.0 end
...> )
iex> # In a path 1->2->3, node 2 lies on the shortest path from 1 to 3
iex> scores[2] |> Float.round(3)
1.0

closeness(graph, opts \\ [])

@spec closeness(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates Closeness Centrality for all nodes.

Closeness centrality measures how close a node is to all other nodes in the graph. It is calculated as the reciprocal of the sum of the shortest path distances from the node to all other nodes.

Formula: C(v) = (n - 1) / Σ d(v, u) for all u ≠ v

Note: In disconnected graphs, nodes that cannot reach all other nodes will have a centrality of 0.0. Consider harmonic/2 for disconnected graphs.

Time Complexity: O(V * (V + E) log V) using Dijkstra from each node

Interpreting Closeness Centrality

ValueMeaning
1.0The node is one hop away from all others (e.g. center of a star)
0.5The node is typically 2 hops away from others
0.0The node cannot reach everyone (disconnected or isolated)

Options

  • :zero - The identity element for distances (e.g., 0 for integers)
  • :add - Function to add two distances
  • :compare - Function to compare two distances (returns :lt, :eq, or :gt)
  • :to_float - Function to convert distance type to Float for final score

Example

iex> graph =
...>   Yog.undirected()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}, {3, 1, 1}])
iex> scores = Yog.Centrality.closeness(graph,
...>   zero: 0,
...>   add: &Kernel.+/2,
...>   compare: fn a, b ->
...>     cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...>   end,
...>   to_float: fn x -> x * 1.0 end
...> )
iex> # In a triangle, all nodes have closeness 1.0
iex> scores[1] |> Float.round(3)
1.0

degree(graph, mode \\ :total_degree)

@spec degree(Yog.graph(), degree_mode()) :: centrality_scores()

Calculates the Degree Centrality for all nodes in the graph.

For directed graphs, use mode to specify which edges to count. For undirected graphs, the mode is ignored.

Interpreting Degree Centrality

ValueMeaning
1.0The node is connected to every other node (hub)
0.5The node is connected to half the other nodes
0.0Isolated node — no connections

Example

iex> graph =
...>   Yog.undirected()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}, {3, 1, 1}])
iex> scores = Yog.Centrality.degree(graph)
iex> # In a triangle, all nodes have degree 2, normalized is 2/2 = 1.0
iex> scores[1] |> Float.round(3)
1.0

iex> # Directed graph with different modes
iex> graph =
...>   Yog.directed()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}])
iex> # Out-degree: Node 1 has 1 outgoing edge
iex> scores = Yog.Centrality.degree(graph, :out_degree)
iex> scores[1] |> Float.round(3)
0.5

degree_total(graph)

@spec degree_total(Yog.graph()) :: centrality_scores()

Degree centrality with default options for undirected graphs. Uses :total_degree mode.

Same as degree(graph, :total_degree).

eigenvector(graph, opts \\ [])

@spec eigenvector(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates Eigenvector Centrality for all nodes.

Eigenvector centrality measures a node's influence based on the centrality of its neighbors. A node is important if it is connected to other important nodes. Uses power iteration to converge on the principal eigenvector.

Time Complexity: O(max_iterations * (V + E))

Interpreting Eigenvector Centrality

ValueMeaning
HighThe node is connected to other highly central nodes
LowThe node is connected to peripheral or unimportant nodes
0.0Isolated node with no connections

Eigenvector scores are normalized (L2 norm = 1.0), so they represent relative importance rather than absolute counts.

Options

  • :max_iterations - Maximum number of power iterations (default: 100)
  • :tolerance - Convergence threshold for L2 norm (default: 0.0001)

Example

iex> graph =
...>   Yog.undirected()
...>   |> Yog.add_node(1, "center")
...>   |> Yog.add_node(2, "A")
...>   |> Yog.add_node(3, "B")
...>   |> Yog.add_edges!([{1, 2, 1}, {1, 3, 1}])
iex> scores = Yog.Centrality.eigenvector(graph)
iex> scores[1] > scores[2]
true

harmonic(graph, opts \\ [])

@spec harmonic(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates Harmonic Centrality for all nodes.

Harmonic centrality is a variation of closeness centrality that handles disconnected graphs gracefully. It sums the reciprocals of the shortest path distances from a node to all reachable nodes.

Formula: H(v) = Σ (1 / d(v, u)) / (n - 1) for all u ≠ v

Time Complexity: O(V * (V + E) log V)

Interpreting Harmonic Centrality

ValueMeaning
1.0The node is directly connected to all others
0.5The node is directly connected to half the others
0.0Isolated node — cannot reach anyone else

Unlike closeness, disconnected nodes still receive credit for the neighbors they can reach rather than being penalized with 0.0.

Options

  • :zero - The identity element for distances
  • :add - Function to add two distances
  • :compare - Function to compare two distances (returns :lt, :eq, or :gt)
  • :to_float - Function to convert distance type to Float

Example

iex> graph =
...>   Yog.undirected()
...>   |> Yog.add_node(1, "Center")
...>   |> Yog.add_node(2, "A")
...>   |> Yog.add_node(3, "B")
...>   |> Yog.add_edges!([{1, 2, 1}, {1, 3, 1}])
iex> scores = Yog.Centrality.harmonic(graph,
...>   zero: 0,
...>   add: &Kernel.+/2,
...>   compare: fn a, b ->
...>     cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...>   end,
...>   to_float: fn x -> x * 1.0 end
...> )
iex> # Center node: (1/1 + 1/1) / 2 = 1.0
iex> scores[1] |> Float.round(3)
1.0

hits(graph, opts \\ [])

@spec hits(
  Yog.graph(),
  keyword()
) :: %{hubs: centrality_scores(), authorities: centrality_scores()}

Calculates HITS hub and authority scores for all nodes.

HITS identifies two types of important nodes in directed graphs:

  • Authorities — nodes with good content (pointed to by good hubs)
  • Hubs — nodes that point to good authorities

Unlike PageRank's single score, HITS provides dual scores using iterative mutual reinforcement. Authority scores are the sum of hub scores of predecessors; hub scores are the sum of authority scores of successors.

Time Complexity: O(max_iterations × (V + E))

Interpreting HITS

ScoreMeaning
High authorityMany high-quality incoming links
High hubPoints to many high-authority nodes
Both lowPeripheral node, neither cited nor curating

Options

  • :max_iterations - Maximum power iterations (default: 100)
  • :tolerance - Convergence threshold for L2 norm (default: 1.0e-6)

Example

iex> graph =
...>   Yog.directed()
...>   |> Yog.add_node(:a, nil)
...>   |> Yog.add_node(:b, nil)
...>   |> Yog.add_node(:c, nil)
...>   |> Yog.add_node(:d, nil)
...>   |> Yog.add_edges!([
...>     {:a, :b, 1}, {:a, :c, 1},
...>     {:b, :c, 1},
...>     {:d, :a, 1}, {:d, :b, 1}
...>   ])
iex> %{hubs: hubs, authorities: auths} = Yog.Centrality.hits(graph)
iex> # a is a strong hub (points to top authorities b and c)
...> hubs[:a] > hubs[:d]
true
iex> # b is the best authority (pointed to by multiple hubs)
...> auths[:b] > auths[:a]
true

katz(graph, opts \\ [])

@spec katz(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates Katz Centrality for all nodes.

Katz centrality is a variant of eigenvector centrality that adds an attenuation factor (alpha) to prevent the infinite accumulation of centrality in cycles. It also includes a constant term (beta) to give every node some base centrality.

Formula: C(v) = α * Σ C(u) + β for all neighbors u

Time Complexity: O(max_iterations * (V + E))

Interpreting Katz Centrality

ValueMeaning
HighThe node has many short paths to other important nodes
LowThe node is distant from the network core
≈ betaIsolated node — only receives the baseline score

Because of the constant beta term, even isolated nodes receive a non-zero score, making Katz more forgiving than eigenvector centrality.

Options

  • :alpha - Attenuation factor (must be < 1/largest_eigenvalue, typically 0.1-0.3)
  • :beta - Base centrality (typically 1.0)
  • :max_iterations - Maximum number of iterations (default: 100)
  • :tolerance - Convergence threshold (default: 0.0001)

Example

iex> graph =
...>   Yog.undirected()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}, {3, 1, 1}])
iex> scores = Yog.Centrality.katz(graph, alpha: 0.1, beta: 1.0)
iex> # All scores should be >= beta
iex> scores[1] >= 1.0
true

pagerank(graph, opts \\ [])

@spec pagerank(
  Yog.graph(),
  keyword()
) :: centrality_scores()

Calculates PageRank centrality for all nodes.

PageRank measures node importance based on the quality and quantity of incoming links. A node is important if it is linked to by other important nodes. Originally developed for ranking web pages, it's useful for:

  • Ranking nodes in directed networks
  • Identifying influential nodes in citation networks
  • Finding important entities in knowledge graphs
  • Recommendation systems

The algorithm uses a "random surfer" model: with probability damping, the surfer follows a random outgoing link; otherwise, they jump to any random node.

Time Complexity: O(max_iterations × (V + E))

Interpreting PageRank

ValueMeaning
HighThe node is linked to by many other important nodes
LowThe node has few or low-quality incoming links
1.0Single-node graph (trivial case)

PageRank scores always sum to 1.0 across all nodes. A node with rank 0.5 in a 2-node graph means it captures half the total importance in the network.

When to Use PageRank

  • Directed graphs where link direction matters
  • When you care about link quality (links from important nodes count more)
  • Citation networks, web graphs, recommendation systems

For undirected graphs, consider eigenvector/2 instead.

Options

  • :damping - Probability of continuing to follow links (default: 0.85)
  • :max_iterations - Maximum iterations before returning (default: 100)
  • :tolerance - Convergence threshold (default: 0.0001)

Example

iex> graph =
...>   Yog.directed()
...>   |> Yog.add_node(1, "A")
...>   |> Yog.add_node(2, "B")
...>   |> Yog.add_node(3, "C")
...>   |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}])
iex> scores = Yog.Centrality.pagerank(graph)
iex> # Scores should sum to approximately 1.0
iex> Enum.sum(Map.values(scores)) |> Float.round(2)
1.0
iex> # With custom options
iex> scores = Yog.Centrality.pagerank(graph, damping: 0.9, max_iterations: 50, tolerance: 0.001)
iex> map_size(scores)
3