生物医学网络科学Biomedical Network Science
Example05 / 07

网络传播——带重启随机游走(RWR)与热扩散的参数、度偏倚与去偏

所属专题
疾病模块、网络邻近度与药物重定位
下载
Notebook · .py

用同一个埋入模块的合成网络:30 个已知疾病基因作种子,按传播分数给其余节点排序, 看前 30 名中有多少隐藏的模块成员(召回)。比较 RWR 的重启概率 r 与热扩散的时间 t, 并计算分数与节点度的 Spearman 相关:r 越小 / t 越大,传播越“全局”,分数越接近度(平稳分布)。 最后用 200 组度匹配随机种子把 RWR 分数转成 z 分数,演示“去度偏倚”。 输出解读:中等参数时召回最好;过度扩散后排名被枢纽占据;z 分数排序降低了与度的相关。

Output输出图

网络传播——带重启随机游走(RWR)与热扩散的参数、度偏倚与去偏:输出图 1

Output文字输出

RWR:
  r = 0.05:前30名中隐藏成员  6 个;分数与度的 Spearman ρ = 0.86
  r = 0.20:前30名中隐藏成员 15 个;分数与度的 Spearman ρ = 0.65
  r = 0.50:前30名中隐藏成员 14 个;分数与度的 Spearman ρ = 0.48
  r = 0.80:前30名中隐藏成员 14 个;分数与度的 Spearman ρ = 0.40
热扩散:
  t =  0.5:前30名中隐藏成员 14 个;分数与度的 Spearman ρ = 0.38
  t =  2.0:前30名中隐藏成员 15 个;分数与度的 Spearman ρ = 0.46
  t =  5.0:前30名中隐藏成员 10 个;分数与度的 Spearman ρ = 0.63
  t = 20.0:前30名中隐藏成员  0 个;分数与度的 Spearman ρ = 0.93
r = 0.05:原始分数 前30命中 6,ρ(度) = 0.86;度匹配 z 分数 前30命中 10,ρ(度) = 0.10

Code代码

# 用同一个埋入模块的合成网络:30 个已知疾病基因作种子,按传播分数给其余节点排序,
# 看前 30 名中有多少隐藏的模块成员(召回)。比较 RWR 的重启概率 r 与热扩散的时间 t,
# 并计算分数与节点度的 Spearman 相关:r 越小 / t 越大,传播越“全局”,分数越接近度(平稳分布)。
# 最后用 200 组度匹配随机种子把 RWR 分数转成 z 分数,演示“去度偏倚”。
# 输出解读:中等参数时召回最好;过度扩散后排名被枢纽占据;z 分数排序降低了与度的相关。
import networkx as nx
import numpy as np
import scipy.sparse as sp
from scipy.sparse.linalg import expm_multiply, spsolve
from scipy.stats import spearmanr

