生物医学网络科学Biomedical Network Science
Example04 / 11

bootstrap 边稳定性与病例删除稳定性(偏相关网络)

所属专题
网络构建与统计验证
下载
Notebook · .py

由一个已知的稀疏精度矩阵生成 n=300 名被试、p=8 个条目的模拟量表数据,估计偏相关网络。 (1) 非参数 bootstrap:有放回重抽样被试 1000 次,得到每条边的 95% 分位数区间和“|偏相关|>0.1 的比例”; (2) 病例删除:无放回地删去 10%–70% 的被试,看强度中心性与全样本的相关,计算类似 CS 系数的指标。 解读:真实的强边区间远离 0;多数不存在的边区间包含 0,但同时考察几十条边时,总会有个别假边的 区间不含 0,弱的真实边也可能与假边难以区分——逐边区间不是多重比较校正。这里为了自包含用了未正则化的偏相关, 实际症状网络常用 EBICglasso,此时 bootstrap 区间会因收缩而偏向 0,只宜用于判断边的可区分性。

Output输出图

bootstrap 边稳定性与病例删除稳定性(偏相关网络):输出图 1

Output文字输出

边      估计     95%区间            |w|>0.1比例  真实存在
0-1   +0.374  [+0.270, +0.474]   1.00        True
5-6   +0.366  [+0.265, +0.456]   1.00        True
4-5   +0.361  [+0.263, +0.456]   1.00        True
2-3   +0.330  [+0.221, +0.435]   1.00        True
1-2   +0.272  [+0.175, +0.366]   1.00        True
6-7   +0.254  [+0.151, +0.358]   1.00        True
2-4   +0.166  [+0.046, +0.287]   0.85        False
1-4   -0.149  [-0.251, -0.041]   0.81        False
2-7   +0.127  [+0.009, +0.233]   0.69        False
3-4   +0.122  [+0.014, +0.231]   0.64        True
0-7   -0.121  [-0.225, -0.009]   0.62        False
0-4   +0.115  [+0.011, +0.213]   0.59        True
区间不含 0:真实边 8/8 条,不存在的边 4/20 条
删去 10% 被试:强度与全样本相关的中位数=0.97,r>=0.7 的比例=1.00
删去 20% 被试:强度与全样本相关的中位数=0.94,r>=0.7 的比例=1.00
删去 30% 被试:强度与全样本相关的中位数=0.90,r>=0.7 的比例=0.97
删去 40% 被试:强度与全样本相关的中位数=0.87,r>=0.7 的比例=0.89
删去 50% 被试:强度与全样本相关的中位数=0.81,r>=0.7 的比例=0.78
删去 60% 被试:强度与全样本相关的中位数=0.72,r>=0.7 的比例=0.55
删去 70% 被试:强度与全样本相关的中位数=0.61,r>=0.7 的比例=0.35
类 CS 系数(95% 的子样本中 r>=0.7 时可删去的最大比例)= 0.3

Code代码

# 由一个已知的稀疏精度矩阵生成 n=300 名被试、p=8 个条目的模拟量表数据,估计偏相关网络。
# (1) 非参数 bootstrap:有放回重抽样被试 1000 次,得到每条边的 95% 分位数区间和“|偏相关|>0.1 的比例”;
# (2) 病例删除:无放回地删去 10%–70% 的被试,看强度中心性与全样本的相关,计算类似 CS 系数的指标。
# 解读:真实的强边区间远离 0;多数不存在的边区间包含 0,但同时考察几十条边时,总会有个别假边的
# 区间不含 0,弱的真实边也可能与假边难以区分——逐边区间不是多重比较校正。这里为了自包含用了未正则化的偏相关,
# 实际症状网络常用 EBICglasso,此时 bootstrap 区间会因收缩而偏向 0,只宜用于判断边的可区分性。
import numpy as np

rng = np.random.default_rng(4)
p, n = 8, 300
K = np.eye(p)
true_edges = [(0, 1, 0.35), (1, 2, 0.3), (2, 3, 0.3), (3, 4, 0.25), (4, 5, 0.3), (5, 6, 0.25), (6, 7, 0.3), (0, 4, 0.2)]
for i, j, w in true_edges:
    K[i, j] = K[j, i] = -w                       # 精度矩阵负元素对应正偏相关
