Neo4j看疾病统计数据

本文介绍了如何利用Neo4j分析KEGG疾病数据库,探讨疾病、药物和致病生物之间的关系。通过预处理数据、构建图数据库,使用Louvain算法识别疾病群和PageRank发现高连接度节点。研究发现,免疫抑制药物用途广泛,而病毒是最常见的致病生物。项目源代码已公开在GitHub上。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文章是我在towards data science上发表文章《Neo4j for Diseases》的自译中译本。

注意:本文章并不提供医学建议。它只是提供信息。它并不能取代专业的医学建议,诊断和治疗。

目录

1. 预处理并导入数据到Neo4j

2. 快速地获得数据全貌

3. 寻找多用途药物

4. 一些致病生物的细节

5. 使用图算法去发现群和连接性高的节点

5.1 用Louvain算法构建疾病群

5.2 用PageRank去发现连接度高的节点

结论


新冠疫情笼罩着全世界。疫情极大地影响着我们的生活。家庭破碎,经济损失,而且我们的日常行为可能也因此而永久改变。同时,疫情也让我们重新关注起公共健康这个问题。有一点是很清楚的,我们人类需要在医学研究上投资更多来避免下一次瘟疫灾情。

图1:使用Neo4j显示KEGG数据库中COVID-19条目作者作图

我在写完之前的文章《Analyzing Genomes in a Graph Database》后,发现其实京都基因和基因组百科全书(KEGG)还有很多的宝藏等着我们去发掘。其中之一就是其疾病数据库。该子数据库含有很多关于人类疾病方面的细节。结合其他的KEGG子数据库,例如基因组,药物和基因子数据库,我们可以构建一个关于疾病,致病生物和治疗药物的非常完备的知识网络。一如我之前的文章里所说的,Neo4j这样的图数据库非常适用于在生物医疗领域进行知识挖掘。而KEGG的疾病数据正好适用于这样一个项目。我从KEGG那里通过API下载了疾病等子数据库,导入到Neo4j里面,做了几项分析,并且发现了一些有趣的信息。

在这篇文章里,我集中分析疾病,致病生物和药物之间的关系。本项目的源代码放在了我Github的库上:https://github.com/dgg32/kegg_disease

1. 预处理并导入数据到Neo4j

你可以在data文件夹里找到本项目所需要的所有CSV文件。不过如果你想更新数据到最新的状态,请按照README.md里的指引行事。

在Neo4j官网那里下载Neo4j Desktop。下载之后,添加一个新的本地DMBS(“Local DMSBS”),称之为kegg_disease。通过点击...然后Open folder,打开其Import文件夹。将所有的CSV文件放到里面,并执行以下命令,导入数据:

LOAD CSV WITH HEADERS FROM 'file:///disease.csv' AS row MERGE (n:disease {name: row.name, ko: row.ko, description: row.description, disease_category:row.disease_category});
LOAD CSV WITH HEADERS FROM 'file:///drug.csv' AS row MERGE (n:drug {name: row.name, ko: row.ko});
LOAD CSV WITH HEADERS FROM 'file:///pathogen.csv' AS row MERGE (n:pathogen {name: row.name, ko: row.ko, taxonomy: row.taxonomy});

CREATE CONSTRAINT ON (n:disease) ASSERT n.ko IS UNIQUE;
CREATE CONSTRAINT ON (n:drug) ASSERT n.ko IS UNIQUE;
CREATE CONSTRAINT ON (n:pathogen) ASSERT n.ko IS UNIQUE;

LOAD CSV WITH HEADERS FROM 'file:///drug_disease.csv' AS row MERGE (n1:drug {ko: row.from}) MERGE (n2:disease {ko: row.to}) MERGE (n1)-[r:treats]->(n2);
LOAD CSV WITH HEADERS FROM 'file:///pathogen_disease.csv' AS row MERGE (n1:pathogen {ko: row.from}) MERGE (n2:disease {ko: row.to}) MERGE (n1)-[r:causes]->(n2);

2. 快速地获得数据全貌

数据导入之后,我们可以很快地计算出一些KEGG疾病数据库的基本信息。以下三个命令可以算出三种类型节点的总数:

MATCH (p:pathogen) RETURN COUNT(DISTINCT p)

#returns 333


MATCH (dr:drug) RETURN COUNT(DISTINCT dr)

