使用 MMseqs2 计算多个 DTI 数据集的蛋白序列相似度

1. 背景

在 DTI(Drug-Target Interaction)预测任务中,不同数据集之间的蛋白空间是否相似,会影响模型的泛化能力。例如,在 cold protein split 或跨数据集测试中,如果测试集蛋白虽然 ID 没有出现在训练集中,但和训练集蛋白序列高度相似,那么这个任务实际上并不算特别"冷启动"。

因此,我们可以使用 MMseqs2 计算蛋白序列之间的相似度。这里主要关注两类指标:

  1. 数据集内部蛋白最近邻相似度

    • 每个蛋白在同一个数据集内部,找到除自身外最相似的蛋白。
    • 用于分析数据集内部蛋白冗余程度。
  2. 数据集之间蛋白最大相似度

    • 每个蛋白在另一个数据集中找到最相似的蛋白。
    • 用于分析不同数据集之间 target space 的覆盖程度。

本文以四个 DTI 数据集为例:

复制代码
SNAP
DRH
TTD
DrugBank

数据目录如下:

复制代码
/home/qfchen/DTIPred_Plus/database

结果保存目录如下:

复制代码
/home/qfchen/DTIPred_Plus/analysis/results

2. 安装 MMseqs2

如果服务器上还没有安装 MMseqs2,可以使用 conda 安装:

soedinglab/MMseqs2: MMseqs2: ultra fast and sensitive search and clustering suite

复制代码
conda install -c conda-forge -c bioconda mmseqs2

检查是否安装成功:

复制代码
mmseqs version

3. 从 ProtInfo.xlsx 提取 FASTA 文件

每个数据集的蛋白信息保存在:

复制代码
SNAP/final_data/ProtInfo.xlsx
DRH/final_data/ProtInfo.xlsx
TTD/final_data/ProtInfo.xlsx
DrugBank/final_data/ProtInfo.xlsx

每个 ProtInfo.xlsx 中至少包含两列:

复制代码
UNIPROTID
SEQUENCE

下面的脚本会读取四个数据集的蛋白序列,并按 UNIPROTID 去重,导出成 FASTA 文件。

python 复制代码
#!/usr/bin/env python
# File: export_protein_fasta.py

import os
import pandas as pd


BASE_DIR = "/home/qfchen/DTIPred_Plus/database"
SAVE_DIR = "/home/qfchen/DTIPred_Plus/analysis/results/protein_fasta"

DATASETS = {
    "SNAP": "SNAP/final_data/ProtInfo.xlsx",
    "DRH": "DRH/final_data/ProtInfo.xlsx",
    "TTD": "TTD/final_data/ProtInfo.xlsx",
    "DrugBank": "DrugBank/final_data/ProtInfo.xlsx",
}


def wrap_sequence(sequence, width=80):
    sequence = str(sequence).strip()
    return "\n".join(sequence[i:i + width] for i in range(0, len(sequence), width))


def export_one_dataset(dataset_name, rel_path):
    data_path = os.path.join(BASE_DIR, rel_path)
    df = pd.read_excel(data_path)[["UNIPROTID", "SEQUENCE"]].drop_duplicates("UNIPROTID")

    fasta_path = os.path.join(SAVE_DIR, f"{dataset_name}.fasta")
    with open(fasta_path, "w", encoding="utf-8") as f:
        for _, row in df.iterrows():
            uniprot_id = str(row["UNIPROTID"]).strip()
            sequence = str(row["SEQUENCE"]).strip()
            f.write(f">{dataset_name}|{uniprot_id}\n")
            f.write(wrap_sequence(sequence) + "\n")

    return {
        "Dataset": dataset_name,
        "Protein_N": len(df),
        "FASTA": fasta_path,
    }


def main():
    os.makedirs(SAVE_DIR, exist_ok=True)

    summary = []
    for dataset_name, rel_path in DATASETS.items():
        summary.append(export_one_dataset(dataset_name, rel_path))

    summary_df = pd.DataFrame(summary)
    summary_df.to_csv(os.path.join(SAVE_DIR, "protein_fasta_summary.csv"), index=False)
    print(summary_df.to_string(index=False))
    print(f"\nSaved protein FASTA files to: {SAVE_DIR}")


if __name__ == "__main__":
    main()

运行:

复制代码
python export_protein_fasta.py

运行后会得到:

复制代码
/home/qfchen/DTIPred_Plus/analysis/results/protein_fasta/SNAP.fasta
/home/qfchen/DTIPred_Plus/analysis/results/protein_fasta/DRH.fasta
/home/qfchen/DTIPred_Plus/analysis/results/protein_fasta/TTD.fasta
/home/qfchen/DTIPred_Plus/analysis/results/protein_fasta/DrugBank.fasta

