5D Library › 社会网络拓扑、结构洞与复杂传染的数学模型 · English
第八章 实践与预注册试验
注
本章的核查表需要关系数据,而关系数据涉及第三方。 在一个团队中记录「谁和谁交流」,就同时记录了其他人的关系。只有在所有当事人知情并同意、数据只以汇总形式报告、不用于任何人的绩效考核或去留决定的前提下,才可以使用下面的核查表。本章不提供、也不鼓励任何操纵他人关系的做法。
第一部分 四步拓扑核查表
这份核查表供一个团队在全体成员同意的前提下,用来检查自己的信息结构。它的目的是让团队看到自己可能看不到的东西,而不是给任何人打分。
- 核心圈的约束与同质性。 团队里最常一起做决定的几个人,他们的联系人是不是彼此全都相识(网络约束高,第二章)?他们的连带是不是几乎都在同一个部门或同一种背景之内?后者可以用克拉克哈特与斯特恩的 E–I 指数衡量:(外部连带 − 内部连带) / (外部连带 + 内部连带),−1 表示全部在内部 (Krackhardt and Stern 1988)。同质性是社会网络中最普遍的规律之一,它会限制一个群体接触到的信息 (McPherson, Smith-Lovin, and Cook 2001)。
- 跨界弱连带的激活。 团队成员有哪些已经很久不联系、但通往其他部门或领域的旧关系?研究发现,重新联系「休眠」的旧关系,往往能以较少的投入带来比现有关系更新颖的信息 (Levin, Walter, and Murnighan 2011)。这一步只是列出可能,联系与否完全由当事人决定。
- 高成本行为的宽桥。 如果团队希望一种费力的新做法跨越到另一个群体,不要指望一个人去「点燃」它:第三章说明,需要多人强化的行为需要宽桥。更有效的做法是让两位已经采纳的人共同与同样的几位同事交流,而不是各自分散地接触不同的人。
- 防级联的独立盲评。 在需要集体判断的场合(方案评审、风险估计、招聘),是否先让每个人独立地写下判断与理由,再公开汇总,然后才讨论?第六章说明,依次公开表态会让后发言的人跟随前面的人,判断的独立性一旦丢失就很难找回。卡尼曼、西博尼与桑斯坦把「先独立判断、再汇总讨论」列为减少判断「噪声」的基本做法之一 (Kahneman, Sibony, and Sunstein 2021)。
推导
把核查表用在一个虚构的 12 人项目团队上(R:研发,S:销售,O:运营;所有姓名与数字均为虚构)。第 1 步计算核心圈四人的约束与 E–I 指数;第 2 步列出休眠的跨部门旧关系,并计算激活其中三条后核心圈的平均约束;第 3 步比较同样三条跨部门连带的两种放置方式,阈值为 2;第 4 步用第二部分定义的锚定指数,检查四项方案评审中最终分数对前两位发言者的锚定程度。
# 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 give这个虚构的团队显示了几件事。核心圈四人中有三人的约束是 0.70,团队的 E–I 指数为 −0.89,几乎所有连带都在部门内部。五条休眠的跨部门旧关系与现有网络都没有共同联系人,都是潜在的桥;激活其中闲置不超过 14 个月的三条,核心圈的平均约束从 0.65 降到 0.54。同样三条通往运营部的连带,分散放置时运营部无人采纳新做法,集中在两位运营同事身上时,运营部四人全部采纳。四项评审中有三项可以计算锚定指数,分别为 1.00、0.82 与 0.84,都远高于「五人等权」时的 0.40;第三项的前两位与其他三位意见相同,指数没有定义。这些数字是编造的,只用来说明核查表的计算方法;一个团队一次的数据只能作为讨论的线索,不能作为证据。
第二部分 预注册随机对照试验方案
问题
在团队做集体估计或方案评审时,「先由每位成员独立、匿名地写下判断与理由,公开汇总后再按惯常方式讨论」,与「直接按资历顺序公开表态并讨论」相比,能否减少团队最终判断向最先发言的两位成员的锚定?对团队判断的准确性有什么影响?
锚定是判断研究中最早被系统描述的偏差之一:先出现的数字会把后来的估计拉向它 (Tversky and Kahneman 1974)。关于头脑风暴的研究也发现,轮流发言会造成「生产阻塞」,使群体产出的想法少于同样人数各自独立工作时的总和 (Diehl and Stroebe 1987)。两者都提示,讨论的顺序本身会影响群体的结论。
设计
| 要素 | 规定 |
|---|---|
| 设计 | 两组平行、以团队为单位随机化的整群随机对照试验,按来源机构分层;按整群试验的 CONSORT 扩展报告 (Campbell et al. 2012) |
| 参与者 | 已经存在的工作团队,每队 5 名年满 18 岁的成员,成员之间有明确的资历差别;通过大学、研究机构与企业的公开招募,不从作者的社群、学员或客户中招募,也不从 Asia Influencer X 与 Yafu Global 的客户中招募 |
| 排除 | 团队已经常规使用结构化的独立估计程序(例如德尔菲法);任何成员不同意参加;团队所在机构要求获得个人层面的数据 |
| 任务 | 每队完成 10 项有客观答案的数量估计任务(答案来自参与者无从查到的公开统计或历史记录,由独立于研究团队的人员编制),每项限时 6 分钟 |
| 两组共同的程序 | 每项任务开始时,每位成员先在纸上私下写下自己的初始估计,由研究助理当场收走封存;讨论时按资历从高到低的顺序发言;最后团队给出一个一致同意的估计 |
| A 组 | 封存之后,研究助理把五个初始估计和每人写下的一句理由以匿名、随机顺序展示给全队,然后才开始讨论 |
| B 组(对照) | 初始估计封存后不展示,直接开始讨论(团队的惯常做法) |
| 盲法 | 参与者无法对分组设盲;主要结局由程序根据记录的数字自动计算,分析人员在分析计划锁定之前不知道组别标签;主持人按统一脚本操作,不参与结局计算 |
| 主要结局 | 锚定指数。对每项任务,以对数刻度计算: = 前两位发言者初始估计的平均, = 其余三位初始估计的平均, = 团队最终估计,。 表示团队完全采纳了前两位的意见, 相当于五人等权平均, 表示完全采纳其余三位的意见。(对数单位)的任务不计入。团队得分为各项任务 的平均 |
| 次要结局 | 团队估计的误差 ;团队估计相对于五个初始估计平均值的改进;前两位发言者的发言时间占比;成员对讨论的满意度与「敢于提出不同意见」的自评 |
| 分析 | 团队层面的线性模型:团队得分 ~ 组别 + 来源机构分层;敏感性分析用任务层面的混合模型(团队随机截距);双侧 |
主要结局只设一项。选择锚定而不是准确性作为主要结局,是因为它直接衡量试验想要改变的机制(判断的独立性),也不依赖任务的难度;准确性作为最重要的次要结局报告。
样本量
推导
# 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.46假设单项任务锚定指数的标准差为 0.25,其中 30% 的变异来自团队之间(组内相关系数 0.30),最小关注差异为 0.10,把握度 90%:每组需要 50 个可分析的团队。考虑约 15% 的团队因成员缺席或任务数据不全而无法分析,每组招募 59 个团队,共 118 个团队、590 人。如果把每项任务当作独立的观察,只需每组 15 个团队,但这忽略了同一团队的各项任务彼此相关:设计效应为 ,模拟显示每组 15 个团队的实际把握度只有 0.46。组内相关系数是关键的未知数:如果它是 0.5,每组需要 74 个可分析团队。这些参数都是假设值,正式实施前应当用预试验的数据核对。
判定规则
推导
# 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 verdict示例结果是编造的。如果 A 组中按规定先收集并展示独立判断的会议少于 80%,无论结果如何,报告都写「不可解释」;如果 B 组中有超过 20% 的会议自发采用了书面轮次,报告须注明对照组受到污染。只有当差异的置信区间上限不高于 −0.10 时,报告才写「独立判断在先的程序使锚定减少了至少最小关注的幅度」。
这项试验不能说明什么
第一,两组都要先私下写下初始估计,这是测量锚定所必需的,但写下判断本身可能已经减少了 B 组的锚定,使两组的差别被低估。第二,任务是有客观答案的数量估计,结论不一定能推广到没有标准答案的价值判断、战略选择或人事决定。第三,A 组同时改变了两件事(展示独立判断、匿名化),即使结果为阳性,也不能区分是哪一个起了作用。第四,参与者知道自己在一项研究中,他们的讨论可能比平时更谨慎。第五,自愿参加的团队,可能本来就比一般团队更愿意听取不同意见。第六,锚定减少不等于决策更好:如果前两位发言者恰好是最了解情况的人,向他们锚定可能是合理的;这正是准确性必须作为次要结局报告的原因。
伦理与利益冲突
试验须经独立伦理委员会批准,每位成员单独签署知情同意,并在第一个团队入组之前登记方案与分析计划 (Nosek et al. 2018)。参加与否完全自愿,任何成员可以随时退出而不需要说明理由;团队所在机构不得获得任何个人层面的数据,也不得把参加与否或结果用于考核。研究只记录发言顺序与资历等级,不收集团队成员之间的关系网络数据。
本章第一部分的核查表则需要关系数据。组织网络分析中有一些特有的伦理问题:调查不可能匿名(必须知道是谁报告了与谁的关系),被报告的人并没有同意;分析结果可能被管理者用来做出对员工不利的决定 (Borgatti and Molina 2003)。因此,使用核查表时应当让所有被记录的人事先同意,只报告汇总结果,不把个人的网络位置与绩效挂钩,用后删除原始数据。第一章提到的领英实验在用户不知情的情况下改变了平台推荐,发表后引起了关于大型平台实验知情同意的讨论;本书的试验不采用任何不知情的干预。
利益披露
作者以 5D Library 的著作(包括本书)作为其社群课程、升维牌卡(5D Ascension Cards,Dimensional Shift Card Decks)与 Yafu AI 知识库的方法论基础,因此与本书主题有商业利益关系。此外,作者创办并经营 Asia Influencer X 与 Yafu Global(网红营销、数字营销与咨询),其业务涉及社交网络传播,因此与本书主题有直接的商业利益。本书不推销任何课程、牌卡或服务,书中不使用这些公司的客户数据作为证据;第八章的试验设计为由独立于作者企业的研究人员实施和分析,不从作者的社群、学员或客户(包括上述公司的客户)中招募;书中所有代码与研究方案均公开,供任何人检验。为限制利益的影响:任务材料与分析代码在入组前公开;无论结果如何,结果、数据和代码都将公开。
参考文献
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).