Links: CS224W L02 (node embeddings, including spectral / Laplacian eigenmaps), L03–L04 (GNNs, the project's community task),
notebooks/project/retweet_thread.py §L02–04 (community prediction on the same world). Atlas: community detection;
social networks and information ecosystems.
Question
Community detection is the oldest structural tool of social network analysis, and moderation teams still use it first: "find the dense cluster, look inside". A coordinated ring is dense by construction, but it also points almost all of its retweets at a few popular accounts inside one political camp. Does modularity maximisation (and its spectral cousin) see a ring as its own community, or absorb it into the camp it imitates? And does the answer change when the ring has no internal ties at all (pure co-amplification)?
Hypotheses (written before running)
-
Absorption. At the default resolution (γ = 1), Louvain puts ring members in the same community as their ring's main target: at least 80% of members share it.
-
Resolution. For reciprocal rings there is a resolution in the sweep at which each ring becomes (almost) its own community: one community holds at least 70% of the ring and is at least 70% ring members. For co-amplification rings no resolution in the sweep does this.
-
Conductance. A reciprocal ring, taken as a node set, has much lower conductance than random same-size sets from its camp. A co-amplification ring has conductance close to 1 (no internal edges), no lower than the random sets.
-
Spectral localisation. Some of the lowest eigenvectors of the normalised Laplacian localise on reciprocal rings (at least 7 of an eigenvector's 10 largest entries in one ring). None do for co-amplification rings.
Setup
-
Worlds:
gl.retweet.make_world(seed, stealthy())(reciprocal rings) andstealthy(co_amplify=True); seeds inconfig.yaml. Graph: the undirected retweet graph, edge weight = number of retweets either way. -
Louvain (
networkx.community.louvain_communities) over a resolution sweep, several Louvain seeds. - Conductance of each ring vs. 200 random same-size sets from the ring's camp.
- The lowest
keigenvectors of the normalised Laplacian of the largest connected component.
Run: uv run marimo edit experiments/rings-in-communities/rings_in_communities.py and press the run button
(about two minutes). The notebook writes results.md and results.json.
Results (summary; full tables in results.md)
-
Hypothesis 1 refuted. At γ = 1 reciprocal rings are not absorbed: each one is its own Louvain community in every run (only 11% of members share their target's community). Modularity's merge test explains it: merging a ring into its camp raises Q only for γ below γ* = e(S,T)·2m/(d(S)d(T)), and γ* is 0.30–0.71 for every ring. A stealthy ring's ties to its camp are fewer than the null model expects. Co-amplification rings sit mostly with their camp (64%), short of the 80% threshold.
-
Hypothesis 2 supported. Reciprocal rings are isolated at almost every γ ≥ 1; co-amplification rings at most 26% of the time (γ = 20), though at high γ their modal community is 72% ring members: "ring plus targets" appears among ~400 small communities.
-
Hypothesis 3 supported. Reciprocal ring conductance 0.55 against 0.98 for random same-size sets; co-amplification rings 0.99 (no internal ties), and adding their targets (0.95) does not make a cluster.
-
Hypothesis 4 supported. In every reciprocal world, eigenvectors 4, 5 and 6 of the normalised Laplacian (right after the three community eigenvectors) each sit on one ring; an unsupervised localisation score gives AUC 0.997. Co-amplification worlds: no localised eigenvectors, AUC 0.62.
-
A trap. The flag "my community has low conductance" has AUC 0.000 at γ = 1 in reciprocal worlds, perfectly inverted: the camps are better clusters than the rings. It works (0.998) only once γ breaks the camps up.
Reading: reciprocal rings are the easy case for community detection, with no labels needed. Co-amplification rings defeat
it, which is consistent with rege-vs-wl experiment 3: what is left is a count over two hops, not a dense block.
Next steps
- Co-amplification rings with more targets (diluting the shared neighbourhood) and more camouflage.
- Bipartite (account–target) community detection or biclustering, the natural tool for "many accounts boost the same few".
- Local spectral methods (personalised PageRank sweep cuts, L04) from one known ring member.
Results
Generated by rings_in_communities.py from config.yaml (worlds [0, 1, 2], Louvain seeds
[0, 1, 2]). Means over worlds, Louvain seeds and rings.
Louvain resolution sweep
| world | γ | communities | share with main target | capture | purity | isolated | AUC (low-conductance community) |
|---|---|---|---|---|---|---|---|
| co-amplify | 0.5 | 5 | 0.67 | 0.68 | 0.03 | 0.00 | 0.518 |
| co-amplify | 1.0 | 5 | 0.64 | 0.66 | 0.04 | 0.00 | 0.443 |
| co-amplify | 2.0 | 17 | 0.81 | 0.86 | 0.32 | 0.00 | 0.189 |
| co-amplify | 5.0 | 121 | 0.87 | 0.87 | 0.51 | 0.04 | 0.514 |
| co-amplify | 10.0 | 241 | 0.63 | 0.71 | 0.61 | 0.22 | 0.557 |
| co-amplify | 20.0 | 412 | 0.44 | 0.56 | 0.72 | 0.26 | 0.558 |
| reciprocal | 0.5 | 5 | 0.78 | 1.00 | 0.15 | 0.11 | 0.433 |
| reciprocal | 1.0 | 8 | 0.11 | 1.00 | 0.97 | 1.00 | 0.000 |
| reciprocal | 2.0 | 19 | 0.74 | 1.00 | 0.75 | 0.78 | 0.296 |
| reciprocal | 5.0 | 126 | 1.00 | 1.00 | 0.82 | 0.96 | 0.997 |
| reciprocal | 10.0 | 250 | 1.00 | 1.00 | 0.88 | 0.96 | 0.998 |
| reciprocal | 20.0 | 416 | 0.85 | 1.00 | 0.91 | 1.00 | 0.998 |
Conductance
| world | ring | ring + targets | random same-size sets (mean) | random (5th pct.) |
|---|---|---|---|---|
| co-amplify | 0.993 | 0.953 | 0.983 | 0.966 |
| reciprocal | 0.552 | 0.644 | 0.982 | 0.966 |
Spectral localisation
| world | seed | localised eigenvectors | rings covered | indices | AUC (localisation score) |
|---|---|---|---|---|---|
| co-amplify | 0 | 0 | 0 | 0.635 | |
| co-amplify | 1 | 0 | 0 | 0.608 | |
| co-amplify | 2 | 0 | 0 | 0.625 | |
| reciprocal | 0 | 3 | 3 | 4, 5, 6 | 0.999 |
| reciprocal | 1 | 3 | 3 | 4, 5, 6 | 0.996 |
| reciprocal | 2 | 3 | 3 | 4, 5, 6 | 0.997 |
Modularity merge test at γ = 1 (reciprocal rings): critical γ* = e(S,T)·2m/(d(S)d(T)) from 0.30 to 0.71; a ring is absorbed only for γ < γ*.
Verdicts
-
Absorption at γ = 1: refuted (reciprocal, 0.11), refuted (co-amplify, 0.64).
-
Resolution isolates reciprocal but not co-amplification rings: supported (best: 1.00 vs 0.26).
-
Conductance: supported.
- Spectral localisation: supported.
In the repository: experiments/rings-in-communities/ · README.md config.yaml results.json results.md rings_in_communities.py