3. 蛋白质互作数据格式:基本文件格式与数据清洗

好,咱们进入实操环节。做蛋白质互作联合学习,第一个拦路虎就是数据格式。我见过太多人代码写得飞起,结果栽在数据解析上。说白了,这章就是教你怎么跟三种主流格式打交道——TSV、GML、PSI-MI XML。

3.1 三种格式的江湖地位

你想想看,做PPI研究,数据源五花八门。INTACT、BioGRID、HPRD……每个数据库输出的格式都带点自己的脾气。我个人习惯把它们分成三代:

  • TSV/CSV:最朴实的表格格式,一列是蛋白A,一列是蛋白B,简单粗暴。像BioGRID就爱用它。
  • GML(Graph Modeling Language):图形化格式,专门用来描述网络结构。节点和边分得很清楚,图算法用起来方便。
  • PSI-MI XML:国际标准,信息最全。但XML嘛,你懂的,解析起来有点绕。
我的建议: 项目初期可以先用TSV快速验证。等跑通了,再用PSI-MI XML做正式实验。别一上来就跟XML死磕。

3.2 TSV格式:最直接的交互对

TSV是我用得最多的格式。原因很简单——不需要额外库就能读。每行代表一对互作关系,列之间用Tab分隔。

# BioGRID互作数据示例(简化版)
# interactor_A    interactor_B    experimental_system    confidence_score
P01116    P01111    Two-hybrid    0.85
P04637    P04629    Co-crystal    0.92
P01116    P04637    Affinity Capture-MS    0.78

记住,不是所有TSV都这么清爽。我在项目中遇到过有些数据库把列顺序藏得特别深,得仔细读README。嗯,这里要注意——一定要检查表头行。有些文件带'#'注释,有些直接就是数据行。

解析小技巧

我个人习惯用Python的csv模块,指定delimiter='\t'。别用split('\t'),万一字段里嵌了引号就悲剧了。

import csv

def load_ppi_tsv(filepath):
    interactions = []
    with open(filepath, 'r', encoding='utf-8') as f:
        # 跳过注释行
        lines = [line for line in f if not line.startswith('#')]
        reader = csv.reader(lines, delimiter='\t')
        for row in reader:
            if len(row) >= 2:
                interactions.append((row[0].strip(), row[1].strip()))
    return interactions

3.3 GML格式:把图结构读出来

GML的好处是节点和边都带属性。做过图神经网络的应该眼熟——节点特征、边权重,这些都能直接塞进GML里。

# 一个简单PPI网络的GML表示
graph [
  directed 0
  node [
    id 0
    label "P01116"
    domain "RAS"
    species "Human"
  ]
  node [
    id 1
    label "P04637"
    domain "p53"
    species "Human"
  ]
  edge [
    source 0
    target 1
    interaction_type "physical"
    confidence 0.88
  ]
]

解析GML,我推荐用networkx。它的read_gml()函数相当成熟,但有个坑——GML对数值和字符串很敏感。我曾经遇到一个文件,label字段用的双引号少了一个,整个图就读不进来。花了我半天排查。

import networkx as nx

def parse_ppi_gml(filepath):
    # 注意:GML文件不能有中文注释,否则会报编码错误
    try:
        G = nx.read_gml(filepath, label='label')
        print(f"节点数: {G.number_of_nodes()}")
        print(f"边数: {G.number_of_edges()}")
        return G
    except Exception as e:
        print(f"解析失败: {e}")
        return None
避坑: GML的id必须是整数,而且不能有空洞。如果你自己构造GML,记得id连续性。我曾经因为删了几个节点但没更新id,导致整个图重建时索引错乱。

3.4 PSI-MI XML:数据金矿,但挖起来累

PSI-MI XML是HUPO(人类蛋白质组组织)推的标准。信息量最大——参与互作的蛋白域、实验方法、引用文献、信心分数……全都塞进XML里了。

但问题也来了:层级太深。一个互作记录,光包裹它的标签就有四五层。看个片段你就懂了:

<?xml version="1.0" encoding="UTF-8"?>
<entrySet xmlns="net:sf:psidev:mi" level="3" version="4">
  <entry>
    <interactionList>
      <interaction id="1">
        <participantList>
          <participant id="2">
            <interactorRef>10</interactorRef>
            <biologicalRole>
              <names>
                <shortLabel>enzyme</shortLabel>
              </names>
            </biologicalRole>
          </participant>
          <participant id="3">
            <interactorRef>20</interactorRef>
          </participant>
        </participantList>
      </interaction>
    </interactionList>
  </entry>
