rdkit的操作中,对大批量化合物的处理,人们倾向于采用并行化的方式加速处理,例如在Pandas的表格中,采用apply的方式实现化合物属性计算等等。
关于apply函数的并行化,网络上不少帖子攻略是采用Joblib的方式实现,但实际采用后才会发现加速效果有限。
一句话总结:非特大规模数据处理,几乎可以无脑优先选用pandarallel这个库,避开joblib的方式。
无并行示例:
我们以计算化合物RO5属性的自定义函数cal_mol_props示例,该函数定义为
from rdkit import Chem
import numpy as np
def cal_mol_props(smi, verbose=False):
try:
m=Chem.MolFromSmiles(smi)
if not m:
return None
mw = np.round(Descriptors.MolWt(m),1)
logp = np.round(Descriptors.MolLogP(m),2)
hbd = rdMolDescriptors.CalcNumLipinskiHBD(m)
hba = rdMolDescriptors.CalcNumLipinskiHBA(m)
psa = np.round(Descriptors.TPSA(m),1)
rob= rdMolDescriptors.CalcNumRotatableBonds(m)
qed= np.round(QED.qed(m),2)
chiral_center=len(Chem.FindMolChiralCenters(m))
aRings=rdMolDescriptors.CalcNumAromaticRings(m)
return mw,logp,hbd,hba,psa,rob,qed,chiral_center,aRings
except Exception as e:
print (e)
return None
对于测试用表格df_test,含有1万个化合物,一般情况下df["new_row"]=df["smiles"].apply(your_func)
即可实现新一列结果的对应计算。
df_test.head(2)
smiles id
O=c1ccc(C2CC2)nn1C1CCNCC1 1
O=C(O)C1CCN(c2cc3c(nn2)CCC3)CC1 2
在jupyter notebook中计时
%time df_test["Mw"],df_test["logP"],df_test["HBD"],df_test["HBA"],df_test["TPSA"],\
df_test["RotB"],df_test["QED"],df_test["chiral"],df_test["aRings"]= \
zip(*df_test['smiles'].apply(cal_mol_props))
CPU times: user 18.7 s, sys: 0 ns, total: 18.7 s
Wall time: 18.8 s
并行方案一: Joblib库
由于该库常常被集成在默认安装库中,anaconda装好后无需额外安装非常方便,不少技术博客中也多次推荐。实现方式如下:
from joblib import Parallel, delayed
#定义一个暂时的函数,包裹我们实际用的函数
def temp_func(df_test):
df_test["Mw"],df_test["logP"],df_test["HBD"],df_test["HBA"],df_test["TPSA"],\
df_test["RotB"],df_test["QED"],df_test["chiral"],df_test["aRings"]= \
zip(*df_test['smiles'].apply(cal_mol_props))
return df_test
# 采用n_jobs=10作为参考
def applyParallel(df_grouped,func):
results=Parallel(n_jobs=10)(delayed(func)(group) for name,group in df_grouped)
return pd.concat(results)
实际计算时,在jupyter 中,
df_grouped= df_test.groupby(df_test.index)
%time applyParallel(df_grouped,temp_func)
CPU times: user 10min 25s, sys: 11.5 s, total: 10min 37s
Wall time: 10min 30s
可以看到,并行比不并行反而慢了约70秒,这是因为进程的建立与结果合并有额外开销,规模不大时反而得不偿失。
并行方案二: pandarallel库
Pandaral库除了需要额外安装以外,几乎全是优点。它提供一个仅做一行代码修改即可实现apply函数加速的功能,同时也能提供一个可视化的进度条。
我们仅需要pip install pandarallel
便可完成安装。
使用时将原有的apply换成parallel_apply即可
from pandarallel import pandarallel
pandarallel.initialize(nb_workers=10)
%time df_test["Mw"],df_test["logP"],df_test["HBD"],df_test["HBA"],df_test["TPSA"],\
df_test["RotB"],df_test["QED"],df_test["chiral"],df_test["aRings"]= \
zip(*df_test['smiles'].parallel_apply(cal_mol_props))
CPU times: user 653 ms, sys: 0 ns, total: 653 ms
Wall time: 3.03 s
结果总结
分别在1万与10万的化合物上测试了速度,pandarallel加速效果明显,反而是其他技术贴里推荐的joblib速度不升反降,可能是需要更大规模的计算才能抵消进程建立与结束的额外开销。
并行同样采用10个cpu时
方案 | 化合物规模 | 耗时 |
---|---|---|
无加速 | 一万 | 18.8s |
joblib | 一万 | 1min 35s |
pandarallel | 一万 | 3.03s |
无加速 | 十万 | 3min 21s |
joblib | 十万 | 10min 30s |
pandarallel | 十万 | 24.8s |