50 分钟真实数据快照已执行的网页阅读版

解读蛋白质物理互作网络

将 12 个 DNA 损伤响应蛋白映射到 STRING,保留孤立节点,比较阈值并查看证据来源。

生物信息学 · 第 05 课🔋 离线核心分析⏱ 50 分钟内嵌真实数据📦 numpy · pandas · matplotlib · networkx · IPython

目标与运行方法

本课适合具备 Python 基础的本科生。先阅读每一步的方法与图注,再修改参数。AI 练习为可选部分,不需要 AI 账号即可完成核心课程。数据写在 notebook metadata 中,不需要另下载数据文件。请保持 notebook 已保存到当前工作文件夹。

学习任务是理解本课的研究问题、执行透明的计算、校验结果,并说明结论的边界。

环境与参数

In [1] · Python
from pathlib import Path
import base64, gzip, hashlib, json, platform
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display, HTML
import networkx as nx
RECIPE_ID='05-string-physical-network'
OUTPUT_DIR=Path.cwd()/"results"/RECIPE_ID
plt.rcParams.update({"figure.dpi":120,"font.size":10,"axes.spines.top":False,"axes.spines.right":False,
                      "axes.prop_cycle":plt.cycler(color=["#18785f","#416aa6","#bc6541","#9974a5"])})
print("Python:",platform.python_version(),"; NumPy:",np.__version__,"; Pandas:",pd.__version__)
print("Working folder:",Path.cwd())
print("Core data mode: embedded snapshot; no network requests")
Python: 3.13.13 ; NumPy: 2.5.3 ; Pandas: 3.0.6
Working folder: /Users/zhiluo/Developer/vibeit-cookbook/cookbook/downloads
Core data mode: embedded snapshot; no network requests

读取内嵌快照

In [2] · Python
def load_snapshot(recipe_id, expected_sha256, snapshot_key):
    """Read embedded data from this notebook, including a renamed copy."""
    for path in sorted(Path.cwd().glob("*.ipynb")):
        try:
            notebook = json.loads(path.read_text(encoding="utf-8"))
            metadata = notebook.get("metadata", {}).get("vibeit_cookbook", {})
            blob = metadata.get("snapshots", {}).get(snapshot_key)
            if metadata.get("recipe_id") != recipe_id or not blob:
                continue
            if blob.get("sha256") != expected_sha256:
                continue
            raw = gzip.decompress(base64.b64decode(blob["gzip_base64"], validate=True))
            if hashlib.sha256(raw).hexdigest() != expected_sha256:
                raise ValueError("Embedded snapshot checksum mismatch: " + snapshot_key)
            return json.loads(raw)
        except (OSError, json.JSONDecodeError):
            continue
    raise FileNotFoundError(
        "Save/open the complete .ipynb in the active working folder. "
        "The embedded snapshot could not be located for " + recipe_id)

SNAPSHOT_HASHES={'network': '19b6b87b2a66cd2e11f0c8c82ee712659dc7e8a9eaded9e36238604a5223d136'}
def snapshot(key):
    return load_snapshot(RECIPE_ID,SNAPSHOT_HASHES[key],key)
print("Embedded snapshots:",list(SNAPSHOT_HASHES))
Embedded snapshots: ['network']

方法函数

In [3] · Python
def physical_graph(snapshot, threshold=0.7):
    if not 0 <= threshold <= 1:
        raise ValueError("Network threshold must be between 0 and 1")
    graph = nx.Graph()
    for mapping in snapshot["mapping"]:
        graph.add_node(mapping["preferredName"],string_id=mapping["stringId"])
    for row in snapshot["edges"]:
        a, b = row["preferredName_A"], row["preferredName_B"]
        if a == b or row["score"] < threshold:
            continue
        if row["ncbiTaxonId"] != 9606 or a not in graph or b not in graph:
            raise ValueError("Unexpected network species or neighbor")
        attrs = {key:float(row.get(key,0)) for key in ("score","escore","dscore","tscore")}
        if not graph.has_edge(a,b) or graph[a][b]["score"] < attrs["score"]:
            graph.add_edge(a,b,**attrs)
    return graph

