5D Library › Social Network Topology, Structural Holes and Complex Contagion · 中文
Chapter 8 Practice and a Pre-registered Trial
Note
The checklist in this chapter needs relational data, and relational data involve third parties. Recording “who talks to whom” in a team also records other people’s relationships. The checklist below may be used only if everyone concerned knows about it and agrees, the data are reported only in aggregate, and they are never used in anyone’s performance appraisal or in decisions about whether anyone stays or goes. This chapter neither offers nor encourages any way of manipulating other people’s relationships.
Part 1 A four-step topology checklist
This checklist is for a team that, with the agreement of all its members, wants to examine its own information structure. Its aim is to let the team see what it may be missing, not to score anyone.
- Constraint and homophily in the core circle. Do the few people who most often take decisions together have contacts who all know one another (high network constraint, Chapter 2)? Are their ties almost all within one department or one background? The latter can be measured with Krackhardt and Stern’s E–I index, (external ties − internal ties) / (external ties + internal ties), where −1 means all ties are internal (Krackhardt and Stern 1988). Homophily is one of the most pervasive regularities of social networks, and it limits the information a group encounters (McPherson, Smith-Lovin, and Cook 2001).
- Reactivating weak ties across boundaries. Which old relationships of team members have long been idle but lead to other departments or fields? Research has found that reconnecting with such “dormant” ties can bring more novel information than current ties for relatively little effort (Levin, Walter, and Murnighan 2011). This step only lists possibilities; whether to get in touch is entirely up to the people concerned.
- Wide bridges for costly behaviour. If the team wants a demanding new practice to cross into another group, it should not expect one person to “ignite” it: Chapter 3 showed that behaviour needing reinforcement from several people needs wide bridges. A more effective approach is for two people who have already adopted it to talk with the same few colleagues together, rather than each approaching different people separately.
- Independent blind assessment against cascades. Where a collective judgment is needed (reviewing proposals, estimating risks, hiring), does everyone first write down a judgment and reasons independently, with the results then pooled publicly, before discussion begins? Chapter 6 showed that announcing judgments in turn leads later speakers to follow earlier ones, and that independence, once lost, is hard to recover. Kahneman, Sibony and Sunstein list “independent judgments first, then pooling and discussion” among the basic practices for reducing “noise” in judgment (Kahneman, Sibony, and Sunstein 2021).
Calculation
The checklist applied to a fictional 12-person project team (R: research and development, S: sales, O: operations; every name and number is invented). Step 1 computes the constraint of the four people in the core circle and the E–I index; step 2 lists dormant cross-department ties and computes the core circle’s mean constraint after reactivating three of them; step 3 compares two ways of placing the same three cross-department ties, with threshold 2; step 4 uses the anchoring index defined in Part 2 to check how far the final scores in four proposal reviews were anchored on the first two speakers.
# The four-step topology checklist applied to an INVENTED 12-person project
# team (every name and number is fictional; in real use, collect such data
# only with informed consent and report aggregates). Departments: R (R&D),
# S (sales), O (operations).
from itertools import combinations
dept = {"Ann": "R", "Ben": "R", "Cal": "R", "Dee": "R", "Eve": "R", "Fay": "S",
"Gus": "S", "Hal": "S", "Ivy": "O", "Jo": "O", "Kim": "O", "Lu": "O"}
core = ["Ann", "Ben", "Cal", "Dee"]
active = set(combinations(core, 2)) | {
("Ann", "Eve"), ("Ben", "Eve"), ("Cal", "Eve"), ("Fay", "Gus"), ("Gus", "Hal"),
("Fay", "Hal"), ("Ivy", "Jo"), ("Jo", "Kim"), ("Kim", "Lu"), ("Ivy", "Lu"),
("Ivy", "Kim"), ("Dee", "Fay")}
dormant = {("Ben", "Jo"): 14, ("Cal", "Kim"): 20, ("Eve", "Ivy"): 9,
("Ann", "Gus"): 30, ("Eve", "Hal"): 7} # months since last contact
people = sorted(dept)
def nbrs(ties):
nb = {v: set() for v in people}
for a, b in ties: nb[a].add(b); nb[b].add(a)
return nb
def constraint(nb, i):
p = lambda a, b: 1 / len(nb[a]) if b in nb[a] else 0.0
return sum((p(i, j) + sum(p(i, q) * p(q, j) for q in sorted(nb[i])
if q != j)) ** 2 for j in sorted(nb[i]))
def ei_index(nb, group):
ext = sum(1 for v in group for w in nb[v] if dept[w] != dept[v])
inn = sum(1 for v in group for w in nb[v] if dept[w] == dept[v])
return (ext - inn) / (ext + inn)
nb = nbrs(active)
print("step 1 core-circle constraint; homophily (E-I index, -1 = all ties inside)")
print(" " + ", ".join(f"{v} {constraint(nb, v):.2f}" for v in core),
f"| core E-I {ei_index(nb, core):+.2f}, team E-I {ei_index(nb, people):+.2f}")
print("step 2 dormant cross-boundary ties (shared contacts, months idle)")
for (a, b), m in sorted(dormant.items(), key=lambda kv: kv[1]):
shared = len(nb[a] & nb[b])
print(f" {a}-{b} ({dept[a]}-{dept[b]}): shared {shared}, idle {m} months")
pick = [e for e, m in dormant.items() if m <= 14 and dept[e[1]] != dept[e[0]]]
nb2 = nbrs(active | set(pick))
print(f" reactivate {len(pick)} ties idle <= 14 months -> core constraint"
f" {sum(constraint(nb2, v) for v in core) / len(core):.2f}"
f" (was {sum(constraint(nb, v) for v in core) / len(core):.2f})")
print("step 3 R&D has adopted a costly practice; can it reach operations")
print(" (threshold 2)? The same number of cross ties, placed two ways:")
plans = {"scattered: Ben-Jo, Eve-Ivy, Cal-Kim":
{("Ben", "Jo"), ("Eve", "Ivy"), ("Cal", "Kim")},
"paired: Ben-Jo, Eve-Jo, Eve-Ivy":
{("Ben", "Jo"), ("Eve", "Jo"), ("Eve", "Ivy")}}
for label, extra in plans.items():
g = nbrs(active | extra)
adopted = {v for v in people if dept[v] == "R"}
while True:
new = {v for v in people if v not in adopted and len(g[v] & adopted) >= 2}
if not new: break
adopted |= new
ops = sorted(v for v in adopted if dept[v] == "O")
print(f" {label} -> operations adopters: {', '.join(ops) or 'none'}")
print("step 4 review of 4 proposals (scores 1-10): first two speakers vs agreed")
reviews = {"P1": ((8, 7), (4, 5, 4), 7.5), "P2": ((3, 4), (6, 7, 6), 4.0),
"P3": ((6, 6), (5, 6, 7), 6.0), "P4": ((9, 8), (5, 5, 6), 8.0)}
for pid, (first, rest, final) in reviews.items():
F, R = sum(first) / 2, sum(rest) / 3
if abs(F - R) < 0.5:
c = "undefined"
else:
c = f"{abs(final - R) / (abs(final - F) + abs(final - R)):.2f}"
print(f" {pid}: first two {F:.1f}, others {R:.1f}, agreed {final:.1f}"
f" -> anchoring {c}")
print(" anchoring 1 = agreed score equals the first two speakers' mean;")
print(" 0.4 = what an equal-weight average of all five would give")
# step 1 core-circle constraint; homophily (E-I index, -1 = all ties inside)
# Ann 0.70, Ben 0.70, Cal 0.70, Dee 0.48 | core E-I -0.88, team E-I -0.89
# step 2 dormant cross-boundary ties (shared contacts, months idle)
# Eve-Hal (R-S): shared 0, idle 7 months
# Eve-Ivy (R-O): shared 0, idle 9 months
# Ben-Jo (R-O): shared 0, idle 14 months
# Cal-Kim (R-O): shared 0, idle 20 months
# Ann-Gus (R-S): shared 0, idle 30 months
# reactivate 3 ties idle <= 14 months -> core constraint 0.54 (was 0.65)
# step 3 R&D has adopted a costly practice; can it reach operations
# (threshold 2)? The same number of cross ties, placed two ways:
# scattered: Ben-Jo, Eve-Ivy, Cal-Kim -> operations adopters: none
# paired: Ben-Jo, Eve-Jo, Eve-Ivy -> operations adopters: Ivy, Jo, Kim, Lu
# step 4 review of 4 proposals (scores 1-10): first two speakers vs agreed
# P1: first two 7.5, others 4.3, agreed 7.5 -> anchoring 1.00
# P2: first two 3.5, others 6.3, agreed 4.0 -> anchoring 0.82
# P3: first two 6.0, others 6.0, agreed 6.0 -> anchoring undefined
# P4: first two 8.5, others 5.3, agreed 8.0 -> anchoring 0.84
# anchoring 1 = agreed score equals the first two speakers' mean;
# 0.4 = what an equal-weight average of all five would giveThis fictional team shows several things. Three of the four people in the core circle have a constraint of 0.70, and the team’s E–I index is −0.89: almost all ties are within departments. The five dormant cross-department ties have no shared contacts with the current network, so all are potential bridges; reactivating the three that have been idle for no more than 14 months lowers the core circle’s mean constraint from 0.65 to 0.54. The same three ties into operations lead to no adopters there when scattered, but to adoption by all four people in operations when concentrated on two operations colleagues. In three of the four reviews the anchoring index can be computed, at 1.00, 0.82 and 0.84, all far above the 0.40 of “equal weight for all five”; in the third review the first two speakers agreed with the other three, so the index is undefined. These numbers are invented and serve only to show how the checklist is computed; a single set of data from one team can only be a prompt for discussion, not evidence.
Part 2 A pre-registered randomised controlled trial protocol
Question
When a team makes collective estimates or reviews proposals, does “each member first writes down a judgment and reasons independently and anonymously, the judgments are shown to everyone, and only then does the usual discussion follow” reduce the anchoring of the team’s final judgment on its first two speakers, compared with “members state their views openly in order of seniority and discuss”? What effect does it have on the accuracy of team judgments?
Anchoring was one of the first biases of judgment to be described systematically: a number that appears first pulls later estimates towards it (Tversky and Kahneman 1974). Research on brainstorming has also found that taking turns to speak causes “production blocking”, so that groups produce fewer ideas than the same number of people working independently (Diehl and Stroebe 1987). Both suggest that the order of a discussion itself shapes the group’s conclusion.
Design
| Element | Specification |
|---|---|
| Design | Two-arm parallel cluster-randomised controlled trial with the team as the unit of randomisation, stratified by source institution; reported according to the CONSORT extension for cluster trials (Campbell et al. 2012) |
| Participants | Existing work teams, each of 5 members aged 18 or over, with clear differences of seniority among them; recruited by open advertisement through universities, research institutes and companies; not recruited from the author’s community, students or customers, nor from the clients of Asia Influencer X and Yafu Global |
| Exclusions | Teams that already routinely use a structured independent-estimation procedure (for example the Delphi method); any member not consenting; the team’s institution requiring access to individual-level data |
| Tasks | Each team completes 10 quantity-estimation tasks with objective answers (taken from public statistics or historical records that participants cannot look up, and compiled by staff independent of the research team), 6 minutes per task |
| Common procedure | At the start of each task every member privately writes an initial estimate on paper, which a research assistant collects and seals on the spot; in the discussion members speak in order of seniority, from most to least senior; finally the team agrees on a single estimate |
| Arm A | After sealing, the research assistant shows the whole team the five initial estimates, each with the one-line reason its author wrote, anonymously and in random order, before the discussion begins |
| Arm B (control) | The initial estimates are sealed and not shown; the discussion begins at once (the team’s usual practice) |
| Blinding | Participants cannot be blinded to arm; the primary outcome is computed automatically from the recorded numbers, and analysts do not know the arm labels until the analysis plan is locked; facilitators follow a common script and take no part in computing outcomes |
| Primary outcome | Anchoring index. For each task, on the log scale: = mean initial estimate of the first two speakers, = mean initial estimate of the other three, = the team’s final estimate, . means the team adopted the first two speakers’ view entirely, corresponds to an equal-weight average of all five, and means the team adopted the other three’s view entirely. Tasks with (log units) are not counted. The team’s score is the mean of over its tasks |
| Secondary outcomes | Error of the team estimate, ; improvement of the team estimate over the mean of the five initial estimates; share of speaking time taken by the first two speakers; members’ satisfaction with the discussion and self-rated willingness to voice disagreement |
| Analysis | Team-level linear model: team score ~ arm + source-institution stratum; sensitivity analysis with a task-level mixed model (random team intercept); two-sided |
There is only one primary outcome. Anchoring rather than accuracy was chosen as the primary outcome because it directly measures the mechanism the trial aims to change (the independence of judgments) and does not depend on how hard the tasks are; accuracy is reported as the most important secondary outcome.
Sample size
Calculation
# Sample size for the Chapter 8 trial. Unit of randomisation and analysis: the
# 5-person team. Each team does m = 10 estimation tasks with known answers.
# Per task, the anchoring index c = |G - R| / (|G - F| + |G - R|) on log
# estimates (G agreed group estimate, F mean private estimate of the first two
# speakers, R mean of the other three); team score = mean c over its tasks.
# Assumptions (to be confirmed): SD of c across tasks sigma = 0.25; share of
# that variance lying between teams (ICC) rho = 0.30; smallest difference of
# interest 0.10; two-sided alpha 0.05; power 0.90; normal approximation + 1.
import math, random
from statistics import NormalDist
Z = NormalDist().inv_cdf(0.975) + NormalDist().inv_cdf(0.90)
SIGMA, M = 0.25, 10
def teams_per_arm(delta, rho, m=M, sigma=SIGMA):
var_team = sigma ** 2 * (rho + (1 - rho) / m) # variance of a team mean
return math.ceil(2 * Z ** 2 * var_team / delta ** 2) + 1
print("teams per arm for 90 % power (10 tasks per team)")
print(" difference | ICC rho = 0.2 | 0.3 | 0.5")
for delta in (0.08, 0.10, 0.12):
print(f" {delta:.2f} | {teams_per_arm(delta, 0.2):13d} |"
f" {teams_per_arm(delta, 0.3):3d} | {teams_per_arm(delta, 0.5):3d}")
n = teams_per_arm(0.10, 0.30); rec = math.ceil(n / 0.85)
print(f"chosen: 0.10, rho = 0.30 -> {n} analysable teams per arm;"
" allowing 15 % loss")
print(f" recruit {rec} teams per arm: {2 * rec} teams, {2 * rec * 5} people")
naive = math.ceil(2 * Z ** 2 * SIGMA ** 2 / 0.10 ** 2 / M) + 1
print(f"ignoring clustering (tasks as independent) would suggest only {naive}"
" teams per arm;")
print(f" design effect 1 + (m - 1) rho = {1 + (M - 1) * 0.30:.2f}")
rng = random.Random(15)
def sim_power(teams, delta, rho, sims=2000):
hits = 0
for _ in range(sims):
means = []
for arm_shift in (0.0, delta):
sd_b, sd_w = SIGMA * math.sqrt(rho), SIGMA * math.sqrt(1 - rho)
means.append([arm_shift + rng.gauss(0, sd_b) +
sum(rng.gauss(0, sd_w) for _ in range(M)) / M
for _ in range(teams)])
a, b = means
ma, mb = sum(a) / teams, sum(b) / teams
va = sum((x - ma) ** 2 for x in a) / (teams - 1)
vb = sum((x - mb) ** 2 for x in b) / (teams - 1)
hits += abs(mb - ma) / math.sqrt((va + vb) / teams) > 1.96
return hits / sims
p1, p2 = sim_power(n, 0.10, 0.30), sim_power(naive, 0.10, 0.30)
print(f"simulation check: power with {n} teams per arm = {p1:.2f};"
f" with {naive} = {p2:.2f}")
# teams per arm for 90 % power (10 tasks per team)
# difference | ICC rho = 0.2 | 0.3 | 0.5
# 0.08 | 59 | 77 | 114
# 0.10 | 38 | 50 | 74
# 0.12 | 27 | 35 | 52
# chosen: 0.10, rho = 0.30 -> 50 analysable teams per arm; allowing 15 % loss
# recruit 59 teams per arm: 118 teams, 590 people
# ignoring clustering (tasks as independent) would suggest only 15 teams per arm;
# design effect 1 + (m - 1) rho = 3.70
# simulation check: power with 50 teams per arm = 0.91; with 15 = 0.46Assuming a standard deviation of 0.25 for the anchoring index of a single task, of which 30 % of the variance lies between teams (intraclass correlation 0.30), a smallest difference of interest of 0.10 and 90 % power, each arm needs 50 analysable teams. Allowing for about 15 % of teams being unanalysable because of absent members or incomplete task data, each arm recruits 59 teams, 118 teams and 590 people in all. Treating every task as an independent observation would suggest only 15 teams per arm, but that ignores the correlation between tasks within a team: the design effect is , and simulation shows that 15 teams per arm would give actual power of only 0.46. The intraclass correlation is the key unknown: if it were 0.5, each arm would need 74 analysable teams. All these parameters are assumptions and should be checked against pilot data before the trial begins.
Decision rule
Calculation
# Decision rule for the Chapter 8 trial, fixed before any data are collected.
# diff = arm A (private written judgments first, anonymous summary, then the
# usual discussion) minus arm B (usual discussion only) in the team-level
# anchoring index; NEGATIVE diff = less anchoring on the first two speakers.
# 95 % CI from the pre-specified linear model (arm + organisation stratum).
# Smallest difference of interest 0.10 on the 0-1 index.
SESOI = 0.10
def verdict(diff, lo, hi, fidelity, contamination):
if fidelity < 0.80:
return (f"diff {diff:+.2f}: NOT INTERPRETABLE - private judgments first"
f" in only {fidelity:.0%} of A sessions")
note = ("\n caution: arm B teams used written rounds in"
f" {contamination:.0%} of sessions") if contamination > 0.20 else ""
if hi <= -SESOI: v = "A reduces anchoring by at least 0.10"
elif hi < 0: v = "A reduces anchoring; not shown to reach 0.10"
elif lo > 0: v = "A INCREASES anchoring"
elif lo >= -SESOI and hi <= SESOI: v = "no difference worth acting on"
else: v = "unclear: the interval is too wide to decide"
return f"diff {diff:+.2f} ({lo:+.2f} to {hi:+.2f}): {v}{note}"
for case in ((-0.18, -0.25, -0.11, 0.95, 0.05), (-0.07, -0.14, 0.00, 0.92, 0.10),
(-0.12, -0.19, -0.05, 0.90, 0.05), (0.01, -0.06, 0.08, 0.93, 0.08),
(-0.20, -0.27, -0.13, 0.70, 0.05), (-0.15, -0.22, -0.08, 0.90, 0.30)):
print(verdict(*case))
print("group error |log agreed estimate - log truth| is secondary: reported with")
print(" intervals, never used to override the primary verdict")
# diff -0.18 (-0.25 to -0.11): A reduces anchoring by at least 0.10
# diff -0.07 (-0.14 to +0.00): unclear: the interval is too wide to decide
# diff -0.12 (-0.19 to -0.05): A reduces anchoring; not shown to reach 0.10
# diff +0.01 (-0.06 to +0.08): no difference worth acting on
# diff -0.20: NOT INTERPRETABLE - private judgments first in only 70% of A sessions
# diff -0.15 (-0.22 to -0.08): A reduces anchoring; not shown to reach 0.10
# caution: arm B teams used written rounds in 30% of sessions
# group error |log agreed estimate - log truth| is secondary: reported with
# intervals, never used to override the primary verdictThe example results are made up. If fewer than 80 % of arm-A sessions collected and displayed the independent judgments as specified, the report says “not interpretable” whatever the result; if more than 20 % of arm-B sessions spontaneously used written rounds, the report must state that the control arm was contaminated. Only when the upper limit of the confidence interval of the difference is no higher than −0.10 does the report say that “the procedure of independent judgments first reduced anchoring by at least the smallest difference of interest”.
What this trial cannot show
First, both arms write down private initial estimates first, which is necessary for measuring anchoring; but writing a judgment down may itself already reduce anchoring in arm B, so the difference between the arms may be underestimated. Second, the tasks are quantity estimates with objective answers, and the conclusions may not carry over to value judgments, strategic choices or personnel decisions that have no correct answer. Third, arm A changes two things at once (displaying independent judgments, and anonymity), so even a positive result cannot say which of them worked. Fourth, participants know they are in a study, and their discussions may be more careful than usual. Fifth, teams that volunteer may already be more willing than most to hear dissent. Sixth, less anchoring is not the same as better decisions: if the first two speakers happen to know the situation best, anchoring on them may be reasonable; this is exactly why accuracy must be reported as a secondary outcome.
Ethics and conflicts of interest
The trial must be approved by an independent ethics committee; every member gives individual informed consent; and the protocol and analysis plan are registered before the first team is enrolled (Nosek et al. 2018). Participation is entirely voluntary, and any member may withdraw at any time without giving a reason; the team’s institution may receive no individual-level data and may not use participation or results in appraisals. The study records only speaking order and seniority level and collects no data on the relationship network among team members.
The checklist in Part 1, by contrast, does need relational data. Organisational network analysis raises some distinctive ethical problems: surveys cannot be anonymous (one must know who reported a tie with whom), the people being reported on have not consented, and the results may be used by managers to make decisions that harm employees (Borgatti and Molina 2003). When using the checklist, therefore, everyone who will be recorded should agree in advance, only aggregate results should be reported, no individual’s network position should be linked to performance, and the raw data should be deleted after use. The LinkedIn experiments mentioned in Chapter 1 changed platform recommendations without users’ knowledge and, after publication, prompted debate about informed consent in large platform experiments; the trial in this book uses no intervention without participants’ knowledge.
Disclosure
The author uses the works of 5D Library, including this book, as the methodological basis of his community courses, the 5D Ascension Cards (Dimensional Shift Card Decks) and the Yafu AI knowledge base, and therefore has a commercial interest in the subject of this book. In addition, the author founded and runs Asia Influencer X and Yafu Global (influencer marketing, digital marketing and consulting), whose business involves diffusion through social networks, and therefore has a direct commercial interest in the subject of this book. The book promotes no course, card deck or service, and it uses no client data of these companies as evidence; the Chapter 8 trial is designed to be run and analysed by researchers independent of the author’s business and does not recruit from the author’s community, students or customers (including the clients of these companies); all the code and the study protocol are published openly for anyone to check. To limit the influence of these interests, the task materials and analysis code will be published before enrolment, and the results, data and code will be published whatever the results.
References
Fan, Y. (2026). Social Network Topology, Structural Holes and Complex Contagion: From the Strength of Weak Ties and Preferential Attachment to Information Cascades, Read Beside Classical Chinese Diplomacy [社会网络拓扑、结构洞与复杂传染的数学模型] (Version 1.0.0). 5D Library. https://doi.org/10.5281/zenodo.23764990 · Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0).