FASTA header 形式如下:

复制代码
>SNAP|P05108
MLAKGLPPRSVLVKGCQTFLSAPREGLGRLRVPTG...

4. 使用 MMseqs2 计算蛋白相似度

MMseqs2 的基本命令格式如下:

复制代码
mmseqs easy-search query.fasta target.fasta output.tsv tmp_dir \
  --format-output query,target,pident,alnlen,evalue,bits \
  --threads 16

其中:

复制代码
query   查询蛋白
target  目标蛋白
pident  sequence identity,百分比形式,范围 0 到 100
alnlen  alignment length
evalue  E-value
bits    bit score

例如,计算 SNAP 到 DRH 的蛋白相似度:

复制代码
mmseqs easy-search \
  /home/qfchen/DTIPred_Plus/analysis/results/protein_fasta/SNAP.fasta \
  /home/qfchen/DTIPred_Plus/analysis/results/protein_fasta/DRH.fasta \
  /home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/mmseqs_tsv/SNAP_vs_DRH.tsv \
  /home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/tmp/SNAP_vs_DRH \
  --format-output query,target,pident,alnlen,evalue,bits \
  --threads 16

5. 批量运行四个数据集两两比较

四个数据集需要计算:

bash 复制代码
SNAP vs SNAP
SNAP vs DRH
SNAP vs TTD
SNAP vs DrugBank
DRH vs SNAP
DRH vs DRH
...
DrugBank vs DrugBank

一共是 4 × 4 = 16 组比较。

可以写一个 bash 脚本批量运行。

bash 复制代码
#!/bin/bash
# File: run_mmseqs_protein_similarity.sh

FASTA_DIR="/home/qfchen/DTIPred_Plus/analysis/results/protein_fasta"
OUT_DIR="/home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/mmseqs_tsv"
TMP_DIR="/home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/tmp"

mkdir -p "$OUT_DIR"
mkdir -p "$TMP_DIR"

DATASETS=("SNAP" "DRH" "TTD" "DrugBank")

for QUERY in "${DATASETS[@]}"; do
  for TARGET in "${DATASETS[@]}"; do
    echo "Running ${QUERY} vs ${TARGET}"

    mmseqs easy-search \
      "${FASTA_DIR}/${QUERY}.fasta" \
      "${FASTA_DIR}/${TARGET}.fasta" \
      "${OUT_DIR}/${QUERY}_vs_${TARGET}.tsv" \
      "${TMP_DIR}/${QUERY}_vs_${TARGET}" \
      --format-output query,target,pident,alnlen,evalue,bits \
      --threads 16
  done
done

保存为:

复制代码
run_mmseqs_protein_similarity.sh

运行:

复制代码
bash run_mmseqs_protein_similarity.sh

6. 后台静默运行

如果不想占用终端,也不想保存日志,可以使用:

复制代码
nohup bash run_mmseqs_protein_similarity.sh > /dev/null 2>&1 &

命令含义:

复制代码
nohup                 断开终端后继续运行
> /dev/null           标准输出不保存
2>&1                  错误输出也重定向到标准输出
&                     后台运行

运行后终端会显示类似:

复制代码
[1] 123456

其中 123456 是后台任务的进程号。


7. 查看运行状态

查看 bash 脚本是否还在运行:

复制代码
ps -ef | grep run_mmseqs_protein_similarity.sh | grep -v grep

查看 MMseqs2 进程:

复制代码
ps -ef | grep mmseqs | grep -v grep

查看后台任务:

复制代码
jobs -l

查看已经生成了多少结果文件:

复制代码
ls /home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/mmseqs_tsv

统计 .tsv 文件数量:

