OriginChainDB docs
examples · graph · 10 / 17

10. louvain - modularity community detection

← Graph examples

what this does

louvain runs two-phase modularity-greedy community detection over one table's relation graph. GET /v1/tenants/:t/graph/:schema/louvain returns a communities envelope with one entry per row: the node's primary key and the integer id of the community it settled into. Where components only separates nodes that cannot reach each other at all, louvain finds the densely connected groups inside a single connected island.

when to use it

  • Segmenting an interaction graph into groups that genuinely talk to each other, rather than groups that merely happen to be reachable.
  • Topic clustering over a citation or co-occurrence graph, where the grouping should come from the edges rather than from a label you already assigned.
  • A first pass before an expensive per-group model: run louvain, then treat each community as an independent unit of work.

schema requirement

The relation must be declared in the schema's [[relations]] block and must target the table it is declared on. louvain refuses a relation that points at another table, and the error names both tables. See schemas/reference#relations.

the request

rel is the only required parameter. tolerance (default 1e-4) is the modularity delta below which the outer aggregation loop stops, and max_levels (default 10) caps that loop. Real graphs converge in a handful of levels - the cap is a guard against pathological input, not a knob you normally touch.

GET /v1/tenants/:t/graph/:schema/louvain?rel=...
curl -G "https://$OC_HOST/v1/tenants/$OC_TENANT/graph/social.users/louvain" \
  --data-urlencode "rel=follows" \
  --data-urlencode "tolerance=1e-4" \
  --data-urlencode "max_levels=10" \
  -H "Authorization: Bearer $OC_TOKEN"

what you get back

{
  "communities": [
    { "pk": "alice",   "community": 0 },
    { "pk": "bob",     "community": 0 },
    { "pk": "carol",   "community": 0 },
    { "pk": "dave",    "community": 0 },
    { "pk": "heidi",   "community": 1 },
    { "pk": "ivan",    "community": 1 },
    { "pk": "judy",    "community": 1 },
    { "pk": "mallory", "community": 2 }
  ]
}

An object with a single communities array - not a bare array, and not the row shape the other algorithm endpoints use. Here pk is a plain string and community is a dense integer in the range zero to k. Ids are assigned in order of first appearance by lexicographic primary key, so the lexicographically smallest key is always in community 0 and a rerun over an unchanged graph produces the same numbering.

how it works

  • Phase one moves each node into whichever neighbouring community gives the largest modularity gain. Phase two collapses each community into a single node and repeats.
  • The loop stops when the modularity delta between levels falls below tolerance, when phase one stops moving nodes, or when max_levels is reached.
  • Community ids are dense and deterministic, so two runs over the same graph are directly comparable and can be stored as-is.
  • The endpoint refuses graphs above its 500,000-node ceiling. The per-level pass is linear in nodes plus edges, and a multi-million-node call will not land inside a request budget.

common mistakes

  • Reading the response as an array. It is { "communities": [ ... ] }. Indexing the body directly gets you nothing.
  • Expecting pk to be an array here. louvain returns a plain string, while components, betweenness, label_propagation and eigenvector_centrality all return a primary-key array. The response grammar genuinely differs per endpoint - write the parser per endpoint.
  • Comparing community ids across graphs. The numbering comes from this graph's own key order. Ids from a different graph, or from the same graph after new rows land, are not comparable.