Network health and structural quality metrics.
These metrics measure the overall "health" and structural properties of your graph, including size, compactness, and connectivity patterns.
Overview
| Metric | Function | Measures |
|---|---|---|
| Diameter | diameter/2 | Maximum distance (worst-case reachability) |
| Radius | radius/2 | Minimum eccentricity (best central point) |
| Eccentricity | eccentricity/3 | Maximum distance from a node |
| Assortativity | assortativity/1 | Degree correlation (homophily) |
| Average Path Length | average_path_length/2 | Typical separation |
Example
# Check graph compactness
diam = Yog.Health.diameter(graph, opts)
rad = Yog.Health.radius(graph, opts)
# Small diameter = well-connected
# High assortativity = nodes cluster with similar nodes
assort = Yog.Health.assortativity(graph)
Summary
Types
Options for health metrics that operate on weighted graphs.
A metric value returned by health functions. May be nil for
disconnected or empty graphs.
Functions
Assortativity coefficient measures degree correlation.
Average local efficiency over all nodes.
Average shortest path length across all node pairs.
Returns nil if the graph is disconnected or empty.
Requires a function to convert edge weights to Float for averaging.
The diameter is the maximum eccentricity (longest shortest path).
Returns nil if the graph is disconnected or empty.
Eccentricity is the maximum distance from a node to all other nodes.
Returns nil if the node cannot reach all other nodes.
Efficiency between two nodes.
Global efficiency of the graph.
Local efficiency of a single node.
The radius is the minimum eccentricity.
Returns nil if the graph is disconnected or empty.
Types
Functions
Assortativity coefficient measures degree correlation.
Time Complexity: O(V+E)
Interpreting Assortativity
| Value | Meaning |
|---|---|
| Positive | High-degree nodes preferentially connect to other high-degree nodes (assortative) |
| Negative | High-degree nodes connect to low-degree nodes (disassortative) |
| Zero | Random mixing, or all nodes have the same degree (regular graph) |
Common real-world patterns:
- Social networks tend to be assortative (people with many friends know each other)
- Biological and technological networks tend to be disassortative (hubs serve many leaves)
Example
iex> # Star graph is disassortative (center with high degree connects to leaves with low degree)
iex> graph =
...> Yog.undirected()
...> |> Yog.add_node(1, "center")
...> |> Yog.add_node(2, "A")
...> |> Yog.add_node(3, "B")
...> |> Yog.add_node(4, "C")
...> |> Yog.add_edges!([{1, 2, 1}, {1, 3, 1}, {1, 4, 1}])
iex> assort = Yog.Health.assortativity(graph)
iex> assort < 0.0
true
iex> # Empty graph returns 0.0
iex> Yog.Health.assortativity(Yog.undirected())
0.0
@spec average_local_efficiency(Yog.graph(), metric_opts()) :: float()
Average local efficiency over all nodes.
This is the mean of local_efficiency/3 for every node in the graph.
Time Complexity: O(V × d² × (d+E') log d)
Example
iex> graph = Yog.undirected() |> Yog.add_node(1, nil) |> Yog.add_node(2, nil) |> Yog.add_node(3, nil) |> Yog.add_edges!([{1, 2, 1}, {1, 3, 1}, {2, 3, 1}])
iex> opts = [with_zero: 0, with_add: &Kernel.+/2, with_compare: &Yog.Utils.compare/2, with: &Function.identity/1, with_to_float: fn x -> x * 1.0 end]
iex> Yog.Health.average_local_efficiency(graph, opts)
1.0
@spec average_path_length(Yog.graph(), metric_opts()) :: float() | nil
Average shortest path length across all node pairs.
Returns nil if the graph is disconnected or empty.
Requires a function to convert edge weights to Float for averaging.
Time Complexity: O(V × (V+E) log V)
Options
:with_zero- The zero value for the weight type:with_add- Function to add two weights:with_compare- Function comparing two weights returning:lt,:eq, or:gt:with- Function to extract/transform edge weight:with_to_float- Function to convert weight to float
Interpreting Average Path Length
| Value | Meaning |
|---|---|
≈ 1.0 | Dense or highly connected graph (e.g., complete graph) |
≈ 2.0 | Star-like or small-world structure |
≈ V/3 | Chain-like or path-like topology |
nil | Disconnected or empty graph |
A low APL relative to the number of nodes indicates a small-world structure: the graph achieves global connectivity through a small number of hops.
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> opts = [
...> with_zero: 0,
...> with_add: &Kernel.+/2,
...> with_compare: fn a, b ->
...> cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...> end,
...> with: &Function.identity/1,
...> with_to_float: fn x -> x * 1.0 end
...> ]
iex> avg = Yog.Health.average_path_length(graph, opts)
iex> # In triangle, average path length is 1.0
iex> abs(avg - 1.0) < 0.001
true
@spec diameter(Yog.graph(), metric_opts()) :: metric_value()
The diameter is the maximum eccentricity (longest shortest path).
Returns nil if the graph is disconnected or empty.
Time Complexity: O(V × (V+E) log V)
Options
:with_zero- The zero value for the weight type (e.g.,0for integers):with_add- Function to add two weights (e.g.,&Kernel.+/2):with_compare- Function comparing two weights returning:lt,:eq, or:gt:with- Function to extract/transform edge weight
Interpreting Diameter
| Value | Meaning |
|---|---|
1 | Complete graph — everyone is directly connected |
2 | Small world — at most one hop between any pair |
> log(V) | Relatively sparse or stretched topology |
nil | Disconnected or empty graph |
Example
iex> graph =
...> Yog.undirected()
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_node(3, "C")
...> |> Yog.add_node(4, "D")
...> |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}, {3, 4, 1}])
iex> opts = [
...> with_zero: 0,
...> with_add: &Kernel.+/2,
...> with_compare: fn a, b ->
...> cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...> end,
...> with: &Function.identity/1
...> ]
iex> Yog.Health.diameter(graph, opts)
3
@spec eccentricity(Yog.graph(), Yog.node_id(), metric_opts()) :: metric_value()
Eccentricity is the maximum distance from a node to all other nodes.
Returns nil if the node cannot reach all other nodes.
Time Complexity: O((V+E) log V)
Options
:with_zero- The zero value for the weight type:with_add- Function to add two weights:with_compare- Function comparing two weights returning:lt,:eq, or:gt:with- Function to extract/transform edge weight
Interpreting Eccentricity
| Value | Meaning |
|---|---|
= radius | The node is in the graph center |
= diameter | The node is on the periphery (worst-case reachability) |
1 | The node is adjacent to every other node |
0 | Single-node graph |
nil | The node cannot reach all others (disconnected component) |
Example
iex> graph =
...> Yog.undirected()
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_node(3, "C")
...> |> Yog.add_node(4, "D")
...> |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}, {3, 4, 1}])
iex> opts = [
...> with_zero: 0,
...> with_add: &Kernel.+/2,
...> with_compare: fn a, b ->
...> cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...> end,
...> with: &Function.identity/1
...> ]
iex> # End nodes have eccentricity 3
iex> Yog.Health.eccentricity(graph, 1, opts)
3
iex> # Middle nodes have eccentricity 2
iex> Yog.Health.eccentricity(graph, 2, opts)
2
@spec efficiency(Yog.graph(), Yog.node_id(), Yog.node_id(), metric_opts()) :: float()
Efficiency between two nodes.
The efficiency is the inverse of the shortest path distance. If no path exists, returns 0.0. A node's efficiency to itself is 0.0.
Time Complexity: O((V+E) log V)
Example
iex> graph = Yog.undirected() |> Yog.add_node(1, nil) |> Yog.add_node(2, nil) |> Yog.add_node(3, nil) |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}])
iex> opts = [with_zero: 0, with_add: &Kernel.+/2, with_compare: &Yog.Utils.compare/2, with: &Function.identity/1, with_to_float: fn x -> x * 1.0 end]
iex> Yog.Health.efficiency(graph, 1, 3, opts)
0.5
iex> Yog.Health.efficiency(graph, 1, 1, opts)
0.0
@spec global_efficiency(Yog.graph(), metric_opts()) :: float()
Global efficiency of the graph.
The average efficiency over all ordered pairs of distinct nodes. Unlike average path length, this is well-defined for disconnected graphs: unreachable pairs simply contribute 0.0.
Time Complexity: O(V × (V+E) log V)
Example
iex> graph = Yog.undirected() |> Yog.add_node(1, nil) |> Yog.add_node(2, nil) |> Yog.add_node(3, nil) |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}, {3, 1, 1}])
iex> opts = [with_zero: 0, with_add: &Kernel.+/2, with_compare: &Yog.Utils.compare/2, with: &Function.identity/1, with_to_float: fn x -> x * 1.0 end]
iex> Yog.Health.global_efficiency(graph, opts)
1.0
@spec local_efficiency(Yog.graph(), Yog.node_id(), metric_opts()) :: float()
Local efficiency of a single node.
Computes the global efficiency of the subgraph induced by the node's neighbors. For directed graphs, the neighborhood includes both successors and predecessors.
Returns 0.0 if the node has fewer than 2 neighbors.
Time Complexity: O(d² × (d+E') log d) where d is the node degree and E' is the number of edges in the neighborhood subgraph.
Example
iex> graph = Yog.undirected() |> Yog.add_node(1, nil) |> Yog.add_node(2, nil) |> Yog.add_node(3, nil) |> Yog.add_edges!([{1, 2, 1}, {1, 3, 1}, {2, 3, 1}])
iex> opts = [with_zero: 0, with_add: &Kernel.+/2, with_compare: &Yog.Utils.compare/2, with: &Function.identity/1, with_to_float: fn x -> x * 1.0 end]
iex> Yog.Health.local_efficiency(graph, 1, opts)
1.0
@spec radius(Yog.graph(), metric_opts()) :: metric_value()
The radius is the minimum eccentricity.
Returns nil if the graph is disconnected or empty.
Time Complexity: O(V × (V+E) log V)
Options
:with_zero- The zero value for the weight type:with_add- Function to add two weights:with_compare- Function comparing two weights returning:lt,:eq, or:gt:with- Function to extract/transform edge weight
Interpreting Radius
| Value | Meaning |
|---|---|
= diameter | Highly symmetric structure (e.g., cycle, complete graph) |
<< diameter | Centralized topology with a clear hub (e.g., star) |
1 | There exists a central node one hop from everyone else |
nil | Disconnected or empty graph |
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> opts = [
...> with_zero: 0,
...> with_add: &Kernel.+/2,
...> with_compare: fn a, b ->
...> cond do a < b -> :lt; a > b -> :gt; true -> :eq end
...> end,
...> with: &Function.identity/1
...> ]
iex> Yog.Health.radius(graph, opts)
1