print("Method ready: physical_graph")
Method ready: physical_graph

分步分析

1. 阅读查询、映射及网络类型

查询包含 12 个人类 DNA 损伤响应相关蛋白。STRING 标识符指向蛋白,构图前需要解析基因符号。本快照采用 network_type=physical、物种 9606,不额外添加邻居;冻结边集从 API score 400 开始,因此可比较 ≥0.4 的阈值。STRING 物理网络可以包含预测证据,并不意味着每条边都在某个实验中被直接测量。

In [4] · Python
network=snapshot("network")
mapping=pd.DataFrame(network["mapping"])
display(mapping[[c for c in ["queryItem","queryIndex","stringId","preferredName","ncbiTaxonId"] if c in mapping]])
print("Query:",network["query"])
print("Snapshot API threshold:",network["request"]["required_score"],"; added nodes:",network["request"]["add_nodes"])
print("Version:",network["version"])
queryItem queryIndex stringId preferredName ncbiTaxonId
0 TP53 0 9606.ENSP00000269305 TP53 9606
1 MDM2 1 9606.ENSP00000258149 MDM2 9606
2 MDM4 2 9606.ENSP00000356150 MDM4 9606
3 ATM 3 9606.ENSP00000278616 ATM 9606
4 ATR 4 9606.ENSP00000343741 ATR 9606
5 CHEK1 5 9606.ENSP00000391090 CHEK1 9606
6 CHEK2 6 9606.ENSP00000372023 CHEK2 9606
7 BRCA1 7 9606.ENSP00000418960 BRCA1 9606
8 BRCA2 8 9606.ENSP00000369497 BRCA2 9606
9 CDKN1A 9 9606.ENSP00000384849 CDKN1A 9606
10 BAX 10 9606.ENSP00000293288 BAX 9606
11 BCL2 11 9606.ENSP00000381185 BCL2 9606
Query: ['TP53', 'MDM2', 'MDM4', 'ATM', 'ATR', 'CHEK1', 'CHEK2', 'BRCA1', 'BRCA2', 'CDKN1A', 'BAX', 'BCL2']
Snapshot API threshold: 400 ; added nodes: 0
Version: [{'stable_address': 'https://version-12-5.string-db.org', 'string_version': '12.5'}]

2. 构图并保留所有查询蛋白

先根据映射创建节点,再添加通过阈值的边。没有边的查询蛋白仍保留在图中。孤立节点可能反映证据不足或查询范围限制,不能证明该蛋白没有互作伙伴。排除自环,对重复无向边保留最大的综合得分。

In [5] · Python
THRESHOLD=.7
graph=physical_graph(network,THRESHOLD)
print("Nodes / edges / isolates:",graph.number_of_nodes(),graph.number_of_edges(),list(nx.isolates(graph)))
positions=nx.spring_layout(graph,seed=42,weight="score")
fig,ax=plt.subplots(figsize=(8,6))
nx.draw_networkx_edges(graph,positions,ax=ax,alpha=.5,width=[1+2*graph[a][b]["score"] for a,b in graph.edges],edge_color="#829b8f")
nx.draw_networkx_nodes(graph,positions,ax=ax,node_size=[450+80*graph.degree(n) for n in graph],
    node_color=["#18785f" if n=="TP53" else "#c4ded3" for n in graph],edgecolors="white",linewidths=1.5)
nx.draw_networkx_labels(graph,positions,ax=ax,font_size=10)
ax.set_title("STRING physical network: human query, confidence ≥0.7");ax.axis("off")
plt.tight_layout();plt.show()
Nodes / edges / isolates: 12 32 []
Computed figure from this code cell

3. 查看证据来源

综合得分表示置信度,不是结合亲和力或互作强度。escore 表示实验证据,dscore 表示数据库证据,tscore 表示文本挖掘证据。不能将这些字段当成独立概率直接相加;分别查看它们,才能判断高综合得分是否具有与你的问题相符的证据。

In [6] · Python
edge_table=pd.DataFrame([dict(protein_a=a,protein_b=b,**attrs) for a,b,attrs in graph.edges(data=True)],
    columns=["protein_a","protein_b","score","escore","dscore","tscore"])