Sigma = np.linalg.inv(K)
X = rng.multivariate_normal(np.zeros(p), Sigma, size=n)

def pcor(X):
    P = np.linalg.inv(np.corrcoef(X, rowvar=False))
    d = np.sqrt(np.diag(P))
    W = -P / np.outer(d, d)
    np.fill_diagonal(W, 0)
    return W

iu = np.triu_indices(p, 1)
W0 = pcor(X)
B = 1000
boot = np.empty((B, len(iu[0])))
for b in range(B):
    boot[b] = pcor(X[rng.integers(n, size=n)])[iu]
lo, hi = np.percentile(boot, [2.5, 97.5], axis=0)
inc = (np.abs(boot) > 0.1).mean(axis=0)
is_true = np.zeros((p, p), bool)
for i, j, _ in true_edges:
    is_true[i, j] = is_true[j, i] = True
print("边      估计     95%区间            |w|>0.1比例  真实存在")
for k in np.argsort(-np.abs(W0[iu]))[:12]:
    i, j = iu[0][k], iu[1][k]
    print(f"{i}-{j}   {W0[i, j]:+.3f}  [{lo[k]:+.3f}, {hi[k]:+.3f}]   {inc[k]:.2f}        {is_true[i, j]}")
excl0 = (lo > 0) | (hi < 0)
t_vec = is_true[iu]
print(f"区间不含 0:真实边 {excl0[t_vec].sum()}/{t_vec.sum()} 条,不存在的边 {excl0[~t_vec].sum()}/{(~t_vec).sum()} 条")

# 病例删除稳定性:强度中心性
s0 = np.abs(W0).sum(axis=1)
props = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
cs = 0.0
r_drop = []                                   # 保存每个删除比例下的相关,用于作图
for prop in props:
    keep = int(round((1 - prop) * n))
    r = np.array([np.corrcoef(np.abs(pcor(X[rng.choice(n, keep, replace=False)])).sum(axis=1), s0)[0, 1]
                  for _ in range(300)])
    r_drop.append(r)
    frac = (r >= 0.7).mean()
    print(f"删去 {prop:.0%} 被试:强度与全样本相关的中位数={np.median(r):.2f},r>=0.7 的比例={frac:.2f}")
    if frac >= 0.95:
        cs = prop
print(f"类 CS 系数(95% 的子样本中 r>=0.7 时可删去的最大比例)= {cs:.1f}")

# 作图:全部边的 bootstrap 95% 区间(按估计值排序,真实边与不存在的边分色);病例删除稳定性曲线
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(10, 4.2))
o = np.argsort(W0[iu])
for j, k in enumerate(o):
    c = "C3" if t_vec[k] else "0.5"
    ax[0].plot([lo[k], hi[k]], [j, j], color=c, lw=1.5); ax[0].plot(W0[iu][k], j, "o", color=c, ms=3)
ax[0].axvline(0, color="k", lw=0.8)
ax[0].plot([], [], color="C3", label="true edge"); ax[0].plot([], [], color="0.5", label="absent edge")
ax[0].set_xlabel("partial correlation (bootstrap 95% interval)"); ax[0].set_yticks([]); ax[0].legend(fontsize=8)
ax[0].set_title("Strong true edges are far from 0", fontsize=10)
ax[1].boxplot(r_drop, showfliers=False); ax[1].set_xticklabels([f"{p_:.0%}" for p_ in props])
ax[1].axhline(0.7, color="C3", ls="--", label="r = 0.7")
ax[1].set_xlabel("proportion of cases dropped"); ax[1].set_ylabel("corr. with full-sample strength")
ax[1].set_title(f"Case-dropping stability (CS-like = {cs:.1f})", fontsize=10); ax[1].legend(fontsize=8)
plt.tight_layout(); plt.show()

Run在浏览器中运行

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

bootstrap 边稳定性与病例删除稳定性(偏相关网络)