Links: CS224W L20 (classic graph ML at scale: label propagation), L21 (subgraphs and motifs, null models),
notebooks/project/retweet_thread.py §L20–21 (label propagation and motif Z-scores on the same world). Atlas: motifs;
coordinated inauthentic behaviour.
Question
The project notebook found reciprocal dyads and mutual triangles over-represented, mostly inside rings. But rings can be built without reciprocity (co-amplification), and real coordination research uses a different family of patterns: co-retweet and synchronised co-retweet networks (two accounts repeatedly amplifying the same posts, possibly within minutes; Pacheco et al. 2021, Weber & Neumann 2021). Which per-account pattern separates which kind of ring, does timing add anything once bots retweet with human-like delays, and which graph should label propagation run on?
Hypotheses (written before running)
-
Reciprocity motifs are ring-specific. For reciprocal rings, reciprocal dyads and mutual triangles are the best single patterns (AUC ≥ 0.9); for co-amplification rings they are at most 0.6.
-
Co-retweet motifs catch co-amplification. For co-amplification rings, the best single pattern is a co-retweet pattern (repeated co-retweets of the same posts with one partner, at any time or synchronised), with AUC ≥ 0.8.
-
Timing helps only naive bots. Synchronised co-retweets (within 1 hour) beat the same pattern without a time window in naive worlds, but not in stealthy worlds (reciprocal and co-amplification), where bot delays are human-like.
-
Propagate on the right graph. Label propagation from 3 known ring members and 30 known humans works better on the graph built from the world's best pattern than on the plain undirected retweet graph, in every world type.
Setup
- Worlds: naive, reciprocal and co-amplification rings, seeds in
config.yaml. -
Per-account patterns (no labels): in- and out-degree; reciprocal dyads; mutual triangles; transitive (feed-forward) triads; repeated co-retweets (the most posts co-retweeted with any single partner, and the number of partners with at least 3 shared posts); the same two within a 1-hour and a 6-hour window (synchronised).
-
Each pattern's AUC as an unsupervised score; all patterns together in a logistic regression (5-fold CV).
- Label propagation (α = 0.9) on the undirected, mutual, co-retweet and synchronised co-retweet graphs, 10 random seed sets.
- Global motif Z-scores against a degree-preserving null for the static patterns, and against a time-shuffled null (each post's retweet times permuted among its retweeters) for synchrony.
Run: uv run marimo edit experiments/motif-signatures/motif_signatures.py and press the run button.
Results (summary; full tables in results.md)
| pattern (AUC as an unsupervised ring score) | naive | reciprocal | co-amplify |
|---|---|---|---|
| mutual triangles | 1.000 | 0.808 | 0.474 |
| reciprocal dyads | 0.984 | 0.728 | 0.417 |
| co-retweet: most posts shared with one partner | 1.000 | 0.728 | 0.810 |
| synchronised (1 h): most posts shared with one partner | 1.000 | 0.286 | 0.469 |
| all patterns, logistic regression | 1.000 | 0.999 | 0.996 |
-
H1 refuted. Mutual triangles are the best single pattern for reciprocal rings, but only 0.81: many members of a stealthy ring have no mutual tie. As predicted they are useless for co-amplification rings (0.47).
-
H2 supported. For co-amplification rings the best pattern is "the most posts co-retweeted with any single partner" (0.81): repetition with the same partner, not the number of co-retweets, which mostly measures popularity.
-
H3 refuted. Timing ties with the any-time pattern for naive bots (both 1.000) and points the wrong way for stealthy ones (0.29 and 0.47): with 20-hour delays ring members are less synchronised than humans in cascades.
-
H4 refuted. Propagating on the best pattern's graph does not beat the plain graph everywhere (the mutual graph lacks reach). But the synchronised 6-hour graph is the best propagation graph in both stealthy worlds (0.961 and 0.762, against 0.905 and 0.584 on the plain graph).
-
The synchrony null fails as a coordination test. Repeated synchronised pairs exceed a time-shuffled null with Z ≈ 11–13 in every world, but in stealthy worlds 0–2 of those pairs are inside rings. Organic cascades produce the excess; two quick checks did not explain how (see the notebook for an untested candidate). A synchrony test needs a null that keeps cascade structure.
Next steps
- A cascade-preserving null for synchrony: re-simulate the independent cascade on the observed follow graph.
- Temporal motifs in the sense of Paranjape et al. (2017): δ-temporal motif counts per account.
- Weighted propagation on U + λ·C (plain reach plus co-retweet homophily), sweeping λ.
Results
Generated by motif_signatures.py from config.yaml (worlds [0, 1, 2]). Means over worlds.
Single-pattern AUC (unsupervised score):
| pattern | family | naive | reciprocal | co-amplify |
|---|---|---|---|---|
| in-degree | degree | 0.553 | 0.400 | 0.480 |
| out-degree | degree | 0.351 | 0.465 | 0.341 |
| reciprocal dyads | reciprocity | 0.984 | 0.728 | 0.417 |
| mutual triangles | reciprocity | 1.000 | 0.808 | 0.474 |
| transitive triads | transitivity | 0.788 | 0.539 | 0.224 |
| co-retweet: max with one partner | co-retweet | 1.000 | 0.728 | 0.810 |
| co-retweet: partners ≥ 3 | co-retweet | 0.399 | 0.239 | 0.410 |
| sync 1 h: max with one partner | sync 1 h | 1.000 | 0.286 | 0.469 |
| sync 1 h: partners ≥ 3 | sync 1 h | 1.000 | 0.406 | 0.460 |
| sync 6 h: max with one partner | sync 6 h | 1.000 | 0.436 | 0.595 |
| sync 6 h: partners ≥ 3 | sync 6 h | 0.634 | 0.368 | 0.513 |
| all patterns (logistic regression, CV) | 1.000 | 0.999 | 0.996 |
Label propagation (3 known ring members, 30 known humans), AUC:
| graph | naive | reciprocal | co-amplify |
|---|---|---|---|
| undirected | 0.981 | 0.905 | 0.584 |
| mutual | 0.899 | 0.775 | 0.574 |
| co-retweet | 0.754 | 0.947 | 0.719 |
| sync 1 h | 0.922 | 0.932 | 0.717 |
| sync 6 h | 0.877 | 0.961 | 0.762 |
Synchronised co-retweet pairs (≥ 3 posts within 1 h) vs a time-shuffled null:
| world | observed | null | inside rings |
|---|---|---|---|
| co-amplify | 178 | 93.3 ± 7.1 | 2 |
| naive | 375 | 261.3 ± 10.7 | 135 |
| reciprocal | 208 | 112.9 ± 7.2 | 0 |
Verdicts: H1 refuted, H2 supported, H3 refuted, H4 refuted.
In the repository: experiments/motif-signatures/ · README.md config.yaml motif_signatures.py results.json results.md