evidence=edge_table.sort_values("score",ascending=False).head(15)
fig,ax=plt.subplots(figsize=(7,5))
if len(evidence):
    im=ax.imshow(evidence[["score","escore","dscore","tscore"]],vmin=0,vmax=1,cmap="YlGnBu",aspect="auto")
    ax.set(xticks=range(4),xticklabels=["Combined","Experiments","Database","Text mining"],
           yticks=range(len(evidence)),yticklabels=evidence.protein_a+" — "+evidence.protein_b)
    fig.colorbar(im,ax=ax,label="Channel score")
else:ax.text(.5,.5,"No edges at this threshold",ha="center",transform=ax.transAxes)
ax.set_title("Evidence channels for the highest-score edges");plt.tight_layout();plt.show()
display(edge_table.head(10))
Computed figure from this code cell
protein_a protein_b score escore dscore tscore
0 TP53 MDM2 0.999 0.999 0.7 0.927
1 TP53 BAX 0.997 0.995 0.0 0.560
2 TP53 ATM 0.999 0.999 0.0 0.651
3 TP53 ATR 0.999 0.999 0.0 0.113
4 TP53 MDM4 0.999 0.999 0.7 0.760
5 TP53 BRCA2 0.999 0.999 0.0 0.190
6 TP53 CHEK2 0.999 0.999 0.0 0.518
7 TP53 BCL2 0.999 0.999 0.9 0.680
8 TP53 CDKN1A 0.999 0.999 0.0 0.343
9 TP53 CHEK1 0.999 0.999 0.0 0.223

4. 用同一个冻结边集比较阈值

比较时只修改阈值,始终保留 12 个节点,因此孤立节点数可以直接比较。网络更稠密并不一定意味着证据更好。快照未获取 <0.4 的边,在本地继续降低阈值无法恢复不存在于快照中的边;若要研究该范围,需要明确重新获取数据。

In [7] · Python
thresholds=[.4,.7,.9,.95,.99]
sensitivity=pd.DataFrame([dict(threshold=t,edges=g.number_of_edges(),isolates=len(list(nx.isolates(g))),
    components=nx.number_connected_components(g)) for t in thresholds for g in [physical_graph(network,t)]])
fig,ax=plt.subplots(figsize=(7,3))
ax.plot(sensitivity.threshold,sensitivity.edges,"o-",label="Edges")
ax.plot(sensitivity.threshold,sensitivity.isolates,"s-",label="Isolates")
ax.set(xlabel="Minimum combined confidence",ylabel="Count",title="Network sensitivity to the confidence threshold")
ax.legend();plt.tight_layout();plt.show();display(sensitivity)
Computed figure from this code cell
threshold edges isolates components
0 0.40 38 0 1
1 0.70 32 0 1
2 0.90 27 0 1
3 0.95 23 0 1
4 0.99 20 0 1

5. 计算拓扑并解释局限

度数统计查询范围内的邻居。无权介数统计最短路径中介作用,避免把置信度伪装成物理距离。社区检测使用置信度权重及固定随机种子。中心性或社区标签不能证明必需性、因果作用或功能模块;查询本身已经偏向特定通路。

In [8] · Python
centrality=nx.betweenness_centrality(graph,weight=None)
communities=nx.community.louvain_communities(graph,weight="score",seed=42) if graph.number_of_edges() else [{n} for n in graph]
membership={n:i+1 for i,nodes in enumerate(communities) for n in nodes}
node_table=pd.DataFrame([dict(protein=n,degree=graph.degree(n),betweenness=centrality[n],community=membership[n],
    string_id=graph.nodes[n]["string_id"]) for n in graph]).sort_values(["degree","protein"],ascending=[False,True])
