生物医学网络科学Biomedical Network Science
Example07 / 09

k-核分解——手写剥离算法并与 NetworkX 对照,比较核数与度

所属专题
度相关性与网络结构的高阶统计
下载
Notebook · .py

实现 Batagelj–Zaversnik 的“按当前度从小到大剥离”思想(这里用简单的最小堆,复杂度 O(M log N)), 与 nx.core_number 对照;再在一个幂律随机网络与一个“度相同但枢纽之间互连更多”的网络上比较: (不用 BA 网络:BA 网络每个新节点恰好带 m 条边,整张图就是一个 m-核,壳层结构是平凡的。) 最内核大小、最内核内部密度,以及核数与度的 Spearman 相关。 解读:核数度量的是“嵌在多稠密的子图里”,与度高度相关但不等同;度相同的节点可以处在很不同的壳层。

Output输出图

k-核分解——手写剥离算法并与 NetworkX 对照,比较核数与度:输出图 1

Output文字输出

手写剥离算法与 nx.core_number 一致: True
幂律随机网络: r = -0.10, 最大核数 k_s^max = 9, 最内核节点数 = 19, 最内核内部密度 = 0.70, 核数与度的 Spearman = 0.91
同配重连(度序列相同): r = +0.05, 最大核数 k_s^max = 20, 最内核节点数 = 25, 最内核内部密度 = 0.95, 核数与度的 Spearman = 0.86
幂律随机网络中度为 6 的节点分布在这些壳层: [2, 3, 4]

Code代码

# 实现 Batagelj–Zaversnik 的“按当前度从小到大剥离”思想(这里用简单的最小堆,复杂度 O(M log N)),
# 与 nx.core_number 对照;再在一个幂律随机网络与一个“度相同但枢纽之间互连更多”的网络上比较:
# (不用 BA 网络:BA 网络每个新节点恰好带 m 条边,整张图就是一个 m-核,壳层结构是平凡的。)
# 最内核大小、最内核内部密度,以及核数与度的 Spearman 相关。
# 解读:核数度量的是“嵌在多稠密的子图里”,与度高度相关但不等同;度相同的节点可以处在很不同的壳层。
import heapq
import numpy as np
import networkx as nx
from scipy.stats import spearmanr


def core_numbers(G):
    deg = dict(G.degree())
    heap = [(d, v) for v, d in deg.items()]
    heapq.heapify(heap)
    removed, core, k = set(), {}, 0
    while heap:
        d, v = heapq.heappop(heap)
        if v in removed or d != deg[v]:
            continue                      # 过期的堆元素
        k = max(k, d)                     # 当前壳层编号只增不减
        core[v] = k
        removed.add(v)
        for u in G[v]:
            if u not in removed:
                deg[u] -= 1
                heapq.heappush(heap, (deg[u], u))
    return core


rng = np.random.default_rng(2)
deg_seq = np.minimum(np.floor((1 - rng.random(3000)) ** (-1 / 1.3)).astype(int), 200)   # 幂律,gamma≈2.3,k_min=1
if deg_seq.sum() % 2:
    deg_seq[0] += 1
G = nx.Graph(nx.configuration_model(deg_seq.tolist(), seed=2))
G.remove_edges_from(nx.selfloop_edges(G))
core = core_numbers(G)
print("手写剥离算法与 nx.core_number 一致:", core == nx.core_number(G))

# 一个度序列相同、但用同配重连让高度节点彼此相连的网络
def assort_rewire(G, n_swaps, seed):
    rng = np.random.default_rng(seed)
    G = G.copy(); dg = dict(G.degree()); edges = list(G.edges()); done = 0
    while done < n_swaps:
        i, j = rng.choice(len(edges), 2, replace=False)
        (a, b), (c, d) = edges[i], edges[j]
        if len({a, b, c, d}) < 4:
            continue
        s = sorted([a, b, c, d], key=lambda x: dg[x], reverse=True)
        new = [(s[0], s[1]), (s[2], s[3])]
        if any(G.has_edge(*e) for e in new):
            continue
        G.remove_edges_from([(a, b), (c, d)]); G.add_edges_from(new)
        edges[i], edges[j] = new; done += 1
    return G


Ga = assort_rewire(G, G.number_of_edges(), seed=3)
for name, H in [("幂律随机网络", G), ("同配重连(度序列相同)", Ga)]:
    cn = nx.core_number(H)
    kmax = max(cn.values())
    inner = [v for v in H if cn[v] == kmax]
    sub = H.subgraph(inner)
    deg = dict(H.degree())
    rho = spearmanr([deg[v] for v in H], [cn[v] for v in H])[0]
    print(f"{name}: r = {nx.degree_assortativity_coefficient(H):+.2f}, 最大核数 k_s^max = {kmax}, "
          f"最内核节点数 = {len(inner)}, 最内核内部密度 = {nx.density(sub):.2f}, 核数与度的 Spearman = {rho:.2f}")

# 同一度的节点可以位于不同壳层
deg = dict(G.degree())
k_ex = 6
shells = sorted({core[v] for v in G if deg[v] == k_ex})
print(f"幂律随机网络中度为 {k_ex} 的节点分布在这些壳层: {shells}")

# 作图:度 vs 核数(加少量抖动以显示重叠点)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
jit = np.random.default_rng(0)
for a, H, ttl in [(ax[0], G, "power-law random network"), (ax[1], Ga, "assortative rewiring (same degrees)")]:
    cn = nx.core_number(H)
    x = np.array([H.degree(v) for v in H]); y = np.array([cn[v] for v in H], float)
    a.scatter(x, y + jit.uniform(-0.2, 0.2, len(y)), s=6, alpha=0.4)
    a.set_xscale("log"); a.set_xlabel("degree k"); a.set_title(f"{ttl}\nmax core = {int(y.max())}", fontsize=10)
ax[0].set_ylabel("core number")
plt.tight_layout(); plt.show()

Run在浏览器中运行

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

k-核分解——手写剥离算法并与 NetworkX 对照,比较核数与度