#returns 1339


MATCH (di:disease) RETURN COUNT(DISTINCT di)

#returns 2498

查询结果显示,图数据库里一共有333个“致病生物”,1339个“药物”和2498个“疾病”节点。这个数据库并不大,因此我们可以使用Neo4j Bloom来制作一个数据库的拓扑全貌,在Bloom里输入以下命令并运行(我之前的文章有说明更详细步骤):

MATCH p=(n:disease) <-[]-() RETURN p;

图2: 本项目的拓扑概览。绿色的点代表“疾病”,红色的是“药物”而蓝色的是“致病生物”。作者作图。

以下查询命令可以显示Top 10疾病类型。要注意的是,一些疾病例如Schwartz-Jampel综合征和中央核肌肉病变被归纳到两个以上的疾病类型,但以下的查询命令不会对它们进行重复计数:

MATCH (d:disease) RETURN d.disease_category, COUNT(d.disease_category) as count ORDER BY count DESC LIMIT 10;

结果如下:

d.disease_categorycount
Congenital malformation647
Infectious disease345
Nervous system disease234
Inherited metabolic disease124
Cancer97
Congenital disorder of metabolism94
Hematologic disease63
Immune system disease56
Cardiovascular disease54
Nervous system disease; Musculoskeletal disease52

让我惊奇的是,感染疾病以及两大杀手-心血管疾病和癌症都没有登上榜首。最多的疾病类型竟然是先天性畸形(Congenital malformation),例如指甲-髌骨综合征(nail-patella syndrome)和Meckel综合征。

病毒,细菌和真核生物都可引起感染。以下的命令可以显示每种感染源的数量:

MATCH (v:pathogen) RETURN split(v.taxonomy, "; ")[0] as domain, COUNT(split(v.taxonomy, "; ")[0]) as count ORDER BY count DESC;

结果如下:

domaincount
Bacteria147
Viruses132
Eukaryota54

又一次的惊喜,细菌,而不是病毒,是最频繁的致病生物。我们还可以进一步看看病毒的情况:

MATCH (v:pathogen) WHERE v.taxonomy contains "virus" RETURN split(v.taxonomy, "; ")[0] + "; " +  split(v.taxonomy, "; ")[1] as domain, COUNT(split(v.taxonomy, "; ")[0] + "; " +  split(v.taxonomy, "; ")[1]) as count ORDER BY count DESC;

该查询命令取回第一和第二层生物分类名字以及它们引起的疾病的总数:

domaincount
Viruses; Riboviria104
Viruses; Duplodnaviria9
Viruses; Varidnaviria9
Viruses; Monodnaviria9
Viruses; Deltavirus1

如结果显示,核糖病毒域是最频繁的域。但核糖病毒是一个很大的类别。引发2002-2004 SARS大爆发的SARS冠状病毒和引发本次疫情的SARS-CoV-2病毒都属于核糖病毒。另外,引发艾滋病的人类免疫缺陷病毒(HIV)和肝炎病毒(hepatitis viruses)也属于该域。

最后,我们可以看看治疗感染的药物的数量:

MATCH (dr:drug) -[]->(di:disease) WHERE di.disease_category = "Infectious disease" RETURN COUNT(DISTINCT(dr));

该查询返回293件药物。注意该查询一定要加“DISTINCT”,因为一些药物能够用于治疗几种疾病,“DISTINCT”保证它们不会被重复计算。

3. 寻找多用途药物

多用途药物就是能够治疗多种疾病的药物。一药多用。因为药物研发的花费巨大,测试时长,所以如果“旧药能医新病”的话(在这里观看NIH的Francis Collins关于此话题的TED talk),对病人和药商无疑都是件大好事。实际上,药物是受到例如美国的FDA的严格管理的。药商只能以FDA批准的用途营销药物。那究竟哪些药物是用途最多的呢,它们又有什么用途呢?

以下的查询命令可以返回Top 10最多用途的药物:

MATCH (dr:drug) -[r:treats]->(di:disease) RETURN dr.name as drug, COUNT(r) as count ORDER BY count DESC LIMIT 10;

结果如下:

drugcount
Prednisolone sodium phosphate37
Prednisone32
Dexamethasone26
Triamcinolone acetonide26
Doxycycline hyclate26
Doxycycline25
Doxycycline calcium25
Hydrocortisone24
Dexamethasone sodium phosphate24
Methylprednisolone24