def planted_module_network(n=2000, m=2, size=60, seed=7):
    rng = np.random.default_rng(seed)
    G = nx.barabasi_albert_graph(n, m, seed=seed)
    module = rng.choice([v for v in G if G.degree(v) <= 6], size=size, replace=False).tolist()
    for _ in range(90):
        u, v = rng.choice(module, size=2, replace=False)
        G.add_edge(int(u), int(v))
    known = sorted(rng.choice(module, size=size // 2, replace=False).tolist())
    return G, known, sorted(set(module) - set(known))

G, known, hidden = planted_module_network()
N = G.number_of_nodes()
A = nx.to_scipy_sparse_array(G, nodelist=range(N), format="csc", dtype=float)
deg = np.asarray(A.sum(axis=0)).ravel()
W = A @ sp.diags(1.0 / deg)                      # 列随机矩阵 W = A D^{-1}
I = sp.identity(N, format="csc")
L_rw = I - W                                      # 随机游走拉普拉斯(作用在概率分布上)

def p0_of(seeds):
    p = np.zeros(N); p[list(seeds)] = 1.0 / len(seeds); return p

def rwr(seeds, r):
    # 闭式解 p* = r (I - (1-r) W)^{-1} p0;稀疏线性方程组求解
    return spsolve((I - (1 - r) * W).tocsc(), r * p0_of(seeds))

def heat(seeds, t):
    # 热扩散 p(t) = exp(-t L_rw) p0,总量守恒
    return expm_multiply(-t * L_rw, p0_of(seeds))

def evaluate(score, seeds, top=30):
    rest = np.array(sorted(set(range(N)) - set(seeds)))
    order = rest[np.argsort(-score[rest], kind="stable")]
    hits = len(set(order[:top].tolist()) & set(hidden))
    rho = spearmanr(score[rest], deg[rest]).statistic
    return hits, rho

sweep = {"RWR": [], "heat": []}
print("RWR:")
for r in [0.05, 0.2, 0.5, 0.8]:
    h, rho = evaluate(rwr(known, r), known)
    print(f"  r = {r:4.2f}:前30名中隐藏成员 {h:2d} 个;分数与度的 Spearman ρ = {rho:.2f}")
    sweep["RWR"].append((f"r={r}", h, rho))
print("热扩散:")
for t in [0.5, 2, 5, 20]:
    h, rho = evaluate(heat(known, t), known)
    print(f"  t = {t:4.1f}:前30名中隐藏成员 {h:2d} 个;分数与度的 Spearman ρ = {rho:.2f}")
    sweep["heat"].append((f"t={t}", h, rho))

# 去度偏倚:r 较小时,用度匹配随机种子集构造每个节点的零分布
r = 0.05
by_deg = {}
for v in range(N):
    by_deg.setdefault(int(deg[v]), []).append(v)
bins, cur = [], []
for d in sorted(by_deg):
    cur += by_deg[d]
    if len(cur) >= 50: bins.append(cur); cur = []
if cur: bins[-1] += cur
node2bin = {v: b for b in bins for v in b}
rng = np.random.default_rng(0)
obs = rwr(known, r)
null = np.array([rwr([int(rng.choice(node2bin[s])) for s in known], r) for _ in range(200)])
z = (obs - null.mean(0)) / (null.std(0) + 1e-12)
h_raw, rho_raw = evaluate(obs, known)
h_z, rho_z = evaluate(z, known)
print(f"r = {r}:原始分数 前30命中 {h_raw},ρ(度) = {rho_raw:.2f};度匹配 z 分数 前30命中 {h_z},ρ(度) = {rho_z:.2f}")

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
for (name, rows), st, off, ha in zip(sweep.items(), ["o-", "s--"], [(-4, 6), (5, -11)], ["right", "left"]):
    ax[0].plot([x[2] for x in rows], [x[1] for x in rows], st, label=name)
    for lab, h, rho in rows:
        ax[0].annotate(lab, (rho, h), textcoords="offset points", xytext=off, ha=ha, fontsize=7)
ax[0].plot(rho_z, h_z, "*", ms=14, color="C3", label=f"RWR r={r}, degree-matched z")
ax[0].set_xlabel("Spearman ρ(score, degree)"); ax[0].set_ylabel("hidden members in top 30"); ax[0].set_ylim(-1.5, 17)
ax[0].set_title("Over-diffusion ranks hubs, not the module"); ax[0].legend(fontsize=8)
rest = np.array(sorted(set(range(N)) - set(known))); hid = np.isin(rest, hidden)
ax[1].scatter(deg[rest], obs[rest], s=8, color="0.7", alpha=0.5, label="non-seed nodes")
ax[1].scatter(deg[rest][hid], obs[rest][hid], s=40, color="C3", label="hidden module members")
ax[1].set_xscale("log"); ax[1].set_yscale("log")
ax[1].set_xlabel("degree"); ax[1].set_ylabel(f"RWR score (r = {r})")
ax[1].set_title("Raw propagation scores track degree"); ax[1].legend(fontsize=8)
plt.tight_layout(); plt.show()

Run在浏览器中运行

首次加载 Python 环境约需 20–40 秒;可以修改参数后重新运行。

网络传播——带重启随机游走(RWR)与热扩散的参数、度偏倚与去偏