pyneoinstance 1.1.2__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/MANIFEST.in +1 -0
- {pyneoinstance-1.1.2/pyneoinstance.egg-info → pyneoinstance-2.2.0}/PKG-INFO +84 -10
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/README.md +79 -7
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/__init__.py +2 -1
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/database/context.py +1 -1
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/database/neo4jdbms.py +407 -7
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/util/__init__.py +2 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/util/functions.py +43 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0/pyneoinstance.egg-info}/PKG-INFO +84 -10
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/SOURCES.txt +2 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/requires.txt +1 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/top_level.txt +1 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyproject.toml +4 -3
- pyneoinstance-2.2.0/setup.py +5 -0
- pyneoinstance-2.2.0/test/__init__.py +3 -0
- pyneoinstance-2.2.0/test/context.py +8 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/cypher_queries.yaml +1 -1
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test_neo4jdb.py +90 -1
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test_util_functions.py +25 -1
- pyneoinstance-1.1.2/setup.py +0 -3
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/LICENSE +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/database/__init__.py +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/__init__.py +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/context.py +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/helpers.py +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/loadyaml.py +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/dependency_links.txt +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/setup.cfg +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/load_files.yaml +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/movies.csv +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/people.csv +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/roles.csv +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test.py +0 -0
- {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test_fileload.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyneoinstance
|
|
3
|
-
Version:
|
|
4
|
-
Summary: Handle Neo4j read and write transactions
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Handle Neo4j read and write transactions and provide methos for performing graph EDA
|
|
5
5
|
Author-email: "Jose A. Alvarado-Guzman" <jose.alvarado-guzman@neo4j.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
7
7
|
Version 3, 29 June 2007
|
|
@@ -688,19 +688,31 @@ License-File: LICENSE
|
|
|
688
688
|
Requires-Dist: neo4j>=5.0.0
|
|
689
689
|
Requires-Dist: pandas
|
|
690
690
|
Requires-Dist: PyYAML
|
|
691
|
+
Requires-Dist: pyvis
|
|
692
|
+
Dynamic: license-file
|
|
691
693
|
|
|
692
694
|
# PyNeoInstance Package
|
|
693
695
|
|
|
694
696
|
## What is it?
|
|
695
697
|
|
|
696
|
-
PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
|
|
698
|
+
PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j and performing Exploratory Data Analysis (EDA) of your graph.
|
|
699
|
+
|
|
697
700
|
It supports the following features:
|
|
698
701
|
|
|
699
|
-
-
|
|
700
|
-
-
|
|
701
|
-
-
|
|
702
|
-
-
|
|
703
|
-
-
|
|
702
|
+
- __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
|
|
703
|
+
- __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
|
|
704
|
+
- __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
|
|
705
|
+
- __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
|
|
706
|
+
- __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
|
|
707
|
+
- __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
|
|
708
|
+
- __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
|
|
709
|
+
- __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
|
|
710
|
+
- __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
|
|
711
|
+
- __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
|
|
712
|
+
- __<u>Constraints</u>__: Get information of all constraints in the graph.
|
|
713
|
+
- __<u>Indexes</u>__: Get information of all indexes in the graph.
|
|
714
|
+
- __<u>Schema Visualization</u>__: Visualize the graph schema.
|
|
715
|
+
|
|
704
716
|
|
|
705
717
|
## How to install it?
|
|
706
718
|
|
|
@@ -921,7 +933,69 @@ person_load_result = graph.execute_write_query_with_data(
|
|
|
921
933
|
workers = 5
|
|
922
934
|
)
|
|
923
935
|
```
|
|
936
|
+
## Graph EDA
|
|
937
|
+
|
|
938
|
+
### Node Label Frequency
|
|
939
|
+
The get_node_label_freq method provides the frequency and relative frequency of all nodes in the graph. This method consider all node labels as single labels, if you want the distribution considering multiple labels use the get_node_multilabel_freq method.
|
|
940
|
+
```python
|
|
941
|
+
graph.get_node_label_freq(database='mydbname')
|
|
942
|
+
```
|
|
943
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
|
|
944
|
+
|
|
945
|
+
### Relationship Type Frequency
|
|
946
|
+
The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
|
|
947
|
+
```python
|
|
948
|
+
graph.get_rela_type_freq(database='mydbname')
|
|
949
|
+
```
|
|
950
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
|
|
951
|
+
|
|
952
|
+
### Relationship Source-Target Frequency
|
|
953
|
+
The get_rela_source_target_freq method provides the frequency and relative frequency of all relationship types considering the node label of the source and targets of the relationships.
|
|
954
|
+
```python
|
|
955
|
+
graph.get_rela_type_freq(database='mydbname')
|
|
956
|
+
```
|
|
957
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
|
|
958
|
+
|
|
959
|
+
### Properties
|
|
960
|
+
The get_properties method provides information about all the nodes and relationships properties.
|
|
961
|
+
```python
|
|
962
|
+
graph.graph.get_properties(database='mydbname')
|
|
963
|
+
```
|
|
964
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
|
|
965
|
+
|
|
966
|
+
### Constraints
|
|
967
|
+
The get_constraints method provides information about all the graph constraints.
|
|
968
|
+
```python
|
|
969
|
+
graph.get_constraints(database='mydbname')
|
|
970
|
+
```
|
|
971
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
|
|
972
|
+
|
|
973
|
+
### Indexes
|
|
974
|
+
The get_indexes method provides information about all the graph indexes.
|
|
975
|
+
```python
|
|
976
|
+
graph.get_indexes(database='mydbname')
|
|
977
|
+
```
|
|
978
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
|
|
979
|
+
|
|
980
|
+
### Schema Visualization
|
|
981
|
+
The graph.get_schema_visualization provides an interactive UI with the visualization of the graph schema with the ability to filter the schema. This UI can be access via a Notebook or HTML page. This visualization displays the node labels as string close to nodes, relationship types are only display on mouse hover.
|
|
982
|
+
|
|
983
|
+
```python
|
|
984
|
+
schema = graph.get_schema_visualization(database='mydbname')
|
|
985
|
+
schema.show('schema.html')
|
|
986
|
+
```
|
|
987
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
|
|
988
|
+
|
|
989
|
+
To access the UI via HTTML page just set the parameter notebook to false in the show method and open the html file in your browser.
|
|
990
|
+
|
|
991
|
+
```python
|
|
992
|
+
schema = graph.get_schema_visualization(database='mydbname')
|
|
993
|
+
schema.show('schema.html',notebook=False)
|
|
994
|
+
```
|
|
995
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
|
|
924
996
|
|
|
925
997
|
## More detailed examples
|
|
926
998
|
|
|
927
|
-
For more detailed examples please
|
|
999
|
+
For more detailed examples please refer to the following articles:
|
|
1000
|
+
* Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
|
|
1001
|
+
* Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
|
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## What is it?
|
|
4
4
|
|
|
5
|
-
PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
|
|
5
|
+
PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j and performing Exploratory Data Analysis (EDA) of your graph.
|
|
6
|
+
|
|
6
7
|
It supports the following features:
|
|
7
8
|
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
|
|
10
|
+
- __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
|
|
11
|
+
- __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
|
|
12
|
+
- __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
|
|
13
|
+
- __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
|
|
14
|
+
- __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
|
|
15
|
+
- __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
|
|
16
|
+
- __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
|
|
17
|
+
- __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
|
|
18
|
+
- __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
|
|
19
|
+
- __<u>Constraints</u>__: Get information of all constraints in the graph.
|
|
20
|
+
- __<u>Indexes</u>__: Get information of all indexes in the graph.
|
|
21
|
+
- __<u>Schema Visualization</u>__: Visualize the graph schema.
|
|
22
|
+
|
|
13
23
|
|
|
14
24
|
## How to install it?
|
|
15
25
|
|
|
@@ -230,7 +240,69 @@ person_load_result = graph.execute_write_query_with_data(
|
|
|
230
240
|
workers = 5
|
|
231
241
|
)
|
|
232
242
|
```
|
|
243
|
+
## Graph EDA
|
|
244
|
+
|
|
245
|
+
### Node Label Frequency
|
|
246
|
+
The get_node_label_freq method provides the frequency and relative frequency of all nodes in the graph. This method consider all node labels as single labels, if you want the distribution considering multiple labels use the get_node_multilabel_freq method.
|
|
247
|
+
```python
|
|
248
|
+
graph.get_node_label_freq(database='mydbname')
|
|
249
|
+
```
|
|
250
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
|
|
251
|
+
|
|
252
|
+
### Relationship Type Frequency
|
|
253
|
+
The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
|
|
254
|
+
```python
|
|
255
|
+
graph.get_rela_type_freq(database='mydbname')
|
|
256
|
+
```
|
|
257
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
|
|
258
|
+
|
|
259
|
+
### Relationship Source-Target Frequency
|
|
260
|
+
The get_rela_source_target_freq method provides the frequency and relative frequency of all relationship types considering the node label of the source and targets of the relationships.
|
|
261
|
+
```python
|
|
262
|
+
graph.get_rela_type_freq(database='mydbname')
|
|
263
|
+
```
|
|
264
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
|
|
265
|
+
|
|
266
|
+
### Properties
|
|
267
|
+
The get_properties method provides information about all the nodes and relationships properties.
|
|
268
|
+
```python
|
|
269
|
+
graph.graph.get_properties(database='mydbname')
|
|
270
|
+
```
|
|
271
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
|
|
272
|
+
|
|
273
|
+
### Constraints
|
|
274
|
+
The get_constraints method provides information about all the graph constraints.
|
|
275
|
+
```python
|
|
276
|
+
graph.get_constraints(database='mydbname')
|
|
277
|
+
```
|
|
278
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
|
|
279
|
+
|
|
280
|
+
### Indexes
|
|
281
|
+
The get_indexes method provides information about all the graph indexes.
|
|
282
|
+
```python
|
|
283
|
+
graph.get_indexes(database='mydbname')
|
|
284
|
+
```
|
|
285
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
|
|
286
|
+
|
|
287
|
+
### Schema Visualization
|
|
288
|
+
The graph.get_schema_visualization provides an interactive UI with the visualization of the graph schema with the ability to filter the schema. This UI can be access via a Notebook or HTML page. This visualization displays the node labels as string close to nodes, relationship types are only display on mouse hover.
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
schema = graph.get_schema_visualization(database='mydbname')
|
|
292
|
+
schema.show('schema.html')
|
|
293
|
+
```
|
|
294
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
|
|
295
|
+
|
|
296
|
+
To access the UI via HTTML page just set the parameter notebook to false in the show method and open the html file in your browser.
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
schema = graph.get_schema_visualization(database='mydbname')
|
|
300
|
+
schema.show('schema.html',notebook=False)
|
|
301
|
+
```
|
|
302
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
|
|
233
303
|
|
|
234
304
|
## More detailed examples
|
|
235
305
|
|
|
236
|
-
For more detailed examples please
|
|
306
|
+
For more detailed examples please refer to the following articles:
|
|
307
|
+
* Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
|
|
308
|
+
* Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
|
|
@@ -13,13 +13,16 @@ from typing import Optional, List, Dict, Any
|
|
|
13
13
|
from collections import defaultdict
|
|
14
14
|
import multiprocessing as mp
|
|
15
15
|
import logging
|
|
16
|
+
import re
|
|
16
17
|
import numpy as np
|
|
17
18
|
from pandas import DataFrame
|
|
18
19
|
from neo4j import GraphDatabase
|
|
20
|
+
from pyvis.network import Network
|
|
19
21
|
from neo4j.exceptions import ServiceUnavailable
|
|
20
22
|
from neo4j.exceptions import AuthError
|
|
21
23
|
from neo4j.exceptions import ClientError
|
|
22
24
|
from neo4j.exceptions import ConfigurationError
|
|
25
|
+
from .context import get_logger, get_parallel_info, get_columns_diff
|
|
23
26
|
|
|
24
27
|
def _get_driver(neo_info : Dict[str, str]):
|
|
25
28
|
try:
|
|
@@ -167,8 +170,12 @@ class Neo4jInstance:
|
|
|
167
170
|
self.neo_info['password'] = password
|
|
168
171
|
self.neo_info['encrypted'] = kwargs.get('encrypted') or ''
|
|
169
172
|
self.__results = defaultdict(int)
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
self.__logger = get_logger('pyneoinstance', logging.WARNING)
|
|
174
|
+
stream_handler = logging.StreamHandler()
|
|
175
|
+
formatter = logging.Formatter(
|
|
176
|
+
'%(levelname)s - %(message)s')
|
|
177
|
+
stream_handler.setFormatter(formatter)
|
|
178
|
+
self.__logger.addHandler(stream_handler)
|
|
172
179
|
def execute_read_query(self, query: str,
|
|
173
180
|
database: Optional[str] = None,
|
|
174
181
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -288,7 +295,8 @@ class Neo4jInstance:
|
|
|
288
295
|
result = self.execute_write_queries([query], database, parameters)
|
|
289
296
|
return result
|
|
290
297
|
|
|
291
|
-
def execute_write_queries_with_data(self, queries: List[str],
|
|
298
|
+
def execute_write_queries_with_data(self, queries: List[str],
|
|
299
|
+
data: DataFrame,
|
|
292
300
|
database: Optional[str] = None,
|
|
293
301
|
partitions: Optional[int] = 1,
|
|
294
302
|
parallel: Optional[bool] = False,
|
|
@@ -334,7 +342,7 @@ class Neo4jInstance:
|
|
|
334
342
|
if partitions > data.shape[0]:
|
|
335
343
|
raise ValueError(
|
|
336
344
|
"The batch size cannot be greater than the number of rows in the data.")
|
|
337
|
-
data_chunks = np.array_split(data,partitions)
|
|
345
|
+
data_chunks = np.array_split(data.index,partitions)
|
|
338
346
|
if parameters:
|
|
339
347
|
params = parameters
|
|
340
348
|
else:
|
|
@@ -345,19 +353,27 @@ class Neo4jInstance:
|
|
|
345
353
|
else:
|
|
346
354
|
workers_num = mp.cpu_count()
|
|
347
355
|
for query in queries:
|
|
356
|
+
col_diff = get_columns_diff(query,data.columns)
|
|
357
|
+
msg = f'This columns are not in your data: {col_diff}'
|
|
358
|
+
if len(col_diff)!=0:
|
|
359
|
+
self.__logger.warning(msg)
|
|
348
360
|
with mp.Pool(workers_num) as worker_pool:
|
|
349
361
|
results = worker_pool.starmap(
|
|
350
362
|
_execute_write_parallel,
|
|
351
|
-
[(self.neo_info, database,query, d.
|
|
363
|
+
[(self.neo_info, database,query, data.iloc[d].to_dict('records'),
|
|
352
364
|
params) for d in data_chunks]
|
|
353
365
|
)
|
|
354
366
|
else:
|
|
355
367
|
results = []
|
|
356
368
|
with _get_driver(self.neo_info) as driver:
|
|
357
369
|
with _get_session(driver, database) as session:
|
|
358
|
-
for
|
|
359
|
-
rows_dict = {'rows':
|
|
370
|
+
for idx in data_chunks:
|
|
371
|
+
rows_dict = {'rows': data.iloc[idx].to_dict('records')}
|
|
360
372
|
for query in queries:
|
|
373
|
+
col_diff = get_columns_diff(query,data.columns)
|
|
374
|
+
msg = f'This columns are not in your data: {col_diff}'
|
|
375
|
+
if len(col_diff)!=0:
|
|
376
|
+
self.__logger.warning(msg)
|
|
361
377
|
results.append(_execute_write(session,
|
|
362
378
|
query,
|
|
363
379
|
rows_dict['rows'],
|
|
@@ -421,3 +437,387 @@ class Neo4jInstance:
|
|
|
421
437
|
result = self.execute_write_queries_with_data(
|
|
422
438
|
[query], data,database, partitions, parallel, workers, params)
|
|
423
439
|
return result
|
|
440
|
+
|
|
441
|
+
def get_node_label_freq(self, database: Optional[str] = None) -> DataFrame:
|
|
442
|
+
"""Use to obtain the graph node label frequency.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
database : str, optional
|
|
447
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
448
|
+
If not provided the default database is going to be use.
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
DataFrame
|
|
453
|
+
Pandas DataFrame object containing the frequency and relative
|
|
454
|
+
frequency of node labels.
|
|
455
|
+
|
|
456
|
+
Raises
|
|
457
|
+
------
|
|
458
|
+
ClientError
|
|
459
|
+
When APOC Library is not install correctly in your Neo4j
|
|
460
|
+
deployment.
|
|
461
|
+
"""
|
|
462
|
+
error_msg = """
|
|
463
|
+
APOC Library not detected.
|
|
464
|
+
Please install it before procceding.
|
|
465
|
+
"""
|
|
466
|
+
query = """
|
|
467
|
+
MATCH(n)
|
|
468
|
+
WITH count(*) AS nodeCount
|
|
469
|
+
CALL db.labels() YIELD label
|
|
470
|
+
CALL apoc.cypher.run('MATCH (:`'+label+'`) RETURN count(*) as freq',{}) YIELD value
|
|
471
|
+
WITH nodeCount,label,value.freq AS freq
|
|
472
|
+
WITH *, 10^3 AS scaleFactor, toFloat(freq)/toFloat(nodeCount) AS relFreq
|
|
473
|
+
RETURN label AS nodeLabel,
|
|
474
|
+
freq AS frequency,
|
|
475
|
+
round(relFreq*scaleFactor)/scaleFactor AS relativeFrequency
|
|
476
|
+
ORDER BY freq DESC
|
|
477
|
+
"""
|
|
478
|
+
with _get_driver(self.neo_info) as driver:
|
|
479
|
+
with _get_session(driver, database) as session:
|
|
480
|
+
try:
|
|
481
|
+
result = session.execute_read(
|
|
482
|
+
_read_transaction_function,query=query)
|
|
483
|
+
except ServiceUnavailable as exception:
|
|
484
|
+
raise ServiceUnavailable() from exception
|
|
485
|
+
except ClientError:
|
|
486
|
+
raise ClientError(error_msg)
|
|
487
|
+
return result
|
|
488
|
+
|
|
489
|
+
def get_node_multilabel_freq(self, database: Optional[str] = None) -> DataFrame:
|
|
490
|
+
"""Use to obtain the graph node multi-label frequency.
|
|
491
|
+
|
|
492
|
+
Parameters
|
|
493
|
+
----------
|
|
494
|
+
database : str, optional
|
|
495
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
496
|
+
If not provided the default database is going to be use.
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
DataFrame
|
|
501
|
+
Pandas DataFrame object containing the frequency and relative
|
|
502
|
+
frequency of node with multiple labels.
|
|
503
|
+
|
|
504
|
+
Raises
|
|
505
|
+
------
|
|
506
|
+
ClientError
|
|
507
|
+
When APOC Library is not install correctly in your Neo4j
|
|
508
|
+
deployment.
|
|
509
|
+
"""
|
|
510
|
+
error_msg = """
|
|
511
|
+
APOC Library not detected.
|
|
512
|
+
Please install it before procceding.
|
|
513
|
+
"""
|
|
514
|
+
query = """
|
|
515
|
+
MATCH (n)
|
|
516
|
+
WITH labels(n) as nodeLabels
|
|
517
|
+
WHERE size(nodeLabels)>1
|
|
518
|
+
RETURN nodeLabels, count(*) as frequency
|
|
519
|
+
"""
|
|
520
|
+
with _get_driver(self.neo_info) as driver:
|
|
521
|
+
with _get_session(driver, database) as session:
|
|
522
|
+
try:
|
|
523
|
+
result = session.execute_read(
|
|
524
|
+
_read_transaction_function,query=query)
|
|
525
|
+
except ServiceUnavailable as exception:
|
|
526
|
+
raise ServiceUnavailable() from exception
|
|
527
|
+
except ClientError:
|
|
528
|
+
raise ClientError(error_msg)
|
|
529
|
+
return result
|
|
530
|
+
|
|
531
|
+
def get_rela_type_freq(self, database: Optional[str] = None) -> DataFrame:
|
|
532
|
+
"""Use to obtain the graph relationship type frequency.
|
|
533
|
+
|
|
534
|
+
Parameters
|
|
535
|
+
----------
|
|
536
|
+
database : str, optional
|
|
537
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
538
|
+
If not provided the default database is going to be use.
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
DataFrame
|
|
543
|
+
Pandas DataFrame object containing the frequency and relative
|
|
544
|
+
frequency of relationship types.
|
|
545
|
+
|
|
546
|
+
Raises
|
|
547
|
+
------
|
|
548
|
+
ClientError
|
|
549
|
+
When APOC Library is not install correctly in your Neo4j
|
|
550
|
+
deployment.
|
|
551
|
+
"""
|
|
552
|
+
error_msg = """
|
|
553
|
+
APOC Library not detected.
|
|
554
|
+
Please install it before procceding.
|
|
555
|
+
"""
|
|
556
|
+
query = """
|
|
557
|
+
MATCH()-[]->()
|
|
558
|
+
WITH count(*) AS relCount
|
|
559
|
+
CALL db.relationshipTypes() YIELD relationshipType as type
|
|
560
|
+
CALL apoc.cypher.run('MATCH ()-[:`'+type+'`]->() RETURN count(*) as freq',{})
|
|
561
|
+
YIELD value
|
|
562
|
+
WITH type AS relationshipType, value.freq AS freq,relCount
|
|
563
|
+
WITH *,3 AS presicion
|
|
564
|
+
WITH *, 10^presicion AS factor,toFloat(freq)/toFloat(relCount) as relFreq
|
|
565
|
+
RETURN relationshipType, freq AS frequency,
|
|
566
|
+
round(relFreq*factor)/factor AS relativeFrequency
|
|
567
|
+
ORDER BY freq DESC;
|
|
568
|
+
"""
|
|
569
|
+
with _get_driver(self.neo_info) as driver:
|
|
570
|
+
with _get_session(driver, database) as session:
|
|
571
|
+
try:
|
|
572
|
+
result = session.execute_read(
|
|
573
|
+
_read_transaction_function,query=query)
|
|
574
|
+
except ServiceUnavailable as exception:
|
|
575
|
+
raise ServiceUnavailable() from exception
|
|
576
|
+
except ClientError:
|
|
577
|
+
raise ClientError(error_msg)
|
|
578
|
+
return result
|
|
579
|
+
|
|
580
|
+
def get_properties(self, database: Optional[str] = None) -> DataFrame:
|
|
581
|
+
"""Use to obtain the node and relationship properties.
|
|
582
|
+
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
database : str, optional
|
|
586
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
587
|
+
If not provided the default database is going to be use.
|
|
588
|
+
|
|
589
|
+
Returns
|
|
590
|
+
-------
|
|
591
|
+
DataFrame
|
|
592
|
+
Pandas DataFrame object containing information about all nodes
|
|
593
|
+
and relationships properties.
|
|
594
|
+
|
|
595
|
+
Raises
|
|
596
|
+
------
|
|
597
|
+
ClientError
|
|
598
|
+
When APOC Library is not install correctly in your Neo4j
|
|
599
|
+
deployment.
|
|
600
|
+
"""
|
|
601
|
+
error_msg = """
|
|
602
|
+
APOC Library not detected.
|
|
603
|
+
Please install it before procceding.
|
|
604
|
+
"""
|
|
605
|
+
query = """
|
|
606
|
+
CALL apoc.meta.data() YIELD label,property,type,elementType
|
|
607
|
+
WHERE type<>'RELATIONSHIP'
|
|
608
|
+
RETURN elementType,label,property,type
|
|
609
|
+
ORDER BY elementType,label,property;
|
|
610
|
+
"""
|
|
611
|
+
with _get_driver(self.neo_info) as driver:
|
|
612
|
+
with _get_session(driver, database) as session:
|
|
613
|
+
try:
|
|
614
|
+
result = session.execute_read(
|
|
615
|
+
_read_transaction_function,query=query)
|
|
616
|
+
except ServiceUnavailable as exception:
|
|
617
|
+
raise ServiceUnavailable() from exception
|
|
618
|
+
except ClientError:
|
|
619
|
+
raise ClientError(error_msg)
|
|
620
|
+
return result
|
|
621
|
+
|
|
622
|
+
def get_constraints(self, database: Optional[str] = None) -> DataFrame:
|
|
623
|
+
"""Use to obtain the constraints in the graph.
|
|
624
|
+
|
|
625
|
+
Parameters
|
|
626
|
+
----------
|
|
627
|
+
database : str, optional
|
|
628
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
629
|
+
If not provided the default database is going to be use.
|
|
630
|
+
|
|
631
|
+
Returns
|
|
632
|
+
-------
|
|
633
|
+
DataFrame
|
|
634
|
+
Pandas DataFrame object containing information about all the
|
|
635
|
+
constraints.
|
|
636
|
+
|
|
637
|
+
Raises
|
|
638
|
+
------
|
|
639
|
+
ClientError
|
|
640
|
+
When APOC Library is not install correctly in your Neo4j
|
|
641
|
+
deployment.
|
|
642
|
+
"""
|
|
643
|
+
error_msg = """
|
|
644
|
+
APOC Library not detected.
|
|
645
|
+
Please install it before procceding.
|
|
646
|
+
"""
|
|
647
|
+
query = """
|
|
648
|
+
SHOW CONSTRAINTS
|
|
649
|
+
"""
|
|
650
|
+
with _get_driver(self.neo_info) as driver:
|
|
651
|
+
with _get_session(driver, database) as session:
|
|
652
|
+
try:
|
|
653
|
+
result = session.execute_read(
|
|
654
|
+
_read_transaction_function,query=query)
|
|
655
|
+
except ServiceUnavailable as exception:
|
|
656
|
+
raise ServiceUnavailable() from exception
|
|
657
|
+
except ClientError:
|
|
658
|
+
raise ClientError(error_msg)
|
|
659
|
+
return result
|
|
660
|
+
|
|
661
|
+
def get_indexes(self, database: Optional[str] = None) -> DataFrame:
|
|
662
|
+
"""Use to obtain the indexes in the graph.
|
|
663
|
+
|
|
664
|
+
Parameters
|
|
665
|
+
----------
|
|
666
|
+
database : str, optional
|
|
667
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
668
|
+
If not provided the default database is going to be use.
|
|
669
|
+
|
|
670
|
+
Returns
|
|
671
|
+
-------
|
|
672
|
+
DataFrame
|
|
673
|
+
Pandas DataFrame object containing information about all the
|
|
674
|
+
indexes.
|
|
675
|
+
|
|
676
|
+
Raises
|
|
677
|
+
------
|
|
678
|
+
ClientError
|
|
679
|
+
When APOC Library is not install correctly in your Neo4j
|
|
680
|
+
deployment.
|
|
681
|
+
"""
|
|
682
|
+
error_msg = """
|
|
683
|
+
APOC Library not detected.
|
|
684
|
+
Please install it before procceding.
|
|
685
|
+
"""
|
|
686
|
+
query = """
|
|
687
|
+
SHOW INDEX
|
|
688
|
+
"""
|
|
689
|
+
with _get_driver(self.neo_info) as driver:
|
|
690
|
+
with _get_session(driver, database) as session:
|
|
691
|
+
try:
|
|
692
|
+
result = session.execute_read(
|
|
693
|
+
_read_transaction_function,query=query)
|
|
694
|
+
except ServiceUnavailable as exception:
|
|
695
|
+
raise ServiceUnavailable() from exception
|
|
696
|
+
except ClientError:
|
|
697
|
+
raise ClientError(error_msg)
|
|
698
|
+
return result
|
|
699
|
+
|
|
700
|
+
def get_rela_source_target_freq(self, database: Optional[str] = None) -> DataFrame:
|
|
701
|
+
"""Use to obtain the relationships source and target frequency.
|
|
702
|
+
|
|
703
|
+
Parameters
|
|
704
|
+
----------
|
|
705
|
+
database : str, optional
|
|
706
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
707
|
+
If not provided the default database is going to be use.
|
|
708
|
+
|
|
709
|
+
Returns
|
|
710
|
+
-------
|
|
711
|
+
DataFrame
|
|
712
|
+
Pandas DataFrame object containing the frequency of
|
|
713
|
+
relationships cource and target frequency.
|
|
714
|
+
|
|
715
|
+
Raises
|
|
716
|
+
------
|
|
717
|
+
ClientError
|
|
718
|
+
When APOC Library is not install correctly in your Neo4j
|
|
719
|
+
deployment.
|
|
720
|
+
"""
|
|
721
|
+
error_msg = """
|
|
722
|
+
APOC Library not detected.
|
|
723
|
+
Please install it before procceding.
|
|
724
|
+
"""
|
|
725
|
+
query = """
|
|
726
|
+
CALL apoc.meta.stats() YIELD relTypes
|
|
727
|
+
"""
|
|
728
|
+
with _get_driver(self.neo_info) as driver:
|
|
729
|
+
with _get_session(driver, database) as session:
|
|
730
|
+
try:
|
|
731
|
+
result = session.execute_read(
|
|
732
|
+
_read_transaction_function,query=query)
|
|
733
|
+
except ServiceUnavailable as exception:
|
|
734
|
+
raise ServiceUnavailable() from exception
|
|
735
|
+
except ClientError:
|
|
736
|
+
raise ClientError(error_msg)
|
|
737
|
+
stat_dict = result.iloc[0,0]
|
|
738
|
+
stats_dicts = []
|
|
739
|
+
for key,value in stat_dict.items():
|
|
740
|
+
nodes = re.findall(r"\(:?(\w*)\)",key)
|
|
741
|
+
relationship = re.findall(r"\[:?(\w*)\]",key)[0]
|
|
742
|
+
info = {'sourceLabel':nodes[0],'relationshipType':relationship,'targetLabel':nodes[1],
|
|
743
|
+
'frequency':value}
|
|
744
|
+
stats_dicts.append(info)
|
|
745
|
+
stats_df = DataFrame(stats_dicts)
|
|
746
|
+
stats_df.sort_values(by=['relationshipType','sourceLabel','targetLabel'],inplace=True)
|
|
747
|
+
return stats_df
|
|
748
|
+
|
|
749
|
+
def get_schema_visualization(self, database: Optional[str] = None) -> Network:
|
|
750
|
+
"""Use to visualize the graph data model (schema).
|
|
751
|
+
|
|
752
|
+
Parameters
|
|
753
|
+
----------
|
|
754
|
+
database : str, optional
|
|
755
|
+
Name of the Neo4j database of which to execute the transaction.
|
|
756
|
+
If not provided the default database is going to be use.
|
|
757
|
+
|
|
758
|
+
Returns
|
|
759
|
+
-------
|
|
760
|
+
Network
|
|
761
|
+
Pyvis Network object containing the visualization information.
|
|
762
|
+
|
|
763
|
+
Raises
|
|
764
|
+
------
|
|
765
|
+
ClientError
|
|
766
|
+
When APOC Library is not install correctly in your Neo4j
|
|
767
|
+
deployment.
|
|
768
|
+
"""
|
|
769
|
+
error_msg = """
|
|
770
|
+
APOC Library not detected.
|
|
771
|
+
Please install it before procceding.
|
|
772
|
+
"""
|
|
773
|
+
query = """
|
|
774
|
+
CALL apoc.meta.schema() YIELD value
|
|
775
|
+
"""
|
|
776
|
+
with _get_driver(self.neo_info) as driver:
|
|
777
|
+
with _get_session(driver, database) as session:
|
|
778
|
+
try:
|
|
779
|
+
result = session.execute_read(
|
|
780
|
+
_read_transaction_function,query=query)
|
|
781
|
+
except ServiceUnavailable as exception:
|
|
782
|
+
raise ServiceUnavailable() from exception
|
|
783
|
+
except ClientError:
|
|
784
|
+
raise ClientError(error_msg)
|
|
785
|
+
schema = result.iloc[0,0]
|
|
786
|
+
network = Network(notebook=True,cdn_resources = "remote",directed=True,
|
|
787
|
+
filter_menu=True,height="800px", width="100%")
|
|
788
|
+
options = """
|
|
789
|
+
const options = {
|
|
790
|
+
"physics": {
|
|
791
|
+
"barnesHut": {
|
|
792
|
+
"gravitationalConstant": -13950,
|
|
793
|
+
"centralGravity": 6.15,
|
|
794
|
+
"springLength": 160,
|
|
795
|
+
"damping": 0.23
|
|
796
|
+
},
|
|
797
|
+
"minVelocity": 0.75
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
"""
|
|
801
|
+
network.set_options(options)
|
|
802
|
+
nodes = []
|
|
803
|
+
relationships = set()
|
|
804
|
+
for key in schema.keys():
|
|
805
|
+
if schema[key]['type']=='node':
|
|
806
|
+
nodes.append(key)
|
|
807
|
+
network.add_nodes(nodes)
|
|
808
|
+
for node in nodes:
|
|
809
|
+
for rel in schema[node]['relationships'].keys():
|
|
810
|
+
title = rel
|
|
811
|
+
if schema[node]['relationships'][rel]['direction']=='in':
|
|
812
|
+
target = node
|
|
813
|
+
for label in schema[node]['relationships'][rel]['labels']:
|
|
814
|
+
source = label
|
|
815
|
+
else:
|
|
816
|
+
source = node
|
|
817
|
+
for label in schema[node]['relationships'][rel]['labels']:
|
|
818
|
+
target = label
|
|
819
|
+
relationships.add(source + "|" + title + "|" + target)
|
|
820
|
+
for relationship in relationships:
|
|
821
|
+
rela_parts = relationship.split("|")
|
|
822
|
+
network.add_edge(rela_parts[0],rela_parts[2],title=rela_parts[1])
|
|
823
|
+
return network
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import functools
|
|
5
5
|
import time
|
|
6
6
|
import logging
|
|
7
|
+
import re
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from typing import Tuple, Any, Callable, List
|
|
9
10
|
from urllib.request import urlopen
|
|
@@ -94,3 +95,45 @@ def timing(function : Callable) -> Tuple[Any, float]:
|
|
|
94
95
|
elapsed_time = end - start * 1.0
|
|
95
96
|
return result, elapsed_time
|
|
96
97
|
return wrap
|
|
98
|
+
|
|
99
|
+
def get_parallel_info(num_rows: int, partition_rows=100_000) -> Tuple[int,bool]:
|
|
100
|
+
"""Get the number of partitions and whether to execute execute
|
|
101
|
+
the load in parallel based on the number of rows of a Data Frame.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
num_rows : int
|
|
106
|
+
Number of rows of the Data Frame
|
|
107
|
+
partition_rows : int
|
|
108
|
+
Number of records per partition
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
Tuble[int, bool]
|
|
112
|
+
Tuple containing the number of partitions and parallelims.
|
|
113
|
+
"""
|
|
114
|
+
partitions = 1
|
|
115
|
+
parallel = False
|
|
116
|
+
batches = int(num_rows/partition_rows)
|
|
117
|
+
if batches > 1:
|
|
118
|
+
partitions = batches
|
|
119
|
+
parallel = True
|
|
120
|
+
return (partitions, parallel)
|
|
121
|
+
|
|
122
|
+
def get_columns_diff(query: str, columns: List[str]) -> List[str]:
|
|
123
|
+
"""Get a list of the columns that a Cypher query is trying to access that are
|
|
124
|
+
not in the list of columns provided.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
query : str
|
|
129
|
+
Cypher query
|
|
130
|
+
columns : List[str]
|
|
131
|
+
List containing the column names of a Pandas Data Frame
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
List[str]
|
|
135
|
+
List containing the columns that Cypher is trying to access that are not
|
|
136
|
+
included in the columns names of the data frame.
|
|
137
|
+
"""
|
|
138
|
+
cypher_attributes = set([w.split('.')[1] for w in re.findall(r'\brow\.\w+',query)])
|
|
139
|
+
return [a for a in cypher_attributes if a not in columns]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyneoinstance
|
|
3
|
-
Version:
|
|
4
|
-
Summary: Handle Neo4j read and write transactions
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Handle Neo4j read and write transactions and provide methos for performing graph EDA
|
|
5
5
|
Author-email: "Jose A. Alvarado-Guzman" <jose.alvarado-guzman@neo4j.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
7
7
|
Version 3, 29 June 2007
|
|
@@ -688,19 +688,31 @@ License-File: LICENSE
|
|
|
688
688
|
Requires-Dist: neo4j>=5.0.0
|
|
689
689
|
Requires-Dist: pandas
|
|
690
690
|
Requires-Dist: PyYAML
|
|
691
|
+
Requires-Dist: pyvis
|
|
692
|
+
Dynamic: license-file
|
|
691
693
|
|
|
692
694
|
# PyNeoInstance Package
|
|
693
695
|
|
|
694
696
|
## What is it?
|
|
695
697
|
|
|
696
|
-
PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
|
|
698
|
+
PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j and performing Exploratory Data Analysis (EDA) of your graph.
|
|
699
|
+
|
|
697
700
|
It supports the following features:
|
|
698
701
|
|
|
699
|
-
-
|
|
700
|
-
-
|
|
701
|
-
-
|
|
702
|
-
-
|
|
703
|
-
-
|
|
702
|
+
- __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
|
|
703
|
+
- __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
|
|
704
|
+
- __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
|
|
705
|
+
- __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
|
|
706
|
+
- __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
|
|
707
|
+
- __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
|
|
708
|
+
- __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
|
|
709
|
+
- __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
|
|
710
|
+
- __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
|
|
711
|
+
- __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
|
|
712
|
+
- __<u>Constraints</u>__: Get information of all constraints in the graph.
|
|
713
|
+
- __<u>Indexes</u>__: Get information of all indexes in the graph.
|
|
714
|
+
- __<u>Schema Visualization</u>__: Visualize the graph schema.
|
|
715
|
+
|
|
704
716
|
|
|
705
717
|
## How to install it?
|
|
706
718
|
|
|
@@ -921,7 +933,69 @@ person_load_result = graph.execute_write_query_with_data(
|
|
|
921
933
|
workers = 5
|
|
922
934
|
)
|
|
923
935
|
```
|
|
936
|
+
## Graph EDA
|
|
937
|
+
|
|
938
|
+
### Node Label Frequency
|
|
939
|
+
The get_node_label_freq method provides the frequency and relative frequency of all nodes in the graph. This method consider all node labels as single labels, if you want the distribution considering multiple labels use the get_node_multilabel_freq method.
|
|
940
|
+
```python
|
|
941
|
+
graph.get_node_label_freq(database='mydbname')
|
|
942
|
+
```
|
|
943
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
|
|
944
|
+
|
|
945
|
+
### Relationship Type Frequency
|
|
946
|
+
The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
|
|
947
|
+
```python
|
|
948
|
+
graph.get_rela_type_freq(database='mydbname')
|
|
949
|
+
```
|
|
950
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
|
|
951
|
+
|
|
952
|
+
### Relationship Source-Target Frequency
|
|
953
|
+
The get_rela_source_target_freq method provides the frequency and relative frequency of all relationship types considering the node label of the source and targets of the relationships.
|
|
954
|
+
```python
|
|
955
|
+
graph.get_rela_type_freq(database='mydbname')
|
|
956
|
+
```
|
|
957
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
|
|
958
|
+
|
|
959
|
+
### Properties
|
|
960
|
+
The get_properties method provides information about all the nodes and relationships properties.
|
|
961
|
+
```python
|
|
962
|
+
graph.graph.get_properties(database='mydbname')
|
|
963
|
+
```
|
|
964
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
|
|
965
|
+
|
|
966
|
+
### Constraints
|
|
967
|
+
The get_constraints method provides information about all the graph constraints.
|
|
968
|
+
```python
|
|
969
|
+
graph.get_constraints(database='mydbname')
|
|
970
|
+
```
|
|
971
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
|
|
972
|
+
|
|
973
|
+
### Indexes
|
|
974
|
+
The get_indexes method provides information about all the graph indexes.
|
|
975
|
+
```python
|
|
976
|
+
graph.get_indexes(database='mydbname')
|
|
977
|
+
```
|
|
978
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
|
|
979
|
+
|
|
980
|
+
### Schema Visualization
|
|
981
|
+
The graph.get_schema_visualization provides an interactive UI with the visualization of the graph schema with the ability to filter the schema. This UI can be access via a Notebook or HTML page. This visualization displays the node labels as string close to nodes, relationship types are only display on mouse hover.
|
|
982
|
+
|
|
983
|
+
```python
|
|
984
|
+
schema = graph.get_schema_visualization(database='mydbname')
|
|
985
|
+
schema.show('schema.html')
|
|
986
|
+
```
|
|
987
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
|
|
988
|
+
|
|
989
|
+
To access the UI via HTTML page just set the parameter notebook to false in the show method and open the html file in your browser.
|
|
990
|
+
|
|
991
|
+
```python
|
|
992
|
+
schema = graph.get_schema_visualization(database='mydbname')
|
|
993
|
+
schema.show('schema.html',notebook=False)
|
|
994
|
+
```
|
|
995
|
+
<img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
|
|
924
996
|
|
|
925
997
|
## More detailed examples
|
|
926
998
|
|
|
927
|
-
For more detailed examples please
|
|
999
|
+
For more detailed examples please refer to the following articles:
|
|
1000
|
+
* Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
|
|
1001
|
+
* Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
|
|
@@ -6,8 +6,8 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyneoinstance"
|
|
9
|
-
version = "
|
|
10
|
-
description = "Handle Neo4j read and write transactions"
|
|
9
|
+
version = "2.2.0"
|
|
10
|
+
description = "Handle Neo4j read and write transactions and provide methos for performing graph EDA"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{ name = "Jose A. Alvarado-Guzman",email = "jose.alvarado-guzman@neo4j.com" }]
|
|
13
13
|
license = { file = "LICENSE" }
|
|
@@ -20,6 +20,7 @@ keywords = ["neo4j", "graph", "cypher"]
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"neo4j>=5.0.0",
|
|
22
22
|
"pandas",
|
|
23
|
-
"PyYAML"
|
|
23
|
+
"PyYAML",
|
|
24
|
+
"pyvis"
|
|
24
25
|
]
|
|
25
26
|
requires-python = ">=3.8"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Use to provide context to the test module"""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
sys.path.insert(0, os.path.abspath(
|
|
5
|
+
os.path.join(os.path.dirname(__file__), '..')))
|
|
6
|
+
from pyneoinstance import fileload
|
|
7
|
+
from pyneoinstance import util
|
|
8
|
+
from pyneoinstance import database
|
|
@@ -28,7 +28,7 @@ queries:
|
|
|
28
28
|
MATCH(p:Person {personId:row.personId}),(m:Movie {movieId:row.movieId})
|
|
29
29
|
MERGE(p)-[r:ACTED_IN]->(m)
|
|
30
30
|
ON CREATE
|
|
31
|
-
SET r.
|
|
31
|
+
SET r.characters=split(row.characters,':')
|
|
32
32
|
create_actor: |
|
|
33
33
|
MERGE(p:Person:Actor {personId:$id})
|
|
34
34
|
ON CREATE
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import os
|
|
10
10
|
import unittest
|
|
11
11
|
import pandas as pd
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas.testing as pd_testing
|
|
14
|
+
from neo4j.exceptions import ClientError
|
|
12
15
|
from .context import database
|
|
13
16
|
from .context import fileload
|
|
14
17
|
from . import test_dir
|
|
@@ -24,6 +27,12 @@ class TestNeo4jInstance(unittest.TestCase):
|
|
|
24
27
|
test_neo4j_instance()
|
|
25
28
|
Test all the methods in the Neo4jInstance class.
|
|
26
29
|
"""
|
|
30
|
+
def assertDataframeEqual(self, a, b, msg):
|
|
31
|
+
try:
|
|
32
|
+
pd_testing.assert_frame_equal(a, b)
|
|
33
|
+
except AssertionError as e:
|
|
34
|
+
raise self.failureException(msg) from e
|
|
35
|
+
|
|
27
36
|
def setUp(self):
|
|
28
37
|
"""Set authentication variables and create Neo4j driver connection."""
|
|
29
38
|
try:
|
|
@@ -37,10 +46,11 @@ class TestNeo4jInstance(unittest.TestCase):
|
|
|
37
46
|
self.yaml_file = os.path.join(
|
|
38
47
|
test_dir,'cypher_queries.yaml')
|
|
39
48
|
self.queries = fileload.load_yaml_file(self.yaml_file,required_keys)
|
|
49
|
+
self.addTypeEqualityFunc(pd.DataFrame, self.assertDataframeEqual)
|
|
40
50
|
|
|
41
51
|
def tearDown(self):
|
|
42
52
|
"""Close the neo4j driver and delete the test data."""
|
|
43
|
-
queries = ["CALL apoc.schema.assert({},{})","MATCH(n) DELETE n"]
|
|
53
|
+
queries = ["CALL apoc.schema.assert({},{})","MATCH(n) DETACH DELETE n"]
|
|
44
54
|
self.graph.execute_write_queries(queries, self.queries['database'])
|
|
45
55
|
|
|
46
56
|
def test_neo4j_instance(self):
|
|
@@ -109,5 +119,84 @@ class TestNeo4jInstance(unittest.TestCase):
|
|
|
109
119
|
'properties_set':4}
|
|
110
120
|
self.assertEqual(solution, result)
|
|
111
121
|
|
|
122
|
+
# Test get_node_labels_freq
|
|
123
|
+
print('Get node labels freq')
|
|
124
|
+
role_file = os.path.join(test_dir,'roles.csv')
|
|
125
|
+
role_df = pd.read_csv(role_file).replace(np.NaN,'')
|
|
126
|
+
result = self.graph.get_node_label_freq(
|
|
127
|
+
self.queries['database'])
|
|
128
|
+
solution = pd.DataFrame([
|
|
129
|
+
{'nodeLabel':'Person','frequency':18727,'relativeFrequency':.75},
|
|
130
|
+
{'nodeLabel':'Movie','frequency':6231,'relativeFrequency':.25},
|
|
131
|
+
{'nodeLabel':'Actor','frequency':1,'relativeFrequency':.00}
|
|
132
|
+
])
|
|
133
|
+
self.assertEqual(result,solution)
|
|
134
|
+
|
|
135
|
+
# Test get_node_multilabels_freq
|
|
136
|
+
print('Get node multilabel freq')
|
|
137
|
+
result = self.graph.get_node_multilabel_freq(
|
|
138
|
+
self.queries['database'])
|
|
139
|
+
solution = pd.DataFrame([
|
|
140
|
+
{'nodeLabels':['Person','Actor'],'frequency':1}])
|
|
141
|
+
self.assertEqual(result,solution)
|
|
142
|
+
|
|
143
|
+
# Test get_rela_labels_freq
|
|
144
|
+
print('Get relationship type freq')
|
|
145
|
+
self.graph.execute_write_query_with_data(
|
|
146
|
+
self.queries['queries']['load_role'],
|
|
147
|
+
role_df,
|
|
148
|
+
self.queries['database'])
|
|
149
|
+
result = self.graph.get_rela_type_freq(
|
|
150
|
+
self.queries['database'])
|
|
151
|
+
solution = pd.DataFrame([
|
|
152
|
+
{'relationshipType':'ACTED_IN','frequency':56914,'relativeFrequency':1.0}])
|
|
153
|
+
self.assertEqual(result,solution)
|
|
154
|
+
|
|
155
|
+
# Test get_properties
|
|
156
|
+
print('Get properties')
|
|
157
|
+
query = """
|
|
158
|
+
CALL apoc.meta.data() YIELD label,property,type,elementType
|
|
159
|
+
WHERE type<>'RELATIONSHIP'
|
|
160
|
+
RETURN elementType,label,property,type
|
|
161
|
+
ORDER BY elementType,label,property;
|
|
162
|
+
"""
|
|
163
|
+
solution = self.graph.execute_read_query(query,self.queries['database'])
|
|
164
|
+
result = self.graph.get_properties(
|
|
165
|
+
self.queries['database'])
|
|
166
|
+
self.assertEqual(result,solution)
|
|
167
|
+
|
|
168
|
+
# Test constraints
|
|
169
|
+
print('Get constraints')
|
|
170
|
+
query = """
|
|
171
|
+
SHOW CONSTRAINTS
|
|
172
|
+
"""
|
|
173
|
+
solution = self.graph.execute_read_query(query,self.queries['database'])
|
|
174
|
+
result = self.graph.get_constraints(
|
|
175
|
+
self.queries['database'])
|
|
176
|
+
self.assertEqual(result,solution)
|
|
177
|
+
|
|
178
|
+
# Test indexes
|
|
179
|
+
print('Get indexes')
|
|
180
|
+
query = """
|
|
181
|
+
SHOW INDEX
|
|
182
|
+
"""
|
|
183
|
+
solution = self.graph.execute_read_query(query,self.queries['database'])
|
|
184
|
+
result = self.graph.get_indexes(
|
|
185
|
+
self.queries['database'])
|
|
186
|
+
self.assertEqual(result,solution)
|
|
187
|
+
|
|
188
|
+
# Test indexes
|
|
189
|
+
print('Get rela details')
|
|
190
|
+
result = self.graph.get_rela_source_target_freq(
|
|
191
|
+
self.queries['database'])
|
|
192
|
+
solution = pd.DataFrame([
|
|
193
|
+
{'sourceLabel':'','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914},
|
|
194
|
+
{'sourceLabel':'','relationshipType':'ACTED_IN','targetLabel':'Movie','frequency':56914},
|
|
195
|
+
{'sourceLabel':'Person','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914}
|
|
196
|
+
]
|
|
197
|
+
)
|
|
198
|
+
#solution.index = [2,0,1]
|
|
199
|
+
self.assertEqual(result,solution)
|
|
200
|
+
|
|
112
201
|
if __name__ == '__main__':
|
|
113
202
|
unittest.main()
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
"""Module use to test the utility functions"""
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
+
import pandas as pd
|
|
5
6
|
import unittest
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from .context import util
|
|
9
|
+
from .context import fileload
|
|
8
10
|
|
|
9
|
-
class
|
|
11
|
+
class TestUtilFunctions(unittest.TestCase):
|
|
10
12
|
"""Test the util function to generate file names.
|
|
11
13
|
|
|
12
14
|
Methods
|
|
@@ -36,3 +38,25 @@ class TestGetFileName(unittest.TestCase):
|
|
|
36
38
|
url = url_scheme + '://' + url_loc + '/' + file_name
|
|
37
39
|
result = util.is_reachable_url(url)
|
|
38
40
|
self.assertTrue(result)
|
|
41
|
+
|
|
42
|
+
def test_parallel_info(self):
|
|
43
|
+
print('Parallel Info')
|
|
44
|
+
"""Test the get_parallel_function."""
|
|
45
|
+
result = util.get_parallel_info(1000)
|
|
46
|
+
solution = (1,False)
|
|
47
|
+
self.assertEqual(solution,result)
|
|
48
|
+
result = util.get_parallel_info(1000000)
|
|
49
|
+
solution = (10,True)
|
|
50
|
+
self.assertEqual(solution,result)
|
|
51
|
+
|
|
52
|
+
def test_get_colums_diff(self):
|
|
53
|
+
print('Columns Diff')
|
|
54
|
+
data_file_name = 'movies.csv'
|
|
55
|
+
cypher_file_name = 'cypher_queries.yaml'
|
|
56
|
+
file_location = os.path.abspath(os.path.dirname(__file__))
|
|
57
|
+
cypher_file = os.path.join(file_location,cypher_file_name)
|
|
58
|
+
query = fileload.load_yaml_file(cypher_file)['queries']['load_movie']
|
|
59
|
+
columns = pd.read_csv(os.path.join(file_location,data_file_name)).columns
|
|
60
|
+
result = util.get_columns_diff(query, columns)
|
|
61
|
+
solution = []
|
|
62
|
+
self.assertEqual(solution,result)
|
pyneoinstance-1.1.2/setup.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|