将化学分子的 ChEMBL ID 转化为 SMILES 的两种方法

Category 碎碎念

ChEMBL 是一个大型的化学分子数据库,其中收集了大量化合物的化学、生物学数据,也是化学信息学、生物信息学领域中许多研究的数据来源。很多情况下,不管是按需求从 ChEMBL 中提取出的指定数据,还是从文章中下载的原始数据都是使用 ChEMBL ID 作为化合物的标识。而为了进一步使用这些数据,通常要将其转换为例如 SMILES 或是 InChI 等更具体的分子表示。

本文分别介绍使用 ChEMBL 的 Web API 和 PostgreSQL 将 ChEMBL ID 转化为 SMILES 的两种方法,在得到 SMILES 之后,想获得其他的分子表示就也很容易了。

ChEMBL Web API

ChEMBL 在其官方接口文档中介绍了多种获取数据库信息的方法,因为完成转化后通常还要额外处理数据,最灵活方便的还是其中的 ChEMBL Web 服务 Python 包 chembl-webresource-client,本文也主要介绍它的使用方法。

$ pip install chembl-webresource-client

通过 pip 安装后,通过 ChEMBL ID 过滤分子并取出其 "molecule_structures" 信息:

>>> from chembl_webresource_client.new_client import new_client
>>> molecule = new_client.molecule
>>> info = molecule.filter(chembl_id="CHEMBL10").only(["molecule_structures"])
>>> info
[{'molecule_structures': {'canonical_smiles': 'C[S+]([O-])c1ccc(-c2nc(-c3ccc(F)cc3)c(-c3ccncc3)[nH]2)cc1', 'molfile': '\n     RDKit          2D\n\n 27 30  0  0  0  0  0  0  0  0999 V2000\n   10.3778   -8.9759    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    9.7898   -8.6390    0.0000 S   0  0  0  0  0  0  0  0  0  0  0  0\n    9.7877   -7.9612    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n    9.0572   -9.0655    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    9.0585   -9.9128    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    8.3255  -10.3375    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    7.5911   -9.9151    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    7.5898   -9.0679    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    8.3228   -8.6431    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    6.8577  -10.3401    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    6.7798  -11.1765    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0\n    5.9511  -11.3526    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    5.6063  -12.1270    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    6.0503  -12.8454    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    5.6484  -13.5912    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    4.8016  -13.6161    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    4.4801  -14.2128    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0\n    4.3566  -12.8952    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    4.7584  -12.1493    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    5.5275  -10.6189    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    6.0943   -9.9893    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0\n    4.6845  -10.5304    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    4.1385  -11.1746    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    3.3050  -11.0228    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    3.0196  -10.2251    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0\n    3.5678   -9.5791    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    4.4013   -9.7308    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n  1  2  1  0\n  2  3  1  0\n  2  4  1  0\n  4  5  2  0\n  5  6  1  0\n  6  7  2  0\n  7  8  1  0\n  8  9  2  0\n  9  4  1  0\n  7 10  1  0\n 10 11  2  0\n 11 12  1  0\n 12 13  1  0\n 13 14  2  0\n 14 15  1  0\n 15 16  2  0\n 16 17  1  0\n 16 18  1  0\n 18 19  2  0\n 19 13  1  0\n 12 20  2  0\n 20 21  1  0\n 21 10  1  0\n 20 22  1  0\n 22 23  2  0\n 23 24  1  0\n 24 25  2  0\n 25 26  1  0\n 26 27  2  0\n 27 22  1  0\nM  CHG  2   2   1   3  -1\nM  END\n> <chembl_id>\nNone\n\n> <chembl_pref_name>\nundefined', 'standard_inchi': 'InChI=1S/C21H16FN3OS/c1-27(26)18-8-4-16(5-9-18)21-24-19(14-2-6-17(22)7-3-14)20(25-21)15-10-12-23-13-11-15/h2-13H,1H3,(H,24,25)', 'standard_inchi_key': 'CDMGBJANTYXAIV-UHFFFAOYSA-N'}}]

尽管指定了仅 "molecule_structures" 部分的信息,但还是输出了很长结果,其中包括 InChI 等各种结构数据 ,只有 'canonical_smiles' 字段是目标信息。

>>> type(info)
chembl_webresource_client.query_set.QuerySet

查询结果对象是与 Django QuerySet 相似的 chembl_webresource_client.query_set.QuerySet,支持多种类似的过滤操作,具体内容可以查看官方案例

处理单条数据

QuerySet 同样也兼容列表、字典等 Python 基本数据结构的操作,所以对于单条 ChEMBL ID,我们可以通过以下方法很方便地直接取出查询到的 SMILES 结果:

>>> chembl_id = "CHEMBL10"
>>> molecule.filter(chembl_id=chembl_id).only(["molecule_structures"])[0]["molecule_structures"]["canonical_smiles"]
'C[S+]([O-])c1ccc(-c2nc(-c3ccc(F)cc3)c(-c3ccncc3)[nH]2)cc1'

处理多条数据

