Community detection and clustering algorithms.
Provides types and utility functions for working with community structures in graphs. Community detection algorithms identify groups of nodes that are more densely connected internally than with the rest of the graph.
Algorithms
| Algorithm | Module | Best For |
|---|---|---|
| Louvain | Yog.Community.Louvain | Large graphs, modularity optimization |
| Leiden | Yog.Community.Leiden | Quality guarantee, well-connected communities |
| Label Propagation | Yog.Community.LabelPropagation | Speed, near-linear time |
| Girvan-Newman | Yog.Community.GirvanNewman | Hierarchical structure, edge betweenness |
| Infomap | Yog.Community.Infomap | Information-theoretic, flow-based |
| Clique Percolation | Yog.Community.CliquePercolation | Overlapping communities |
| Walktrap | Yog.Community.Walktrap | Random walk-based distances |
| Local Community | Yog.Community.LocalCommunity | Massive graphs, seed expansion |
| Fluid Communities | Yog.Community.FluidCommunities | Exact k partitions, fast |
Core Types
Communities- Community assignment mapping nodes to community IDsDendrogram- Hierarchical community structure with multiple levelsCommunityId- Integer identifier for a community
Performance Notes
For frequent community-level queries, use to_dict/1 to convert the
assignments map to a community-centric structure (community_id -> nodes).
Functions like sizes/1, nodes_in/2, and largest/1 perform O(V) scans;
to_dict/1 performs a single O(V) conversion that enables O(1) lookups for all subsequent community operations.
Community Structure Visualization
Communities are groups of nodes with dense internal connections and sparse connections to other groups.
iex> alias Yog.Community
iex> graph = Yog.from_edges(:undirected, [
...> {1, 2, 1}, {2, 3, 1}, {3, 1, 1},
...> {4, 5, 1}, {5, 6, 1}, {6, 4, 1},
...> {3, 4, 1}
...> ])
iex> communities = Community.Result.new(%{1 => 0, 2 => 0, 3 => 0, 4 => 1, 5 => 1, 6 => 1})
iex> Community.modularity(graph, communities) > 0.3
iex> Community.modularity(graph, communities) > 0.3
trueOverlapping Communities
In overlapping community detection, nodes can be members of multiple groups.
Hierarchical Structure (Dendrogram)
Algorithms like Louvain and Girvan-Newman produce hierarchical results showing how nodes merge into larger functional units.
Examples
# Create a graph with clear community structure (two cliques connected by a bridge)
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_node(4, nil)
...> |> Yog.add_node(5, nil)
...> |> Yog.add_node(6, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
...> |> Yog.add_edge_ensure(from: 2, to: 3, with: 1)
...> |> Yog.add_edge_ensure(from: 1, to: 3, with: 1) # Triangle: 1-2-3
...> |> Yog.add_edge_ensure(from: 4, to: 5, with: 1)
...> |> Yog.add_edge_ensure(from: 5, to: 6, with: 1)
...> |> Yog.add_edge_ensure(from: 4, to: 6, with: 1) # Triangle: 4-5-6
...> |> Yog.add_edge_ensure(from: 3, to: 4, with: 1) # Bridge between communities
iex> communities = Yog.Community.Louvain.detect(graph)
iex> communities.num_communities >= 2
true
iex> communities_dict = Yog.Community.to_dict(communities)
iex> map_size(communities_dict) >= 2
true
iex> {:ok, _community_id} = Yog.Community.largest(communities)
iex> true
trueChoosing an Algorithm
- Louvain: Fast and widely used, good for most cases
- Leiden: Better quality than Louvain, guarantees well-connected communities
- Label Propagation: Fastest option for very large graphs
- Girvan-Newman: When you need hierarchical structure
- Infomap: When flow/random walk structure matters
- Clique Percolation: When nodes may belong to multiple communities
- Walktrap: Good for capturing local structure via random walks
- Local Community: When the graph is massive/infinite and you only care about the immediate community around specific seeds
- Fluid Communities: Fast and allows finding exactly
kcommunities
Summary
Types
Community assignment for nodes
Community identifier
Hierarchical community structure
Overlapping communities (nodes can belong to multiple)
Functions
Calculates the average clustering coefficient for the entire graph.
Calculates the average density across all communities.
Calculates the local clustering coefficient for a node.
Calculates the density of edges within a specific community.
Counts the total number of triangles in the graph.
Calculates the graph density.
Returns the community ID for a specific node.
Returns the community ID with the largest number of nodes.
Merges two communities into one.
Calculates modularity for a given community partition.
Returns all nodes belonging to a specific community.
Returns a dictionary mapping community IDs to their sizes (number of nodes).
Converts community assignments to a dictionary mapping community IDs to sets of node IDs.
Returns the number of triangles each node participates in.
Types
@type communities() :: Yog.Community.Result.t()
Community assignment for nodes
@type community_id() :: integer()
Community identifier
@type dendrogram() :: Yog.Community.Dendrogram.t()
Hierarchical community structure
@type overlapping_communities() :: Yog.Community.Overlapping.t()
Overlapping communities (nodes can belong to multiple)
Functions
Calculates the average clustering coefficient for the entire graph.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
...> |> Yog.add_edge_ensure(from: 2, to: 3, with: 1)
...> |> Yog.add_edge_ensure(from: 3, to: 1, with: 1)
iex> Yog.Community.average_clustering_coefficient(graph)
1.0
@spec average_community_density(Yog.graph(), communities()) :: float()
Calculates the average density across all communities.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 0})
iex> avg_cd = Yog.Community.average_community_density(graph, communities)
iex> is_float(avg_cd)
true
@spec clustering_coefficient(Yog.graph(), Yog.node_id()) :: float()
Calculates the local clustering coefficient for a node.
Measures how close the node's neighbors are to forming a complete clique.
Range: [0.0, 1.0]. 1.0 means all neighbors are connected to each other.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
...> |> Yog.add_edge_ensure(from: 2, to: 3, with: 1)
...> |> Yog.add_edge_ensure(from: 3, to: 1, with: 1)
iex> Yog.Community.clustering_coefficient(graph, 1)
1.0
@spec community_density(Yog.graph(), communities(), community_id()) :: float()
Calculates the density of edges within a specific community.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 0})
iex> cd = Yog.Community.community_density(graph, communities, 0)
iex> is_float(cd)
true
Counts the total number of triangles in the graph.
A triangle is a set of three nodes where each pair is connected.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
...> |> Yog.add_edge_ensure(from: 2, to: 3, with: 1)
...> |> Yog.add_edge_ensure(from: 3, to: 1, with: 1)
iex> Yog.Community.count_triangles(graph)
1
Calculates the graph density.
The ratio of actual edges to possible edges.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
iex> d = Yog.Community.density(graph)
iex> is_float(d)
true
@spec for_node(communities(), Yog.node_id()) :: {:ok, community_id()} | :error
Returns the community ID for a specific node.
Returns :error if the node is not assigned to any community.
Examples
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 1})
iex> Yog.Community.for_node(communities, 1)
{:ok, 0}
iex> Yog.Community.for_node(communities, 999)
:error
@spec largest(communities()) :: {:ok, community_id()} | :error
Returns the community ID with the largest number of nodes.
Returns :error if there are no communities (empty graph or no assignments).
Performance
This function performs a single-pass O(V) reduction. For repeated queries,
consider using to_dict/1 first.
Examples
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 0, 4 => 1})
iex> Yog.Community.largest(communities)
{:ok, 0}
@spec merge(communities() | map(), source: community_id(), target: community_id()) :: communities() | map()
Merges two communities into one.
All nodes from the source community are reassigned to the target community. The source community ID is effectively removed.
Parameters
communities- The current community partition (can be Result struct or legacy map)source- The community ID to merge from (will be removed)target- The community ID to merge into (will be kept)
Examples
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 1, 4 => 1})
iex> merged = Yog.Community.merge(communities, source: 1, target: 0)
iex> merged.assignments
%{1 => 0, 2 => 0, 3 => 0, 4 => 0}
iex> merged.num_communities
1
Calculates modularity for a given community partition.
Modularity measures the quality of a division of a network into modules (communities). High modularity indicates that the community structure captures significant structural patterns in the graph.
Range: [-0.5, 1.0]. Values > 0.3 indicate significant community structure.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
...> |> Yog.add_edge_ensure(from: 2, to: 3, with: 1)
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 0})
iex> q = Yog.Community.modularity(graph, communities)
iex> is_float(q)
true
@spec nodes_in(communities(), community_id()) :: MapSet.t(Yog.node_id())
Returns all nodes belonging to a specific community.
Performance
This function performs an O(V) scan. For repeated queries or checking
multiple communities, use to_dict/1 first for O(1) lookups.
Examples
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 0, 4 => 1})
iex> Yog.Community.nodes_in(communities, 0)
MapSet.new([1, 2, 3])
@spec sizes(communities()) :: %{required(community_id()) => integer()}
Returns a dictionary mapping community IDs to their sizes (number of nodes).
Performance
This function performs a single O(V) scan. For repeated queries,
consider using to_dict/1 first.
Examples
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 1, 4 => 1, 5 => 1})
iex> Yog.Community.sizes(communities)
%{0 => 2, 1 => 3}
@spec to_dict(communities()) :: %{required(community_id()) => MapSet.t(Yog.node_id())}
Converts community assignments to a dictionary mapping community IDs to sets of node IDs.
This is useful when you need to iterate over all nodes in each community rather than looking up the community for each node. This performs a single O(V) scan and produces a structure optimized for community-level queries.
Performance Tip: Use this function before making multiple community-level queries. Functions like
sizes/1,nodes_in/2, andlargest/1each perform O(V) scans. Converting once withto_dict/1enables O(1) lookups thereafter.
Examples
iex> communities = Yog.Community.Result.new(%{1 => 0, 2 => 0, 3 => 1})
iex> Yog.Community.to_dict(communities)
%{0 => MapSet.new([1, 2]), 1 => MapSet.new([3])}
@spec triangles_per_node(Yog.graph()) :: %{required(Yog.node_id()) => integer()}
Returns the number of triangles each node participates in.
Examples
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 1)
...> |> Yog.add_edge_ensure(from: 2, to: 3, with: 1)
...> |> Yog.add_edge_ensure(from: 3, to: 1, with: 1)
iex> Yog.Community.triangles_per_node(graph)
%{1 => 1, 2 => 1, 3 => 1}