</entrySet>

解析这个?用Python标准库xml.etree.ElementTree能搞定,但代码会很长。我建议用专门的工具——有的数据库提供了REST API,可以直接拉JSON格式。如果一定要啃XML,记得先看schema。

三种格式对比

特性 TSV GML PSI-MI XML
存储内容 边列表 节点+边+属性 全量元数据
可读性 ★★★★★ ★★★☆☆ ★★☆☆☆
解析难度 低 中 高
适合场景 快速原型 图分析 数据库集成
文件大小 小 中 大
标准化程度 低 中 高

3.5 数据清洗:别让脏数据毁了模型

数据格式搞定了?先别急。从数据库直接扒下来的数据,十个有九个是脏的。我自己处理过的最大一个数据集,2万条互作记录里,有30%需要清洗。

清洗流程

  1. 去除重复互作对:两个蛋白顺序调换不算新边。用集合判重,或者统一排序(蛋白A字典序<蛋白B字典序)再查重。
  2. 标准化蛋白ID:UniProt ID有长有短,还有带isoform的(比如P01116-2)。我习惯统一用UniProt AC,不要版本号,不要isoform后缀。
  3. 过滤自环:蛋白自己跟自己互作?生物学上意义不大,联合学习里还会造成自环影响。果断去掉。
  4. 处理缺失值:有些记录confidence_score是空的。看情况——如果只有少量缺失,填中位数;如果大面积缺失,干脆不用这个特征。
  5. 合并多源数据:从BioGRID和INTACT同时拉数据,ID形式可能不一样。一定要统一到同一个命名空间。比如都转UniProt AC。
实操建议: 建立一个清洗日志文件。每条清洗规则执行后,输出处理了多少行、删除了多少、修改了多少。这样出错了能回溯。

一个完整的清洗管道示例

import pandas as pd

def clean_ppi_data(df):
    # step 1: 去重(不区分方向)
    df['sorted_pair'] = df.apply(
        lambda x: tuple(sorted([x['protein_a'], x['protein_b']])), axis=1
    )
    df = df.drop_duplicates(subset='sorted_pair')
    
    # step 2: 删除自环
    df = df[df['protein_a'] != df['protein_b']]
    
    # step 3: ID标准化(假设已有UniProt映射表)
    id_map = {"P53_HUMAN": "P04637", "RAS_HUMAN": "P01116"}  # 示例
    df['protein_a'] = df['protein_a'].map(id_map).fillna(df['protein_a'])
    df['protein_b'] = df['protein_b'].map(id_map).fillna(df['protein_b'])
    
    # step 4: 缺失值处理
    df['confidence_score'] = df['confidence_score'].fillna(df['confidence_score'].median())
    
    return df[['protein_a', 'protein_b', 'confidence_score', 'experiment_type']]

3.6 一张图看清数据流转

下面我画了一张图,把从原始数据到干净特征的整体流程展示出来。你用这个图做参考,对照自己的项目流程,应该能少走很多弯路。

PPI数据格式处理流程 原始数据源 TSV GML PSI-MI XML 格式解析 数据清洗与标准化 去重 → 过滤自环 → 标准化ID → 缺失值处理 多源合并 → 格式统一 → 质量检查 干净PPI特征集 → 联合学习 清洗日志 质量控制

3.7 多源数据的统一之战

实际项目中你很少只用单一数据库。INTACT、BioGRID、HPRD、STRING……格式不同,ID不同,甚至互作定义也不同。怎么办?

我的经验是:先统一到UniProt AC,再检查互作方向。有些数据库会记录“A与B互作”,有些会标注“A是kinase,B是substrate”。如果你做的是无向图,直接把方向信息扔掉。但有些下游任务(比如预测互作类型)需要保留下文信息——这时候数据清洗就得多留个心眼。

特别注意: ID映射是踩坑重灾区。UniProt AC有主ID和备用ID,有时还会更新。我做过一次批量映射,发现3%的ID已经废弃了。建议定期从UniProt下载最新的ID映射表。