若要查询多条 ChEMBL ID,只需要把参数 chembl_id 改为 molecule_chembl_id__in 并传入ChEMBL ID 的列表即可。

 Warning 输入 ChEMBL ID 列表时,参数名称 molecule_chembl_id__in 中位于 “in” 前的是双下划线

>>> chembl_id = ["CHEMBL10", "CHEMBL100", "CHEMBL100"]
>>> infos = molecule.filter(molecule_chembl_id__in=chembl_id).only(["molecule_chembl_id", "molecule_structures"])
>>> {info["molecule_chembl_id"]: info["molecule_structures"]["canonical_smiles"] for info in infos}
{'CHEMBL10': 'C[S+]([O-])c1ccc(-c2nc(-c3ccc(F)cc3)c(-c3ccncc3)[nH]2)cc1', 'CHEMBL100': 'CC1(C)Oc2ccc(C#N)cc2[C@@H](N2CCCC2=O)[C@@H]1O', 'CHEMBL1000': 'O=C(O)COCCN1CCN(C(c2ccccc2)c2ccc(Cl)cc2)CC1'}

通过这种方法查询 SMILES 的优点在于方便灵活,可以很容易地将查询代码嵌入处据处理的脚本中。但由于该方法是通过 Web API 查询结果,速度受限于网络,内容大小受限于 memory,无法用于大量数据的转化。经笔者测试,将 1000 条 ChEMBL ID 转化为 SMILES 耗时约 102 秒,用这种方法处理较小规模的数据更加合理。

PostgreSQL

若要将大批量的 ChEMBL ID 转化为 SMILES,更推荐将 ChEMBL 数据库下载至本地,查询的速度更快。ChEMBL 提供了MySQL、PostgreSQL 等多种数据库的下载方式,读者可以下载熟悉的数据库压缩包,本文选择在 Linux 上使用 PostgreSQL。当然,在安装 ChEMBL 数据库前必须安装相应的数据库软件,很容易就能检索到各种数据的安装方法,这里从略。

$ wget "https://ftp.ebi.ac.uk/pub/databases/chembl/ChEMBLdb/latest/chembl_34_postgresql.tar.gz" -O chembl_34_postgresql.tar.gz
$ tar -zxvf chembl_34_postgresql.tar.gz

在安装好对应的数据库软件后,下载并解压数据库压缩包,得到 chembl_34_postgresql.dmpINSTALL_postgresql 两个文件,先来看看安装指导:

$ cat INSTALL_postgresql
...
Instructions
------------

1. Log into PostgreSQL database server where you intend to load chembl data
   and run the following command to create new database:

    pgdb=# create database chembl_34;

2. Logout of database and run the following command to load data. You will
   need to replace USERNAME, HOST and PORT with local settings.
   Depending on your database setup you may not need the host and port
   arguments.

    $> pg_restore --no-owner -h HOST -p PORT -U USERNAME -d chembl_34 chembl_34_postgresql.dmp

创建数据库

按照安装指引,先通过 psql 进入 PostgreSQL 终端,进入终端后会显示 =# 提示符:

$ psql
psql (12.15 (Ubuntu 12.15-0ubuntu0.20.04.1))
Type "help" for help.

postgres=#

 Note 正确安装 PostgreSQL 却无法通过 psql 进入数据库终端可能是用户问题,可以尝试通过 sudo -i -u postgres 切换到 PostgreSQL 的默认管理账号再进入。

使用指引提供的命令创建名为 chembl_34 的数据库,创建完成后使用 \l 可以看到新创建的数据库,最后用 exit 退出数据库终端:

postgres=# CREATE DATABASE chembl_34;
CREATE DATABASE
postgres=# \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
 chembl_34 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
postgres=# \q

导入数据库

$ pg_restore --no-owner -h HOST -p PORT -U USERNAME -d chembl_34 chembl_34_postgresql.dmp

pg_restore 命令用于从数据库文件恢复(导入)数据库,使用到了以下几个参数:

  • --no-owner:不与最初创建数据库的用户匹配;
  • -h:PostgreSQL 服务主机地址;
  • -p:PostgreSQL 服务端口号;
  • -U:PostgreSQL 用户名,与上文创建数据库所使用的用户一致;
  • -d:数据库名称,与上文中创建的数据库名保持一致;
  • <path>:数据库文件的路径,也就是解压得到的 .dmp 文件。

如果忘记了 PostgreSQL 服务的端口号,可以使用以下命令查询本机的 PostgreSQL 监听端口:

$ sudo netstat -plunt|grep postgres
tcp     0       0   127.0.0.1:5432      0.0.0.0:*       LISTEN      3887/postgres

因此我就使用以下命令导入数据库:

$ pg_restore --no-owner -h 127.0.0.1 -p 5432 -U postgres -d chembl_34 chembl_34_postgresql.dmp
Password:

输入 PostgreSQL 用户的密码后没有输出,就已经开始导入数据库了,大约耗时 30 分钟。

提取 SMILES

导入完成后进入数据库查看其结构:

postgres=# \c chembl_34
You are now connected to database "chembl_34" as user "postgres".
chembl_34=#

进入 chembl_34 数据库后可以使用 \dt 列出所有的表,使用 \d <table_name> 查看表信息。但是这样一个个查看实在太费事,好在 ChEMBL 提供了更直观的图示和文档。

