残基命名与标准氨基酸:三字母代码、单字母代码、非标准残基处理

处理PDB文件,说白了你每天都在跟残基命名打交道。我刚开始接触结构生物学那会儿,就被ATOM记录里的三字母代码搞得晕头转向——明明看着是LYS,心里还想着这是不是赖氨酸,结果库里面写的是LYS,单元里写的是L、Y、S,完全对不上。嗯,今天我们就把这个事情彻底讲清楚。

标准氨基酸的命名体系

PDB格式使用两种残基标识符:三字母代码和单字母代码。前者是PDB文件默认的存储格式,后者常用于序列比对和展示。

这里是我个人习惯的一个对比表,你写脚本的时候直接照着核对就行:

氨基酸全名三字母代码单字母代码侧链性质
丙氨酸ALAA非极性,脂肪族
精氨酸ARGR碱性,带正电
天冬酰胺ASNN极性,不带电
天冬氨酸ASPD酸性,带负电
半胱氨酸CYSC极性,含巯基
谷氨酰胺GLNQ极性,不带电
谷氨酸GLUE酸性,带负电
甘氨酸GLYG非极性,柔性最大
组氨酸HISH碱性,含咪唑环
异亮氨酸ILEI非极性,支链脂肪族
亮氨酸LEUL非极性,支链脂肪族
赖氨酸LYSK碱性,带正电
甲硫氨酸METM非极性,含硫
苯丙氨酸PHEF非极性,芳香族
脯氨酸PROP非极性,亚氨基
丝氨酸SERS极性,含羟基
苏氨酸THRT极性,含羟基
色氨酸TRPW非极性,芳香族
酪氨酸TYRY极性,含酚羟基
缬氨酸VALV非极性,支链脂肪族

我个人习惯在写解析脚本时,会把三字母到单字母的映射写成一个静态字典,而不是用if-else判断。因为PDB里某些非标准残基也遵循类似的命名规则,你有个字典能更方便地做扩展。

三字母代码的ATOM记录格式

在PDB文件的ATOM或HETATM记录中,残基名称固定占据第18-20列,右对齐。你写解析器的时候,千万别按字符串trim来做,要按字符位置截取。

ATOM      1  N   ALA A   1       6.247  18.672  -9.654  1.00  0.00           N  
ATOM      2  CA  ALA A   1       7.056  17.432  -9.507  1.00  0.00           C  
ATOM      3  C   ALA A   1       8.566  17.672  -9.473  1.00  0.00           C  
# Python示例:截取三字母代码
resname = line[17:20].strip()   # 第18-20列,注意索引从0开始
chain_id = line[21]             # 第22列,链标识

曾经我在处理一批老旧的PDB文件时,发现某些条目把残基名写在了第17列,导致截取失败。原因是部分早期的X射线结构使用了格式化不严格的记录。所以,稳妥的做法是先检查第17列的字符是否为空格,如果不是,就得用自定义的偏移量做容错。

非标准残基与修饰残基

PDB里不只有20种标准氨基酸。晶体结构解析过程中,经常会遇到修饰残基、配体、水分子、甚至错误建模的残基。你想想看,一个磷酸化的丝氨酸,PDB里写成了SEP,而不是SER。如果你用单纯的三字母到单字母映射去处理,就会丢失这个修饰信息。

常见的非标准残基包括:

  • 修饰氨基酸:磷酸化丝氨酸(SEP)、磷酸化苏氨酸(TPO)、磷酸化酪氨酸(PTR)、乙酰化赖氨酸(ALY)
  • 非天然氨基酸:正亮氨酸(NLE)、正缬氨酸(NVA)、鸟氨酸(ORN)
  • 水分子:HOH或SOL
  • 配体与离子:ATP、NAD、ZN、MG等
  • 未知或未解析残基:UNK或XXX

我当年在一个蛋白质-配体复合物的项目中,因为没处理好SEP残基,导致后续的力场参数分配全错了——那家伙把磷酸基团当作侧链原子一锅炖,能量最小化直接跑飞。从那以后,我写预处理流程时一定会在PDB解析阶段单独处理非标准残基,并建立一张映射表,把修饰残基还原成标准类型,同时保留修饰标记。

处理策略:统一化还是保留原貌?

这里其实有两种思路,取决于你的下游任务:

  1. 统一化策略:把非标准残基映射到邻近的标准残基。比如SEP→SER,TPO→THR。这种策略适合做序列比对、保守性分析、机器学习特征提取。
  2. 保留原貌策略:保留所有修饰标记,把SEP当作独立残基类别。这种策略适合做结合口袋分析、相互作用计算、力场参数分配。

我个人建议用一个YAML配置文件来管理这种映射关系,保持可追踪性。这样下次换一个数据集也不用手动改代码。