这前十名主要就是两类药物:甾体激素和四环素里的多西环素。甾体激素用于压制免疫系统和治疗一系列的炎症。列表里的第三地塞米松(Dexamethasone),曾用于治疗新冠病人。初步结果显示,它能够减少那些有创机械通气和输氧病人的死亡率,但对于那些无需辅助呼吸的病人则没差别(The RECOVERY Collaborative Group, 2020)。多西环素是一种广谱抗生素,被用于治疗疟疾,莱姆病,霍乱和梅毒。实际上,以下的查询命令显示,列表里的这些甾体激素的用途重叠很多;而多西环素及其衍生药物也是用于多种同样疾病。

MATCH (a:drug)-[r:treats]->(b:disease)
WITH a, COUNT(r) AS indicationCount
     ORDER BY indicationCount DESC LIMIT 10
MATCH (a:drug)-[r:treats]->(b:disease)
RETURN (a) -[r] -> (b)

图3,前十多用途药物及其用途。作者作图。

例如,前两名的药物泼尼松(prednisone)和其活性形式泼尼松龙(prednisolone)自1955年起已经在美国被批准药用。它们也非常常见:在美国,泼尼松是第21,泼尼松龙是第134被开得最多的药物(DrugStats Database)。以下的两条命令可以显示泼尼松龙治疗的疾病类型:

MATCH (dr:drug) -[]->(di:disease) WHERE dr.name="Prednisolone sodium phosphate" RETURN  di.disease_category, count(di.disease_category) as count ORDER BY count DESC LIMIT 10;

MATCH (dr:drug) -[]->(di:disease) WHERE dr.name="Prednisolone sodium phosphate" AND (di.disease_category = "Lung disease" or di.disease_category="Infectious disease") RETURN di.name, di.disease_category ORDER BY di.disease_category;

结果是:

di.disease_categorycount
Immune system disease10
Infectious disease4
Musculoskeletal disease3
Skin and connective tissue disease2
Hematologic disease; Immune system disease2
Lung disease2
Skin and connective tissue disease; Immune system disease1
Urinary system disease1
Endocrine and metabolic diseases1
Respiratory disease1

还有:

di.namedi.disease_category
TrichinosisInfectious disease
Pneumocystis pneumoniaInfectious disease
TuberculosisInfectious disease
AspergillosisInfectious disease
Chronic obstructive pulmonary disease (COPD)Lung disease
Idiopathic pulmonary fibrosisLung disease

结果显示,泼尼松龙不但用于免疫系统疾病,也用于治疗感染,例如旋毛虫病和结核病。因为它能消炎,它还用于治疗慢性阻塞性肺疾病(COPD)。但对特发性肺纤维化(IPF),其效果尚不明朗。

至于2021年到目前为止在美国开得最多的药物,阿托伐他汀和它的衍生物,只是用于四种代谢和心血管疾病:高脂血症,家族性高胆固醇血症,心绞痛和心肌梗死。

图4,阿托伐他汀和它的衍生物的药用,作者作图。

4. 一些致病生物的细节

现在看看致病生物的情况。首先看看一些Top致病生物的致病的数目。

MATCH (p:pathogen) -[r:causes]->(di:disease) RETURN p.name, COUNT(r) as count ORDER BY count DESC LIMIT 10; 
p.namecount
Human papillomavirus type 167
Human papillomavirus type 185
Human herpesvirus 4 (Epstein-Barr virus)4
Human immunodeficiency virus 1 (HIV-1)4
Machupo mammarenavirus3
Hepatitis B virus (HBV)3
Sabia mammarenavirus3
Guanarito mammarenavirus3
Oropouche virus3
Hepatitis C virus (HCV)3

能致最多疾病的前十全是病毒。前两名人类乳头瘤病毒(HPV)16和18型可以引发多种癌症。根据维基百科全书,大多HPV的感染都是无症状的,而且90%的病例会在两年内褪去。但在其他情况,它们则会残留在体内并引起疣和病变,并增加例如宫颈,外阴,阴道,阴茎,肛门,口部,扁桃体和喉咙等人体腔道表面各种癌变的几率(图5)。几乎所有的宫颈癌都是由HPV引起的;而这前两个病毒型则是占据了70%的病例。而90%的HPV阳性口咽癌就是由16型引起的。

