Neo4j是一款开源图数据库,Py2neo提供了应用Python语言拜访Neo4j的接口。本文介绍了应用Py2neo的NodeMatcher和RelationshipMatcher查问图中的节点和关系,以及通过执行Cypher语句的查问形式。
本文应用的Py2neo是2021.1之后的版本,手册请戳这里:
The Py2neo Handbook
连贯Neo4j数据库
本文中会用到多种数据类型,在此一并援用
import numpy as npimport pandas as pdfrom py2neo import Node,Relationship,Graph,Path,Subgraphfrom py2neo import NodeMatcher,RelationshipMatcher
配置Neo4j数据库的拜访地址、用户名和明码
neo4j_url = 'http://localhost:7474/'user = 'neo4j'pwd = 'admin'
2021.1之前拜访数据库的形式为:
graph = Graph(neo4j_url, username=user, password=pwd)
2021.1之后的版本拜访数据库的形式为:
graph = Graph(neo4j_url, auth=(user, pwd))
以下图为例:
- 图中蕴含一些Person节点,每个Person节点有name、age、work属性;
- 其中“赵赵”节点是多label的节点,除了有Person标签,它还有Teacher标签;
- Person和Person节点之间有共事、街坊、学生、老师等关系;
- 图中还有一些Location节点,它们之间有蕴含关系;
- Person节点和Location节点之间有“到访”关系,“到访”关系具备date和stay_hours两个属性。
1. 通过graph.schema查问图中节点和关系有哪些类型
查看节点的类型用graph.schema.node_labels,查看关系的类型用graph.schema.relationship_types,它们的返回值类型都是frozenset,是不能增删元素的汇合。
>>>graph.schema.node_labels frozenset({'Location', 'Person', 'Teacher'})>>>graph.schema.relationship_typesfrozenset({'到访', '蕴含', '共事', '学生', '老师', '街坊'})
2.应用NodeMatcher查问节点
首先创立一个NodeMatcher对象,用match来指明要匹配哪种label的节点,用where来示意筛选条件(有两种办法)。须要留神的是,匹配胜利返回的是NodeMatcher的对象,要转化成Node对象,能够用first取出符合条件的第一个节点,或者转化成节点的list。
>>>node_matcher = NodeMatcher(graph)>>>node = node_matcher.match("Person").where(age=20).first()>>>nodeNode('Person', age=20, name='李李', work='宇宙电子厂')>>>nodes = list(node_matcher.match("Person").where(age=35))>>>nodes[Node('Person', age=35, name='王王', work='宇宙电子厂')]
where条件有两种写法,一种是把要匹配的属性和值写成key=value的模式,例如下面的where(age=20),这种写法只能依照值是否完全一致来匹配,不能依照值的大小来筛选,如果写成上面这样是会报错的:
node = node_matcher.match("Person").where(age>20).first() # 谬误
想要依照值的大小筛选或者做一些字符串的含糊匹配,能够把条件表达式写成一个字符串,整体放在where语句中,在这个字符串中,能够用 _ 来代指匹配到的节点。上面两个例子,第一个是匹配work属性为“月亮XX”模式的Person节点,另一个是匹配age大于20的Person节点。
>>>node = node_matcher.match("Person").where("_.work =~ '月亮.*'").first()>>>nodeNode('Person', 'Teacher', age=45, name='赵赵', work='月亮中学')>>>nodes = list(node_matcher.match("Person").where("_.age > 20"))>>>nodes[Node('Person', age=35, name='王王', work='宇宙电子厂'), Node('Person', age=30, name='张张', work='宇宙电子厂'), Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学')]
将NodeMatcher返回的后果转化为Node数据类型或者Node的list之后,拜访其中的属性也就非常简略了,如下面最初一例的后果,拜访其中第一个节点的name属性:
>>>nodes[0]['name']'王王'
3. 应用RelationshipMatcher查问关系
RelationshipMatcher的match办法有三个及以上参数:
- 第一个参数是节点的序列或者set,能够为None,为None示意任意节点均可;
- 第二个参数是关系的类型,能够为None,为None示意任意类型的关系均可;
- 第三个参数开始是要匹配的属性,写成key=value的模式。
match办法的返回值是RelationshipMatcher类型,须要通过first转化成Relationship数据结构,或者转化为list。
举例说明,比方想要查问“李李”节点的所有关系。先查问出节点,再查问节点的关系,r_type=None示意任意类型的关系均可。返回的关系包含到访、共事。
>>>node1 = node_matcher.match("Person").where(name='李李').first()>>>relationship = list(relationship_matcher.match([node1], r_type=None))>>>relationship[到访(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Location', name='禄口机场'), date='2021/7/16', stay_hours=1), 共事(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Person', age=30, name='张张', work='宇宙电子厂')), 共事(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Person', age=35, name='王王', work='宇宙电子厂'))]
例2,查问“李李”和“张张”的关系,两个节点的程序示意了要匹配的关系的方向。所以在整个图中“李李”和“张张”节点之间的共事关系是双向的,然而查问后果只给出了从“张张”节点到“李李”节点的一条关系。
>>>node1 = node_matcher.match("Person").where(name='李李').first()>>>node2 = node_matcher.match("Person").where(name='张张').first()>>>relationship = list(relationship_matcher.match((node2,node1), r_type=None))>>>relationship[共事(Node('Person', age=30, name='张张', work='宇宙电子厂'), Node('Person', age=20, name='李李', work='宇宙电子厂'))]
例3,查问图中某一类关系,第一个参数为None,第二个参数r_type指定关系类型,这里查问了图中所有的共事关系。
>>>relationship = list(relationship_matcher.match(None, r_type='共事'))>>>relationship[共事(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Person', age=30, name='张张', work='宇宙电子厂')), 共事(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Person', age=35, name='王王', work='宇宙电子厂')), 共事(Node('Person', age=35, name='王王', work='宇宙电子厂'), Node('Person', age=20, name='李李', work='宇宙电子厂')), 共事(Node('Person', age=30, name='张张', work='宇宙电子厂'), Node('Person', age=20, name='李李', work='宇宙电子厂'))]
例4,在查问关系时依照属性的值筛选,能够将该属性写为key=value的模式作为match办法的第三个参数。这里,查问图中的到访关系,并且stay_hours属性为1。
>>>relationship = list(relationship_matcher.match(None, r_type='到访', stay_hours=1))>>>relationship[到访(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Location', name='禄口机场'), date='2021/7/16', stay_hours=1)]
尽管Py2neo的手册上没有写,但其实RelationshipMatcher也能够接上where办法,依照属性的值筛选关系。下面这个例子也能够写作上面这种模式,成果是一样的。
relationship = list(relationship_matcher.match(None, r_type='到访').where(stay_hours=1))
同样,在where办法中也能够写一个字符串表达式,实现按值大小来筛选关系。例如要筛选出所有到访关系,且stay_hours>=1的关系时,能够这样写:
>>>relationship = list(relationship_matcher.match(None, r_type='到访').where("_.stay_hours>=1"))>>>relationship[到访(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Location', name='禄口机场'), date='2021/7/16', stay_hours=1), 到访(Node('Person', age=20, name='刘刘', work='地球电子商务公司'), Node('Location', name='禄口机场'), date='2021/7/19', stay_hours=4)]
如何拜访返回的后果中的各个属性呢,Relationship其实是蕴含了一对起止节点:start_node和end_node,蕴含了关系的类型,而关系的属性是以字典模式存在的,能够用get办法来获取属性的值。
获取关系的起止节点:
>>>print(relationship[0].start_node['name'])>>>print(relationship[0].end_node['name'])李李禄口机场
获取关系的类型的文本字符串
>>>print(relationship[0])>>>print(type(relationship[0]).__name__)(李李)-[:到访 {date: '2021/7/16', stay_hours: 1}]->(禄口机场)到访
获取关系中的属性和值
>>>print(relationship[0].keys())>>>print(relationship[0].values())>>>print(relationship[0].get('date'))dict_keys(['date', 'stay_hours'])dict_values(['2021/7/16', 1])2021/7/16
4.通过执行Cypher语句查问
NodeMatcher和RelationshipMatcher可能表白的匹配条件绝对简略,更加简单的查问还是须要用Cypher语句来表白。Py2neo自身反对执行Cypher语句的执行,能够将简单的查问写成Cypher语句,通过graph.run办法查问,返回的后果能够转化为pandas.DataFrame或者pandas.Series对象,从而和其余数据分析工具无缝连接。
例如,要查问Person节点,并满足work属性为“宇宙电子厂”。Cypher语句中能够应用WHERE接条件表达式,应用AS将返回的属性改名,返回多个属性时,用xxx AS x, yyy AS y。graph.run办法之后再接to_data_frame()能够将返回的数据变成pandas的DataFrame对象,并且用AS改过的属性名即为DataFrame中的列名。
cypher_ = "MATCH (n:Person) \WHERE n.work='宇宙电子厂' \RETURN n.name AS name, n.age AS age "df = graph.run(cypher_).to_data_frame() # pd.DataFrame
例2,查问一个已知节点和其余哪些节点有关系,有什么样的关系。Cypher语言查问关系时用 < 或者 > 示意方向,这里须要返回type(r),间接返回r的话后果里是空值。
>>>cypher_ = "MATCH (n:Person)-[r]->(m:Person) \WHERE n.name='李李' \RETURN type(r) AS type,m.name AS name">>>df = graph.run(cypher_).to_data_frame() # pd.DataFrame
例3,Cypher语言还能够查问门路,因为不确定返回的门路数量,所以最好先将后果转化为pandas.Series,再遍历拜访其中每条门路的节点和关系。
这里查问的是“赵赵”节点和“王王”节点之间的关系门路,关系指定为共事或街坊,关系不超过4层。
>>>cypher_ = "MATCH path=(m:Person)-[:共事|街坊*1..4]->(n:Person) \WHERE m.name='赵赵' AND n.name='王王' \RETURN path">>>s = graph.run(cypher_).to_series()>>>print(len(s))>>>s[0]1Path(Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学'),街坊(Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学'), Node('Person', age=30, name='张张', work='宇宙电子厂')), 共事(Node('Person', age=30, name='张张', work='宇宙电子厂'), Node('Person', age=20, name='李李', work='宇宙电子厂')), 共事(Node('Person', age=20, name='李李', work='宇宙电子厂'), Node('Person', age=35, name='王王', work='宇宙电子厂')))
这里查问到的关系门路数量仅有1条。从上图的后果中也能够看进去,Path是一个比较复杂的构造,Path中的节点和关系别离用nodes和relationships示意,并且是依照门路上节点和关系的程序别离寄存的。这里给出一段示例代码,对每一个门路都做了间接打印path数据结构和本人组织门路文本。
for path in s: # 间接打印path print(path) # 获取门路中的节点和关系 nodes = path.nodes relationshis = path.relationships # 本人组织门路文本 path_text = "" for n,r in zip(nodes, relationshis): # 每次退出一个节点和一个关系的类型 path_text += "{} - {} - ".format(n['name'], type(r).__name__) # 别忘了最初一个节点 path_text += nodes[-1]['name'] + '\n' print(path_text)
运行这段代码得的后果如下所示,下面一行是间接打印门路的后果,上面一行是本人组织文本失去的后果。
(赵赵)-[:街坊 {}]->(张张)-[:共事 {}]->(李李)-[:共事 {}]->(王王)赵赵 - 街坊 - 张张 - 共事 - 李李 - 共事 - 王王
小结
应用Py2neo查问Neo4j中的节点、关系和门路时,条件简略的查问能够通NodeMatcher和RelationshipMatcher来实现。而较为简单的查问,能够写成Cypher语句来查问,查问后果能够转化为pandas的DataFrame或者Series数据类型,与其余数据分析工具联合。
我的Python版本
>>> import sys>>> print(sys.version)3.7.6 (default, Jan 8 2020, 13:42:34) [Clang 4.0.1 (tags/RELEASE_401/final)]