# standardize_nonstandard.yaml
# 修饰氨基酸 → 标准氨基酸映射
SEP: SER   # 磷酸化丝氨酸 → 丝氨酸
TPO: THR   # 磷酸化苏氨酸 → 苏氨酸
PTR: TYR   # 磷酸化酪氨酸 → 酪氨酸
ALY: LYS   # 乙酰化赖氨酸 → 赖氨酸
CSO: CYS   # 巯基氧化半胱氨酸 → 半胱氨酸
MSE: MET   # 硒代甲硫氨酸 → 甲硫氨酸
HID: HIS   # 质子化组氨酸 → 组氨酸
HIE: HIS   # 去质子化组氨酸 → 组氨酸
HIP: HIS   # 双质子化组氨酸 → 组氨酸

你解析的时候,可以这样加载:

import yaml

with open("standardize_nonstandard.yaml", "r") as f:
    mapping = yaml.safe_load(f)

def standardize_resname(resname):
    """将非标准残基映射为标准残基,如果找不到映射则返回原值。"""
    return mapping.get(resname, resname)

特殊案例:组氨酸的质子化状态

组氨酸在PDB中有三种常见的命名变体:HID(δ-质子化)、HIE(ε-质子化)、HIP(双质子化)。这三种构型在静电力学计算中差异很大。你要是直接统一映射成HIS,相当于抹掉了这个关键信息。

我做过一个蛋白质pKa预测的项目,里面有一处关键组氨酸被建模成HID形式,而默认的残基库把它当作标准HIS处理,结果pKa偏移了1.2个单位,整个pH-活性曲线对不上实验数据。所以,如果你的下游分析涉及氢键网络、静电计算或质子化状态,务必保留原始的HID/HIE/HIP标记,至少保留一份元数据副本。

水分子与配体的处理

很多初学者会忽略水分子和配体残基。PDB里水分子统一用HOH或SOL表示(取决于来源),配体则使用HETATM记录。你写预处理流程时,需要根据记录类型来分流:

  • ATOM:蛋白质或核酸的主链/侧链原子
  • HETATM:非标准残基、水分子、配体、离子

这里有个小技巧:不要只看残基名来区分,因为某些非标准残基(比如SEP)也会出现在ATOM记录中。你要结合record_type和resname两个字段做判断。

def is_protein_atom(record_type, resname):
    """判断原子是否属于蛋白质主链/侧链"""
    if record_type != "ATOM":
        return False
    standard_residues = {
        "ALA","ARG","ASN","ASP","CYS","GLN","GLU","GLY",
        "HIS","ILE","LEU","LYS","MET","PHE","PRO","SER",
        "THR","TRP","TYR","VAL",
        # 修饰残基也视为蛋白原子
        "SEP","TPO","PTR","ALY","CSO","MSE",
        "HID","HIE","HIP"
    }
    return resname in standard_residues
PDB残基命名处理流程图 PDB ATOM / HETATM 行 提取残基名(第18-20列) 三字母 → 单字母映射 标准残基 直接映射 + 链标识 修饰/非标准残基 查映射表 → 统一化 配体/水/离子 标记分离,不参与映射 输出:统一残基表 + 修饰元数据 遇 HOH / SOL 直接跳过

常见坑点与避坑指南

我整理了几个你一定会遇到的坑:

  • 大小写问题:PDB文件里残基名全部大写,但有些第三方库或旧版本PDB会输出小写。解析时最好先做 .upper()。
  • 空列填充:残基名不足3字符时,左侧补空格,右侧对齐。比如CYS的C往往会被解析成CY,因为第18列是空格。你截取的时候要用line[17:20],而不是line.split()[3],因为split会把空格压缩掉。
  • 插入残基代码:PDB格式有插入残基(insertion code)的概念,如果两条链在同一位置有不同残基,会用字母后缀区分(如100A、100B)。你在做残基编号映射时,一定要把resSeq和iCode拼起来作为唯一键。
  • 内存残基:某些PDB文件会出现 .(三个点)或UNK作为残基名,通常表示该位置未解析。稳妥的做法是直接跳过或者记录为缺失。

曾经我在解析一个来自EMDB的冷冻电镜结构时,发现某个链在中间位置突然出现了一个残基名为 1(空格空格1)的条目,搞得程序直接崩溃。后来排查发现,这是数据准备阶段的一个残基编号标记错误,原子坐标却是正常的。所以,你一定要对残基名做合法性校验,只通过白名单方式处理。

总结一下核心要点

残基命名这件事,说白了就是:三字母代码是存储格式,单字母代码是计算格式,非标准残基必须单独管理。你按这个思路去设计预处理流程,能省掉不少回头填坑的时间。

我建议你在实际的PDB预处理程序中,把残基命名规范化的模块独立出来做单元测试,特别是针对那些修饰残基、水分子和异常命名,提前写几个边界case的测试用例。如果你刚入门,可以先用PDBbind或PDBbind refine数据集练手,里面既有标准残基也有大量配体,能逼着你做完整的分支处理。