复制代码
ls /home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/mmseqs_tsv/*.tsv | wc -l

查看某个结果文件前几行:

复制代码
head /home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/mmseqs_tsv/SNAP_vs_DRH.tsv

8. MMseqs2 结果格式

每个 .tsv 文件包含六列:

复制代码
query target pident alnlen evalue bits

例如:

bash 复制代码
SNAP|P05108    DRH|P05108    100.0    521    0.0    1000
SNAP|P05108    DRH|Q9XXX1     45.2    300    1e-50  200

其中:

bash 复制代码
pident = sequence identity percentage

如果 pident = 85.6,表示两个蛋白 alignment 区域的序列一致性为 85.6%。


9. 后处理:提取每个蛋白的最大相似度

我们关心的是:

  1. 数据集内部

    • 例如 SNAP vs SNAP。
    • 需要去掉 query == target 的自身匹配。
    • 对每个 query 取最大 pident,得到同数据集内部最近邻相似度。
  2. 数据集之间

    • 例如 SNAP vs DRH。
    • 对每个 query 取最大 pident,得到该蛋白到另一个数据集的最大相似度。

下面是后处理脚本:

python 复制代码
#!/usr/bin/env python
# File: process_mmseqs_protein_similarity.py

import os

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns


MMSEQS_DIR = "/home/qfchen/DTIPred_Plus/analysis/results/protein_similarity/mmseqs_tsv"
SAVE_DIR = "/home/qfchen/DTIPred_Plus/analysis/results/protein_similarity"

DATASETS = ["SNAP", "DRH", "TTD", "DrugBank"]


def read_mmseqs_result(path):
    cols = ["query", "target", "pident", "alnlen", "evalue", "bits"]
    return pd.read_csv(path, sep="\t", names=cols)


def summarize(values):
    s = pd.Series(values, dtype="float64")
    return {
        "N": int(s.shape[0]),
        "Mean": float(s.mean()),
        "Median": float(s.median()),
        "Std": float(s.std()),
        "Min": float(s.min()),
        "Q25": float(s.quantile(0.25)),
        "Q75": float(s.quantile(0.75)),
        "Q90": float(s.quantile(0.90)),
        "Q95": float(s.quantile(0.95)),
        "Max": float(s.max()),
        "Ratio_GE_30": float((s >= 30).mean()),
        "Ratio_GE_50": float((s >= 50).mean()),
        "Ratio_GE_70": float((s >= 70).mean()),
        "Ratio_GE_90": float((s >= 90).mean()),
    }


def process_one(query_dataset, target_dataset):
    path = os.path.join(MMSEQS_DIR, f"{query_dataset}_vs_{target_dataset}.tsv")
    df = read_mmseqs_result(path)

    if query_dataset == target_dataset:
        df = df[df["query"] != df["target"]].copy()
        comparison = "IntraNearestNeighbor"
    else:
        comparison = "InterMaxToReference"

    best_df = (
        df.sort_values(["query", "pident", "bits"], ascending=[True, False, False])
        .drop_duplicates("query")
        .copy()
    )
    best_df["QueryDataset"] = query_dataset
    best_df["ReferenceDataset"] = target_dataset
    best_df["Comparison"] = comparison

    summary = {
        "Comparison": comparison,
        "QueryDataset": query_dataset,
        "ReferenceDataset": target_dataset,
        **summarize(best_df["pident"]),
    }
    return best_df, summary


def plot_results(intra_df, inter_df):
    sns.set_theme(style="whitegrid", context="paper", font_scale=1.25)

    plt.figure(figsize=(8, 5.2), dpi=300)
    for dataset in DATASETS:
        values = intra_df.loc[intra_df["QueryDataset"] == dataset, "pident"]
        sns.kdeplot(x=values, label=dataset, linewidth=2.2, fill=False, common_norm=False)
    plt.xlabel("Nearest-neighbor sequence identity (%)")
    plt.ylabel("Density")
    plt.title("Intra-dataset Protein Nearest-neighbor Similarity")
    plt.xlim(0, 100)
    plt.legend(frameon=False)
    plt.tight_layout()
    plt.savefig(os.path.join(SAVE_DIR, "intra_dataset_protein_similarity_density.png"), bbox_inches="tight")
    plt.close()

    inter_df = inter_df.copy()
    inter_df["Pair"] = inter_df["QueryDataset"] + " -> " + inter_df["ReferenceDataset"]

    order = [
        f"{query} -> {target}"
        for query in DATASETS
        for target in DATASETS
        if query != target
    ]

    plt.figure(figsize=(11, 5.6), dpi=300)
    sns.boxplot(data=inter_df, x="Pair", y="pident", order=order, showfliers=False)
    plt.xlabel("")
    plt.ylabel("Maximum sequence identity to reference dataset (%)")
    plt.title("Inter-dataset Protein Maximum Similarity")
    plt.ylim(0, 100)
    plt.xticks(rotation=45, ha="right")
    plt.tight_layout()
    plt.savefig(os.path.join(SAVE_DIR, "inter_dataset_protein_similarity_boxplot.png"), bbox_inches="tight")
    plt.close()

    mean_matrix = inter_df.pivot_table(
        index="QueryDataset",
        columns="ReferenceDataset",
        values="pident",
        aggfunc="mean",
    ).reindex(index=DATASETS, columns=DATASETS)

    plt.figure(figsize=(6.5, 5.6), dpi=300)
    sns.heatmap(
        mean_matrix,
        annot=True,
        fmt=".1f",
        cmap="YlGnBu",
        vmin=0,
        vmax=100,
        linewidths=0.5,
        linecolor="white",
        cbar_kws={"label": "Mean maximum sequence identity (%)"},
    )
    plt.xlabel("Reference dataset")
    plt.ylabel("Query dataset")
    plt.title("Inter-dataset Mean Maximum Protein Similarity")
    plt.tight_layout()
    plt.savefig(os.path.join(SAVE_DIR, "inter_dataset_protein_similarity_heatmap.png"), bbox_inches="tight")
    plt.close()


def main():
    os.makedirs(SAVE_DIR, exist_ok=True)

    detail_rows = []
    summary_rows = []

    for query_dataset in DATASETS:
        for target_dataset in DATASETS:
            best_df, summary = process_one(query_dataset, target_dataset)
            detail_rows.append(best_df)
            summary_rows.append(summary)

    detail_df = pd.concat(detail_rows, ignore_index=True)
    summary_df = pd.DataFrame(summary_rows)

    intra_df = detail_df[detail_df["Comparison"] == "IntraNearestNeighbor"].copy()
    inter_df = detail_df[detail_df["Comparison"] == "InterMaxToReference"].copy()

    intra_df.to_csv(os.path.join(SAVE_DIR, "intra_dataset_protein_similarity.csv"), index=False)
    inter_df.to_csv(os.path.join(SAVE_DIR, "inter_dataset_protein_similarity.csv"), index=False)
    summary_df.to_csv(os.path.join(SAVE_DIR, "protein_similarity_summary.csv"), index=False)

    plot_results(intra_df, inter_df)

    print(summary_df.to_string(index=False))
    print(f"\nSaved protein similarity results to: {SAVE_DIR}")


if __name__ == "__main__":
    main()

运行:

bash 复制代码
python process_mmseqs_protein_similarity.py

10. 输出结果说明

后处理后会得到:

bash 复制代码
intra_dataset_protein_similarity.csv
inter_dataset_protein_similarity.csv
protein_similarity_summary.csv

同时生成图:

bash 复制代码
intra_dataset_protein_similarity_density.png
inter_dataset_protein_similarity_boxplot.png
inter_dataset_protein_similarity_heatmap.png

其中:

bash 复制代码
intra_dataset_protein_similarity.csv

表示每个蛋白在同一数据集内部的最高相似蛋白。

bash 复制代码
inter_dataset_protein_similarity.csv

表示每个蛋白到另一个数据集中的最高相似蛋白。

bash 复制代码
protein_similarity_summary.csv

是统计汇总表,包括均值、中位数、分位数,以及大于 30%、50%、70%、90% identity 的比例。


11. 小结

整个流程如下:

bash 复制代码
ProtInfo.xlsx
   ↓
导出 FASTA
   ↓
MMseqs2 easy-search 批量比较
   ↓
每个 query 蛋白取最大 pident
   ↓
得到内部最近邻相似度和跨数据集最大相似度
   ↓
绘制密度图、箱型图和热图

这个分析可以帮助解释:

  1. 哪些数据集内部蛋白冗余程度高;
  2. 哪些数据集之间蛋白空间更接近;
  3. cold protein 或跨数据集实验是否真的具有较强泛化挑战。
相关推荐
Li-Yongjun1 小时前
Linux 内核等待队列(Wait Queue)
linux·运维·windows
字节高级特工1 小时前
【Linux】深入理解C语言命令行参数与环境变量
linux·c++·人工智能·后端
念恒123062 小时前
Python 函数完全指南:定义与调用
开发语言·python
linux开发之路2 小时前
C++项目推荐:eBPF+调度器性能分析框架
linux·c++·ebpf·火焰图·调度器
大数据魔法师2 小时前
Streamlit(十二)- API 参考文档(五)- 输入组件
python·web
愿天垂怜2 小时前
【C++脚手架】ffmpeg 库的介绍与使用
linux·服务器·开发语言·c++·ide·git·ffmpeg
涛声依旧-底层原理研究所2 小时前
Node.js在高并发低延迟场景中的优势
java·人工智能·python·node.js
jimy12 小时前
Linux动态加载器,loader,dynamic linker
linux·运维·服务器
Vick_Zhang2 小时前
ubuntu上rabbitmq
服务器·ubuntu·rabbitmq