图5,HPV 18和16型和癌症的关系。作者作图。

相比之下,SARS和SARS-CoV-2则各只与一种疾病相关联。SARS引发了2002-2004年中国的SARS疫情,至今仍没有相对的抗病毒治疗。KEGG把新冠肺炎与瑞德西韦(remdesivir)联系起来。根据Siemieniuk等人的持续监察,瑞德西韦可能可以缩短新冠的病情时长。

图6,SARS和SARS-CoV-2及其引起的疾病。作者作图。

而根据KEGG,用于治疗疟疾的硫酸羟氯喹不是新冠治疗药物。

图7,硫酸羟氯喹及治疗的疾病,作者作图。

这也对应了Siemieniuk的一个研究结论:

羟氯喹可能并不能减少死亡率和机械通气,对新冠肺炎也很可能不会带来其他的好处。

5. 使用图算法去发现群和连接性高的节点

图2的拓扑图有些奇特之处。很多小的网络围绕着一个大的网络。如果能够分离出那些群,并且得出那些枢纽,就是连接度高的节点,就好了。幸好,Neo4j提供了很强大的插件:图数据科学(Graph Data Science (gds))来处理这些任务。

首先去Neo4j Desktop里的该项目Plugins项,安装该插件:

图8,安装图数据科学插件,作者作图。

在Neo4j里,图算法要运行在图投射(graph projections)上。所以,首先要用Cypher建一个包括所有节点和联系的图投射“disease-graph”:

CALL gds.graph.create.cypher(
    'disease-graph',
    'MATCH (n) RETURN id(n) AS id',
    'MATCH (n)--(m) RETURN id(n) AS source, id(m) AS target'
)

5.1 用Louvain算法构建疾病群

现在我们可以用Louvain算法来构建群。Louvain算法浓缩紧密关联的节点成大的节点,不断重复,直至再没有浓缩可做。首先,看看我们能构建多少群。

CALL gds.louvain.stats('disease-graph')
YIELD communityCount

#return 2120

从4170个节点,Louvain算法浓缩出了2120个群。我试着用不同的“maxIterations”和“tolerance”去运行该命令,而结果都是大同小异。

我们可以看看最大的群和里面的成员:

CALL gds.louvain.stream('disease-graph')
YIELD nodeId, communityId, intermediateCommunityIds
RETURN  communityId, COUNT(communityId) as count
ORDER BY count DESC LIMIT 10;

结果是:

communityIdcount
3421190
2767189
2615172
3549160
3402120
3561116
3518108
319090
284874
323973

群3421是最大的,让我们看看里面的成员:

CALL gds.louvain.stream('disease-graph')
YIELD nodeId, communityId, intermediateCommunityIds
WHERE communityId = 3421
RETURN  gds.util.asNode(nodeId).name AS name, communityId

其前十的成员是:

namecommunityId
Osteosarcoma3421
Breast cancer3421
Melanoma3421
Hodgkin lymphoma3421
Congenital nongoitrous hypothyroidism (CHNG)3421
Basal cell carcinoma3421
Cervical cancer3421
Viral hepatitis3421
Subependymal giant cell astrocytoma3421
Head and neck cancer3421

细细地看过群的成员之后,可以知道该群主要是肿瘤。肝炎病毒如乙肝和丁肝也在该群里面。

我们可以进一步看看其他群。我发现大的群都是被研究得很多的疾病,它们则是进一步通过共同的致病生物或多用药物联系起来形成群。相比之下,很多单独的小岛则是罕见疾病,例如Christianson综合征和Zimmermann-Laband综合征值得注意的是,虽然SARS和新冠引发了大新闻,它们在KEGG的疾病数据里也只是两个独立的小岛(图2)。说明了:我们相对地还是对新冠知道得很少。

5.2 用PageRank去发现连接度高的节点

最后,我们用PageRank去看看图里哪些节点是连接度高的:

CALL gds.pageRank.stream('disease-graph')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS name, score
ORDER BY score DESC, name ASC LIMIT 12;

结果是:

namescore
High blood pressure39.84163054917008
Type 2 diabetes mellitus25.417368324007835
HIV infection23.399820479750638
Breast cancer13.46088549327105
Major depressive disorder12.292233879491686
Primary open angle glaucoma10.7658223032151
Schizophrenia10.442885867986478
Rheumatoid arthritis9.220130287948999
Chronic obstructive pulmonary disease (COPD)9.169124054256827
Non-small cell lung cancer8.981185495341197
Asthma8.857213483797388
Psoriasis8.154248254094272