display(node_table)
protein degree betweenness community string_id
0 TP53 11 0.434848 1 9606.ENSP00000269305
3 ATM 7 0.031818 2 9606.ENSP00000278616
6 CHEK2 7 0.031818 2 9606.ENSP00000372023
7 BRCA1 6 0.009091 2 9606.ENSP00000418960
5 CHEK1 6 0.030303 2 9606.ENSP00000391090
4 ATR 5 0.000000 2 9606.ENSP00000343741
1 MDM2 5 0.030303 1 9606.ENSP00000258149
2 MDM4 5 0.040909 1 9606.ENSP00000356150
8 BRCA2 4 0.000000 2 9606.ENSP00000369497
11 BCL2 3 0.009091 1 9606.ENSP00000381185
9 CDKN1A 3 0.000000 1 9606.ENSP00000384849
10 BAX 2 0.000000 1 9606.ENSP00000293288

6. 校验并导出可移植网络

GraphML 保留节点标识符与本课展示的置信度字段。notebook 内部原始快照保留完整返回字段,包括未展示的证据。阈值审计表也应导出,让读者能够复现网络,而不只是查看一张图片。

In [9] · Python
assert graph.number_of_nodes()==12
assert nx.number_of_selfloops(graph)==0
assert all(attrs["score"]>=THRESHOLD for _,_,attrs in graph.edges(data=True))
OUTPUT_DIR.mkdir(parents=True,exist_ok=True)
edge_table.to_csv(OUTPUT_DIR/"physical-edges.csv",index=False)
node_table.to_csv(OUTPUT_DIR/"proteins.csv",index=False)
sensitivity.to_csv(OUTPUT_DIR/"threshold-audit.csv",index=False)
nx.write_graphml(graph,OUTPUT_DIR/"physical-network.graphml")
print("Checks passed; GraphML, nodes, edges and threshold audit exported")
Checks passed; GraphML, nodes, edges and threshold audit exported

自主练习与参考答案

  1. 为什么孤立查询蛋白仍应显示在图中?
  2. 将 THRESHOLD 从 0.7 改为 0.95,与敏感性表中的孤立节点数比较。

参考解释。 删除孤立节点会改变节点背景,掩盖查询范围内的证据缺口。快照及节点固定时,升高阈值可以删除边,但不能增加边。

先保留当前 notebook 副本,再修改参数。把新图与原图一起比较,并记录改变了哪些输入、哪些计算和哪些结论。

可选的 AI 编程练习

提示词 1

用一个共享的固定随机布局,并排展示 0.7 与 0.95 的网络,保留全部 12 个节点,表格中显示 STRING 标识符。

提示词 2

添加实验证据得分 ≥0.7 的边列表,明确这是冻结物理网络内部的额外证据过滤。

人工验收。 检查各阈值均有 12 个节点、没有自环或额外邻居、物种为人类,且随阈值升高边数不增加。

检查修改后的源代码,再手动运行受影响单元及后续校验。要求助手解释修改的目的、使用的包和数据来源,并用实际输出核对解释。

可选联网扩展

In [10] · Python
RUN_ONLINE_UPDATE=False
if RUN_ONLINE_UPDATE:
    import requests
    try:
        response=requests.get('https://string-db.org/api/json/version',timeout=20)
        response.raise_for_status()
        print("Source:",response.url)
        print(response.text[:700])
    except requests.RequestException as error:
        print("Online extension unavailable:",type(error).__name__,str(error)[:160])
else:
    print("Online extension skipped; embedded core data unchanged")
Online extension skipped; embedded core data unchanged

来源、快照与下一步

  • STRING physical network — CC BY 4.0 (STRING data); retrieved 2026-10-09. SHA-256 bfae5cea28cd3e90ff8bc12f768bfcfd66a6037c25f7c47fe2a81baaa7ea222f. Original response preserved in the embedded snapshot.

  • STRING identifier mapping — CC BY 4.0 (STRING data); retrieved 2026-10-09. SHA-256 77fc78421492b54895dc862e7a8f4de4a84c61db73fdfbf0696ccee24fe1c371. Original response preserved in the embedded snapshot.

下一步。 在保留本课的数据检查、参数记录和结果校验之后,将相同方法用于自己的公开或已获授权数据。记录研究设计与方法限制,不把示例结果当成新实验的验证。完整数据许可与生成说明见 cookbook 网页。