打开下载页面中名为 chembl_34_schema.png 的文件,图片展示了 ChEMBL 数据库中的所有表、表字段以及表之间的关系,Schema Documentation 页面对字段信息做了详细介绍。

我们的目标是将 ChEMBL ID 转换为 SMILES,所以要寻找一条由 CHEMBL_ID 字段起始到达 CANONICAL_SMILES 字段的连线:

n

  整张图片太大,因此对图片裁剪拼贴,该部分位于原图的左上角和右上角

其中需要注意的是,文档中说明了 entity_idmolregno 字段的值相同,因此图中二者可以连结起来,图中的关系也可以用文字描述成

chembl_id_lookup: chembl_id → entity_id
|
molecule_dictionary: molregno
|
compound_structures: molregno → canonical_smiles

理清了关系后暂时先放在一边,接下来处理需要转换的 ChEMBL ID。将 ChEMBL ID 存储为 CSV 文件,类似于

chembl_id
CHEMBL10
CHEMBL100
CHEMBL1000
CHEMBL10000
CHEMBL100004
CHEMBL100005
CHEMBL100006
CHEMBL100007
CHEMBL100008
CHEMBL100009

然后准备将 ChEMBL ID 导入为数据库中的一张新表,进入 chembl_34 数据库后,替换相应路径并执行命令:

CREATE TEMPORARY TABLE input_chembl_id (
   id SERIAL PRIMARY KEY,
   chembl_id VARCHAR(20)
);

COPY input_chembl_id(chembl_id)
FROM '/path/to/input_chembl_id.csv'
CSV HEADER;

成功导入 CSV 后理应能够通过以下方法输出表中数据:

chembl_34=# SELECT * FROM input_chembl_id LIMIT 10;
 id |  chembl_id
----+--------------
  1 | CHEMBL10
  2 | CHEMBL100
  3 | CHEMBL1000
  4 | CHEMBL10000
  5 | CHEMBL100004
  6 | CHEMBL100005
  7 | CHEMBL100006
  8 | CHEMBL100007
  9 | CHEMBL100008
 10 | CHEMBL100009
(10 rows)

根据上文得到的 chembl_idcanonical_smiles 字段间关系,可以编写 SQL 语句获取对应的 SMILES:

SELECT input_chembl_id.chembl_id, compound_structures.canonical_smiles
FROM (((input_chembl_id
LEFT JOIN chembl_id_lookup
ON input_chembl_id.chembl_id = chembl_id_lookup.chembl_id)
LEFT JOIN molecule_dictionary
ON molecule_dictionary.molregno = chembl_id_lookup.entity_id)
LEFT JOIN compound_structures
ON molecule_dictionary.molregno = compound_structures.molregno)
LIMIT 10;

执行上述 SQL 即可输出 ChEMBL ID 对应的 SMILES:

  chembl_id   |                     canonical_smiles
--------------+-----------------------------------------------------------
 CHEMBL10     | C[S+]([O-])c1ccc(-c2nc(-c3ccc(F)cc3)c(-c3ccncc3)[nH]2)cc1
 CHEMBL100    | CC1(C)Oc2ccc(C#N)cc2[C@@H](N2CCCC2=O)[C@@H]1O
 CHEMBL1000   | O=C(O)COCCN1CCN(C(c2ccccc2)c2ccc(Cl)cc2)CC1
 CHEMBL10000  | O=c1oc(Nc2ccc(I)cc2)nc2ccccc12
 CHEMBL100004 | CCO/C(O)=C1/C(C)=NC(C)=C(C(=O)OCCSc2ccccc2)C1C
 CHEMBL100005 | COC(=O)C(Cc1ccc2c(c1)OCO2)c1c2ccccc2nc2ccccc12
 CHEMBL100006 | COc1cc(C)c(OC)c(CC(C)N)c1
 CHEMBL100007 | CNC(Cc1cc(Br)ccc1N)c1sccc1C
 CHEMBL100008 | CN(C)C(=O)Cn1c(-c2ccc(Br)cc2)nc2cccnc21
 CHEMBL100009 | CC1=NS(=O)(=O)c2ncccc2N1
(10 rows)

对于大规模的转换,通常需要查将询结果导出为 CSV 文件,那么查询并保存结果的完整 SQL 语句就为:

COPY (SELECT input_chembl_id.chembl_id, compound_structures.canonical_smiles
FROM (((input_chembl_id
LEFT JOIN chembl_id_lookup
ON input_chembl_id.chembl_id = chembl_id_lookup.chembl_id)
LEFT JOIN molecule_dictionary
ON molecule_dictionary.molregno = chembl_id_lookup.entity_id)
LEFT JOIN compound_structures
ON molecule_dictionary.molregno = compound_structures.molregno))
TO '/path/to/chembl_id_smiles.csv'
WITH (FORMAT csv, HEADER);

替换其中的输出路径并执行即可得到结果。经测试,将 300 万条 ChEMBL ID 转化为 SMILES 耗时不到 30 秒,非常适合大规模数据的场景。