没有一个“药物”和“致病生物”节点能打进前12,表里全是常见病。原因一方面是,每一个“药物”和“致病生物”都会连接在至少一个“疾病”节点上。所以“疾病”本来就是处在该图项目的中心位置。另一方面,列表里全是现代人类社会里最常见的疾患,而我们也开发了很多药物去治疗它们。我们还可以用另一个查询来数数治疗这些疾患的药物:

CALL gds.pageRank.stream('disease-graph')
YIELD nodeId, score
WITH COLLECT(DISTINCT(gds.util.asNode(nodeId))) AS di_list, score
ORDER BY score DESC 
MATCH (dr:drug) -[r:treats]-> (di:disease)
WHERE di in di_list
RETURN di.name as disease_name, score AS pagerank_score, COUNT(DISTINCT(r)) AS drug LIMIT 12;
disease_namepagerank_scoredrug
High blood pressure39.84163054917008103
Type 2 diabetes mellitus25.41736832400783563
HIV infection23.39982047975063853
Breast cancer13.4608854932710545
Major depressive disorder12.29223387949168636
Primary open angle glaucoma10.765822303215126
Schizophrenia10.44288586798647830
Rheumatoid arthritis9.22013028794899948
Chronic obstructive pulmonary disease (COPD)9.16912405425682731
Non-small cell lung cancer8.98118549534119732
Asthma8.85721348379738840
Psoriasis8.15424825409427234

结果显示,103样药物可用于治疗第一项的高血压。西方社会2-4%的人口患有牛皮癣,表里的最后一项,也获得了34样药物的治疗。但是细看最后两列可以发现,药物数量的排名和PageRank的排名并不一致。比如,有48样药物可用于类风湿关节炎,多于排在前面的乳腺癌,重性抑郁疾患和精神分裂症。


结论

该小项目又一次显示了Neo4j在医药生物研究方面的优势。导入数据之后,我们可以在弹指之间获得大量关于疾病的数字和细节。如果是用关系数据库,我们至少得用三个表格做数据模型。而在Neo4j,我们只要一个就可以了。另外,其查询语言Cypher非常直观,没有SQL里频繁的“JOIN”,编写的人可以很直白地写出查询语句,读者也能很快地读懂作者的意图。最后,图数据库还允许我们运行图算法,让我们很快地获得诸如群和高连接度的节点

当然这并不是说我们要舍弃其他数据库。如果我们的数据里具有很多的关联,图数据库是个很好的选择。我们之前的基因组项目和眼下这个疾病数据项目就是好例子。但如果我们有的是简单的表格,例如价目表,我们则应该用关系数据库了。对于文档,我们可以考虑MongoDB。时间系列的数据则有它们自己的数据库,例如Amazon的Timestream和InfluxDB。

这个项目也带给了我们不少关于疾病和健康的新知识。其中让我印象最深的是关于我们的免疫系统。现在疫情,我们很容易会误入那些铺天盖地的“加强你的免疫力”广告。但其实我们的免疫系统是很容易被矫枉过正的,我们也绝不能让它超速过热。相反,我们上面的分析表明,最多用和开得最多的药物,其实是抑制免疫力的药物。而世界上也有很多人在受着免疫力过强之苦。约翰斯·霍普金斯大学估计3%的美国人患有自身免疫疾病,大概一千万人。而患者更多为女性(自身免疫疾病的男女比率为1:2)。这让我想起了Matt Richtel的书《An Elegant Defense》里提到,美国新冠疫情的理性代表人物--免疫学家安东尼·弗契(Anthony Fauci),当他听到提高免疫力的广告的时候,说道:

那让我想笑。首先,它们认定你需要提升免疫力,其实那应该是不要的。就算你成功地提升了你的免疫力,你可能提升了它去做有害的事。

该项目只涉及了KEGG很少的一部分数据:疾病,药物和致病生物。我好奇,如果我们能动用所有KEGG的数据的话,我们会发现一些什么样的新知识呢。所以,我鼓励大家去用图数据库来进一步发掘这个数据宝藏,并告诉我你发现的东西。加油!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值