pyneoinstance 1.1.2__tar.gz → 2.0.2__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.
Files changed (34) hide show
  1. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/MANIFEST.in +1 -0
  2. {pyneoinstance-1.1.2/pyneoinstance.egg-info → pyneoinstance-2.0.2}/PKG-INFO +82 -9
  3. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/README.md +79 -7
  4. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/__init__.py +1 -1
  5. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/database/neo4jdbms.py +386 -1
  6. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2/pyneoinstance.egg-info}/PKG-INFO +82 -9
  7. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance.egg-info/SOURCES.txt +2 -0
  8. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance.egg-info/requires.txt +1 -0
  9. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance.egg-info/top_level.txt +1 -0
  10. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyproject.toml +4 -3
  11. pyneoinstance-2.0.2/setup.py +5 -0
  12. pyneoinstance-2.0.2/test/__init__.py +3 -0
  13. pyneoinstance-2.0.2/test/context.py +8 -0
  14. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/cypher_queries.yaml +1 -1
  15. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/test_neo4jdb.py +89 -1
  16. pyneoinstance-1.1.2/setup.py +0 -3
  17. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/LICENSE +0 -0
  18. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/database/__init__.py +0 -0
  19. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/database/context.py +0 -0
  20. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/fileload/__init__.py +0 -0
  21. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/fileload/context.py +0 -0
  22. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/fileload/helpers.py +0 -0
  23. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/fileload/loadyaml.py +0 -0
  24. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/util/__init__.py +0 -0
  25. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance/util/functions.py +0 -0
  26. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/pyneoinstance.egg-info/dependency_links.txt +0 -0
  27. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/setup.cfg +0 -0
  28. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/load_files.yaml +0 -0
  29. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/movies.csv +0 -0
  30. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/people.csv +0 -0
  31. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/roles.csv +0 -0
  32. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/test.py +0 -0
  33. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/test_fileload.py +0 -0
  34. {pyneoinstance-1.1.2 → pyneoinstance-2.0.2}/test/test_util_functions.py +0 -0
@@ -1,2 +1,3 @@
1
1
  include test/*.yaml
2
2
  include test/*.csv
3
+ recursive-exclude images
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyneoinstance
3
- Version: 1.1.2
4
- Summary: Handle Neo4j read and write transactions
3
+ Version: 2.0.2
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,30 @@ 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
691
692
 
692
693
  # PyNeoInstance Package
693
694
 
694
695
  ## What is it?
695
696
 
696
- PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
697
+ 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.
698
+
697
699
  It supports the following features:
698
700
 
699
- - __Read query__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
700
- - __Write query__: Submit a write Cypher query, with or without parameters, to update an existing database.
701
- - __Write queries__: Submit a list of Cypher queries to update an existing database.
702
- - __Write query with data__: Update a database based on a Pandas Data Frame and a Cypher query.
703
- - __Write queries with data__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
701
+ - __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
702
+ - __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
703
+ - __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
704
+ - __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
705
+ - __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
706
+ - __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
707
+ - __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
708
+ - __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
709
+ - __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
710
+ - __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
711
+ - __<u>Constraints</u>__: Get information of all constraints in the graph.
712
+ - __<u>Indexes</u>__: Get information of all indexes in the graph.
713
+ - __<u>Schema Visualization</u>__: Visualize the graph schema.
714
+
704
715
 
705
716
  ## How to install it?
706
717
 
@@ -921,7 +932,69 @@ person_load_result = graph.execute_write_query_with_data(
921
932
  workers = 5
922
933
  )
923
934
  ```
935
+ ## Graph EDA
936
+
937
+ ### Node Label Frequency
938
+ 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.
939
+ ```python
940
+ graph.get_node_label_freq(database='mydbname')
941
+ ```
942
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
943
+
944
+ ### Relationship Type Frequency
945
+ The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
946
+ ```python
947
+ graph.get_rela_type_freq(database='mydbname')
948
+ ```
949
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
950
+
951
+ ### Relationship Source-Target Frequency
952
+ 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.
953
+ ```python
954
+ graph.get_rela_type_freq(database='mydbname')
955
+ ```
956
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
957
+
958
+ ### Properties
959
+ The get_properties method provides information about all the nodes and relationships properties.
960
+ ```python
961
+ graph.graph.get_properties(database='mydbname')
962
+ ```
963
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
964
+
965
+ ### Constraints
966
+ The get_constraints method provides information about all the graph constraints.
967
+ ```python
968
+ graph.get_constraints(database='mydbname')
969
+ ```
970
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
971
+
972
+ ### Indexes
973
+ The get_indexes method provides information about all the graph indexes.
974
+ ```python
975
+ graph.get_indexes(database='mydbname')
976
+ ```
977
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
978
+
979
+ ### Schema Visualization
980
+ 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.
981
+
982
+ ```python
983
+ schema = graph.get_schema_visualization(database='mydbname')
984
+ schema.show('schema.html')
985
+ ```
986
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
987
+
988
+ 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.
989
+
990
+ ```python
991
+ schema = graph.get_schema_visualization(database='mydbname')
992
+ schema.show('schema.html',notebook=False)
993
+ ```
994
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
924
995
 
925
996
  ## More detailed examples
926
997
 
927
- For more detailed examples please referred to this [article](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
998
+ For more detailed examples please refer to the following articles:
999
+ * Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
1000
+ * 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
- - __Read query__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
9
- - __Write query__: Submit a write Cypher query, with or without parameters, to update an existing database.
10
- - __Write queries__: Submit a list of Cypher queries to update an existing database.
11
- - __Write query with data__: Update a database based on a Pandas Data Frame and a Cypher query.
12
- - __Write queries with data__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
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 referred to this [article](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
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)
@@ -1,4 +1,4 @@
1
1
  from .database.neo4jdbms import Neo4jInstance
2
2
  from .fileload.loadyaml import load_yaml_file
3
3
 
4
- __version__ = '1.1.2'
4
+ __version__ = '2.0.2'
@@ -13,9 +13,11 @@ 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
@@ -168,7 +170,6 @@ class Neo4jInstance:
168
170
  self.neo_info['encrypted'] = kwargs.get('encrypted') or ''
169
171
  self.__results = defaultdict(int)
170
172
 
171
-
172
173
  def execute_read_query(self, query: str,
173
174
  database: Optional[str] = None,
174
175
  parameters: Optional[Dict[str, Any]] = None
@@ -421,3 +422,387 @@ class Neo4jInstance:
421
422
  result = self.execute_write_queries_with_data(
422
423
  [query], data,database, partitions, parallel, workers, params)
423
424
  return result
425
+
426
+ def get_node_label_freq(self, database: Optional[str] = None) -> DataFrame:
427
+ """Use to obtain the graph node label frequency.
428
+
429
+ Parameters
430
+ ----------
431
+ database : str, optional
432
+ Name of the Neo4j database of which to execute the transaction.
433
+ If not provided the default database is going to be use.
434
+
435
+ Returns
436
+ -------
437
+ DataFrame
438
+ Pandas DataFrame object containing the frequency and relative
439
+ frequency of node labels.
440
+
441
+ Raises
442
+ ------
443
+ ClientError
444
+ When APOC Library is not install correctly in your Neo4j
445
+ deployment.
446
+ """
447
+ error_msg = """
448
+ APOC Library not detected.
449
+ Please install it before procceding.
450
+ """
451
+ query = """
452
+ MATCH(n)
453
+ WITH count(*) AS nodeCount
454
+ CALL db.labels() YIELD label
455
+ CALL apoc.cypher.run('MATCH (:`'+label+'`) RETURN count(*) as freq',{}) YIELD value
456
+ WITH nodeCount,label,value.freq AS freq
457
+ WITH *, 10^3 AS scaleFactor, toFloat(freq)/toFloat(nodeCount) AS relFreq
458
+ RETURN label AS nodeLabel,
459
+ freq AS frequency,
460
+ round(relFreq*scaleFactor)/scaleFactor AS relativeFrequency
461
+ ORDER BY freq DESC
462
+ """
463
+ with _get_driver(self.neo_info) as driver:
464
+ with _get_session(driver, database) as session:
465
+ try:
466
+ result = session.execute_read(
467
+ _read_transaction_function,query=query)
468
+ except ServiceUnavailable as exception:
469
+ raise ServiceUnavailable() from exception
470
+ except ClientError:
471
+ raise ClientError(error_msg)
472
+ return result
473
+
474
+ def get_node_multilabel_freq(self, database: Optional[str] = None) -> DataFrame:
475
+ """Use to obtain the graph node multi-label frequency.
476
+
477
+ Parameters
478
+ ----------
479
+ database : str, optional
480
+ Name of the Neo4j database of which to execute the transaction.
481
+ If not provided the default database is going to be use.
482
+
483
+ Returns
484
+ -------
485
+ DataFrame
486
+ Pandas DataFrame object containing the frequency and relative
487
+ frequency of node with multiple labels.
488
+
489
+ Raises
490
+ ------
491
+ ClientError
492
+ When APOC Library is not install correctly in your Neo4j
493
+ deployment.
494
+ """
495
+ error_msg = """
496
+ APOC Library not detected.
497
+ Please install it before procceding.
498
+ """
499
+ query = """
500
+ MATCH (n)
501
+ WITH labels(n) as nodeLabels
502
+ WHERE size(nodeLabels)>1
503
+ RETURN nodeLabels, count(*) as frequency
504
+ """
505
+ with _get_driver(self.neo_info) as driver:
506
+ with _get_session(driver, database) as session:
507
+ try:
508
+ result = session.execute_read(
509
+ _read_transaction_function,query=query)
510
+ except ServiceUnavailable as exception:
511
+ raise ServiceUnavailable() from exception
512
+ except ClientError:
513
+ raise ClientError(error_msg)
514
+ return result
515
+
516
+ def get_rela_type_freq(self, database: Optional[str] = None) -> DataFrame:
517
+ """Use to obtain the graph relationship type frequency.
518
+
519
+ Parameters
520
+ ----------
521
+ database : str, optional
522
+ Name of the Neo4j database of which to execute the transaction.
523
+ If not provided the default database is going to be use.
524
+
525
+ Returns
526
+ -------
527
+ DataFrame
528
+ Pandas DataFrame object containing the frequency and relative
529
+ frequency of relationship types.
530
+
531
+ Raises
532
+ ------
533
+ ClientError
534
+ When APOC Library is not install correctly in your Neo4j
535
+ deployment.
536
+ """
537
+ error_msg = """
538
+ APOC Library not detected.
539
+ Please install it before procceding.
540
+ """
541
+ query = """
542
+ MATCH()-[]->()
543
+ WITH count(*) AS relCount
544
+ CALL db.relationshipTypes() YIELD relationshipType as type
545
+ CALL apoc.cypher.run('MATCH ()-[:`'+type+'`]->() RETURN count(*) as freq',{})
546
+ YIELD value
547
+ WITH type AS relationshipType, value.freq AS freq,relCount
548
+ WITH *,3 AS presicion
549
+ WITH *, 10^presicion AS factor,toFloat(freq)/toFloat(relCount) as relFreq
550
+ RETURN relationshipType, freq AS frequency,
551
+ round(relFreq*factor)/factor AS relativeFrequency
552
+ ORDER BY freq DESC;
553
+ """
554
+ with _get_driver(self.neo_info) as driver:
555
+ with _get_session(driver, database) as session:
556
+ try:
557
+ result = session.execute_read(
558
+ _read_transaction_function,query=query)
559
+ except ServiceUnavailable as exception:
560
+ raise ServiceUnavailable() from exception
561
+ except ClientError:
562
+ raise ClientError(error_msg)
563
+ return result
564
+
565
+ def get_properties(self, database: Optional[str] = None) -> DataFrame:
566
+ """Use to obtain the node and relationship properties.
567
+
568
+ Parameters
569
+ ----------
570
+ database : str, optional
571
+ Name of the Neo4j database of which to execute the transaction.
572
+ If not provided the default database is going to be use.
573
+
574
+ Returns
575
+ -------
576
+ DataFrame
577
+ Pandas DataFrame object containing information about all nodes
578
+ and relationships properties.
579
+
580
+ Raises
581
+ ------
582
+ ClientError
583
+ When APOC Library is not install correctly in your Neo4j
584
+ deployment.
585
+ """
586
+ error_msg = """
587
+ APOC Library not detected.
588
+ Please install it before procceding.
589
+ """
590
+ query = """
591
+ CALL apoc.meta.data() YIELD label,property,type,elementType
592
+ WHERE type<>'RELATIONSHIP'
593
+ RETURN elementType,label,property,type
594
+ ORDER BY elementType,label,property;
595
+ """
596
+ with _get_driver(self.neo_info) as driver:
597
+ with _get_session(driver, database) as session:
598
+ try:
599
+ result = session.execute_read(
600
+ _read_transaction_function,query=query)
601
+ except ServiceUnavailable as exception:
602
+ raise ServiceUnavailable() from exception
603
+ except ClientError:
604
+ raise ClientError(error_msg)
605
+ return result
606
+
607
+ def get_constraints(self, database: Optional[str] = None) -> DataFrame:
608
+ """Use to obtain the constraints in the graph.
609
+
610
+ Parameters
611
+ ----------
612
+ database : str, optional
613
+ Name of the Neo4j database of which to execute the transaction.
614
+ If not provided the default database is going to be use.
615
+
616
+ Returns
617
+ -------
618
+ DataFrame
619
+ Pandas DataFrame object containing information about all the
620
+ constraints.
621
+
622
+ Raises
623
+ ------
624
+ ClientError
625
+ When APOC Library is not install correctly in your Neo4j
626
+ deployment.
627
+ """
628
+ error_msg = """
629
+ APOC Library not detected.
630
+ Please install it before procceding.
631
+ """
632
+ query = """
633
+ SHOW CONSTRAINTS
634
+ """
635
+ with _get_driver(self.neo_info) as driver:
636
+ with _get_session(driver, database) as session:
637
+ try:
638
+ result = session.execute_read(
639
+ _read_transaction_function,query=query)
640
+ except ServiceUnavailable as exception:
641
+ raise ServiceUnavailable() from exception
642
+ except ClientError:
643
+ raise ClientError(error_msg)
644
+ return result
645
+
646
+ def get_indexes(self, database: Optional[str] = None) -> DataFrame:
647
+ """Use to obtain the indexes in the graph.
648
+
649
+ Parameters
650
+ ----------
651
+ database : str, optional
652
+ Name of the Neo4j database of which to execute the transaction.
653
+ If not provided the default database is going to be use.
654
+
655
+ Returns
656
+ -------
657
+ DataFrame
658
+ Pandas DataFrame object containing information about all the
659
+ indexes.
660
+
661
+ Raises
662
+ ------
663
+ ClientError
664
+ When APOC Library is not install correctly in your Neo4j
665
+ deployment.
666
+ """
667
+ error_msg = """
668
+ APOC Library not detected.
669
+ Please install it before procceding.
670
+ """
671
+ query = """
672
+ SHOW INDEX
673
+ """
674
+ with _get_driver(self.neo_info) as driver:
675
+ with _get_session(driver, database) as session:
676
+ try:
677
+ result = session.execute_read(
678
+ _read_transaction_function,query=query)
679
+ except ServiceUnavailable as exception:
680
+ raise ServiceUnavailable() from exception
681
+ except ClientError:
682
+ raise ClientError(error_msg)
683
+ return result
684
+
685
+ def get_rela_source_target_freq(self, database: Optional[str] = None) -> DataFrame:
686
+ """Use to obtain the relationships source and target frequency.
687
+
688
+ Parameters
689
+ ----------
690
+ database : str, optional
691
+ Name of the Neo4j database of which to execute the transaction.
692
+ If not provided the default database is going to be use.
693
+
694
+ Returns
695
+ -------
696
+ DataFrame
697
+ Pandas DataFrame object containing the frequency of
698
+ relationships cource and target frequency.
699
+
700
+ Raises
701
+ ------
702
+ ClientError
703
+ When APOC Library is not install correctly in your Neo4j
704
+ deployment.
705
+ """
706
+ error_msg = """
707
+ APOC Library not detected.
708
+ Please install it before procceding.
709
+ """
710
+ query = """
711
+ CALL apoc.meta.stats() YIELD relTypes
712
+ """
713
+ with _get_driver(self.neo_info) as driver:
714
+ with _get_session(driver, database) as session:
715
+ try:
716
+ result = session.execute_read(
717
+ _read_transaction_function,query=query)
718
+ except ServiceUnavailable as exception:
719
+ raise ServiceUnavailable() from exception
720
+ except ClientError:
721
+ raise ClientError(error_msg)
722
+ stat_dict = result.iloc[0,0]
723
+ stats_dicts = []
724
+ for key,value in stat_dict.items():
725
+ nodes = re.findall(r"\(:?(\w*)\)",key)
726
+ relationship = re.findall(r"\[:?(\w*)\]",key)[0]
727
+ info = {'sourceLabel':nodes[0],'relationshipType':relationship,'targetLabel':nodes[1],
728
+ 'frequency':value}
729
+ stats_dicts.append(info)
730
+ stats_df = DataFrame(stats_dicts)
731
+ stats_df.sort_values(by=['relationshipType','sourceLabel','targetLabel'],inplace=True)
732
+ return stats_df
733
+
734
+ def get_schema_visualization(self, database: Optional[str] = None) -> Network:
735
+ """Use to visualize the graph data model (schema).
736
+
737
+ Parameters
738
+ ----------
739
+ database : str, optional
740
+ Name of the Neo4j database of which to execute the transaction.
741
+ If not provided the default database is going to be use.
742
+
743
+ Returns
744
+ -------
745
+ Network
746
+ Pyvis Network object containing the visualization information.
747
+
748
+ Raises
749
+ ------
750
+ ClientError
751
+ When APOC Library is not install correctly in your Neo4j
752
+ deployment.
753
+ """
754
+ error_msg = """
755
+ APOC Library not detected.
756
+ Please install it before procceding.
757
+ """
758
+ query = """
759
+ CALL apoc.meta.schema() YIELD value
760
+ """
761
+ with _get_driver(self.neo_info) as driver:
762
+ with _get_session(driver, database) as session:
763
+ try:
764
+ result = session.execute_read(
765
+ _read_transaction_function,query=query)
766
+ except ServiceUnavailable as exception:
767
+ raise ServiceUnavailable() from exception
768
+ except ClientError:
769
+ raise ClientError(error_msg)
770
+ schema = result.iloc[0,0]
771
+ network = Network(notebook=True,cdn_resources = "remote",directed=True,
772
+ filter_menu=True,height="800px", width="100%")
773
+ options = """
774
+ const options = {
775
+ "physics": {
776
+ "barnesHut": {
777
+ "gravitationalConstant": -13950,
778
+ "centralGravity": 6.15,
779
+ "springLength": 160,
780
+ "damping": 0.23
781
+ },
782
+ "minVelocity": 0.75
783
+ }
784
+ }
785
+ """
786
+ network.set_options(options)
787
+ nodes = []
788
+ relationships = set()
789
+ for key in schema.keys():
790
+ if schema[key]['type']=='node':
791
+ nodes.append(key)
792
+ network.add_nodes(nodes)
793
+ for node in nodes:
794
+ for rel in schema[node]['relationships'].keys():
795
+ title = rel
796
+ if schema[node]['relationships'][rel]['direction']=='in':
797
+ target = node
798
+ for label in schema[node]['relationships'][rel]['labels']:
799
+ source = label
800
+ else:
801
+ source = node
802
+ for label in schema[node]['relationships'][rel]['labels']:
803
+ target = label
804
+ relationships.add(source + "|" + title + "|" + target)
805
+ for relationship in relationships:
806
+ rela_parts = relationship.split("|")
807
+ network.add_edge(rela_parts[0],rela_parts[2],title=rela_parts[1])
808
+ return network
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyneoinstance
3
- Version: 1.1.2
4
- Summary: Handle Neo4j read and write transactions
3
+ Version: 2.0.2
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,30 @@ 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
691
692
 
692
693
  # PyNeoInstance Package
693
694
 
694
695
  ## What is it?
695
696
 
696
- PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
697
+ 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.
698
+
697
699
  It supports the following features:
698
700
 
699
- - __Read query__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
700
- - __Write query__: Submit a write Cypher query, with or without parameters, to update an existing database.
701
- - __Write queries__: Submit a list of Cypher queries to update an existing database.
702
- - __Write query with data__: Update a database based on a Pandas Data Frame and a Cypher query.
703
- - __Write queries with data__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
701
+ - __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
702
+ - __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
703
+ - __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
704
+ - __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
705
+ - __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
706
+ - __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
707
+ - __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
708
+ - __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
709
+ - __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
710
+ - __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
711
+ - __<u>Constraints</u>__: Get information of all constraints in the graph.
712
+ - __<u>Indexes</u>__: Get information of all indexes in the graph.
713
+ - __<u>Schema Visualization</u>__: Visualize the graph schema.
714
+
704
715
 
705
716
  ## How to install it?
706
717
 
@@ -921,7 +932,69 @@ person_load_result = graph.execute_write_query_with_data(
921
932
  workers = 5
922
933
  )
923
934
  ```
935
+ ## Graph EDA
936
+
937
+ ### Node Label Frequency
938
+ 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.
939
+ ```python
940
+ graph.get_node_label_freq(database='mydbname')
941
+ ```
942
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
943
+
944
+ ### Relationship Type Frequency
945
+ The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
946
+ ```python
947
+ graph.get_rela_type_freq(database='mydbname')
948
+ ```
949
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
950
+
951
+ ### Relationship Source-Target Frequency
952
+ 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.
953
+ ```python
954
+ graph.get_rela_type_freq(database='mydbname')
955
+ ```
956
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
957
+
958
+ ### Properties
959
+ The get_properties method provides information about all the nodes and relationships properties.
960
+ ```python
961
+ graph.graph.get_properties(database='mydbname')
962
+ ```
963
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
964
+
965
+ ### Constraints
966
+ The get_constraints method provides information about all the graph constraints.
967
+ ```python
968
+ graph.get_constraints(database='mydbname')
969
+ ```
970
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
971
+
972
+ ### Indexes
973
+ The get_indexes method provides information about all the graph indexes.
974
+ ```python
975
+ graph.get_indexes(database='mydbname')
976
+ ```
977
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
978
+
979
+ ### Schema Visualization
980
+ 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.
981
+
982
+ ```python
983
+ schema = graph.get_schema_visualization(database='mydbname')
984
+ schema.show('schema.html')
985
+ ```
986
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
987
+
988
+ 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.
989
+
990
+ ```python
991
+ schema = graph.get_schema_visualization(database='mydbname')
992
+ schema.show('schema.html',notebook=False)
993
+ ```
994
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
924
995
 
925
996
  ## More detailed examples
926
997
 
927
- For more detailed examples please referred to this [article](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
998
+ For more detailed examples please refer to the following articles:
999
+ * Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
1000
+ * Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
@@ -18,6 +18,8 @@ pyneoinstance/fileload/helpers.py
18
18
  pyneoinstance/fileload/loadyaml.py
19
19
  pyneoinstance/util/__init__.py
20
20
  pyneoinstance/util/functions.py
21
+ test/__init__.py
22
+ test/context.py
21
23
  test/cypher_queries.yaml
22
24
  test/load_files.yaml
23
25
  test/movies.csv
@@ -1,3 +1,4 @@
1
1
  neo4j>=5.0.0
2
2
  pandas
3
3
  PyYAML
4
+ pyvis
@@ -6,8 +6,8 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyneoinstance"
9
- version = "1.1.2"
10
- description = "Handle Neo4j read and write transactions"
9
+ version = "2.0.2"
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,5 @@
1
+ import setuptools
2
+
3
+ setuptools.setup(
4
+ packages=setuptools.find_packages(exclude=("images"))
5
+ )
@@ -0,0 +1,3 @@
1
+ import os
2
+
3
+ test_dir = os.path.abspath(os.path.dirname(__file__))
@@ -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.character=split(row.character,':')
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,8 @@
9
9
  import os
10
10
  import unittest
11
11
  import pandas as pd
12
+ import pandas.testing as pd_testing
13
+ from neo4j.exceptions import ClientError
12
14
  from .context import database
13
15
  from .context import fileload
14
16
  from . import test_dir
@@ -24,6 +26,12 @@ class TestNeo4jInstance(unittest.TestCase):
24
26
  test_neo4j_instance()
25
27
  Test all the methods in the Neo4jInstance class.
26
28
  """
29
+ def assertDataframeEqual(self, a, b, msg):
30
+ try:
31
+ pd_testing.assert_frame_equal(a, b)
32
+ except AssertionError as e:
33
+ raise self.failureException(msg) from e
34
+
27
35
  def setUp(self):
28
36
  """Set authentication variables and create Neo4j driver connection."""
29
37
  try:
@@ -37,10 +45,11 @@ class TestNeo4jInstance(unittest.TestCase):
37
45
  self.yaml_file = os.path.join(
38
46
  test_dir,'cypher_queries.yaml')
39
47
  self.queries = fileload.load_yaml_file(self.yaml_file,required_keys)
48
+ self.addTypeEqualityFunc(pd.DataFrame, self.assertDataframeEqual)
40
49
 
41
50
  def tearDown(self):
42
51
  """Close the neo4j driver and delete the test data."""
43
- queries = ["CALL apoc.schema.assert({},{})","MATCH(n) DELETE n"]
52
+ queries = ["CALL apoc.schema.assert({},{})","MATCH(n) DETACH DELETE n"]
44
53
  self.graph.execute_write_queries(queries, self.queries['database'])
45
54
 
46
55
  def test_neo4j_instance(self):
@@ -109,5 +118,84 @@ class TestNeo4jInstance(unittest.TestCase):
109
118
  'properties_set':4}
110
119
  self.assertEqual(solution, result)
111
120
 
121
+ # Test get_node_labels_freq
122
+ print('Get node labels freq')
123
+ role_file = os.path.join(test_dir,'roles.csv')
124
+ role_df = pd.read_csv(role_file)
125
+ result = self.graph.get_node_label_freq(
126
+ self.queries['database'])
127
+ solution = pd.DataFrame([
128
+ {'nodeLabel':'Person','frequency':18727,'relativeFrequency':.75},
129
+ {'nodeLabel':'Movie','frequency':6231,'relativeFrequency':.25},
130
+ {'nodeLabel':'Actor','frequency':1,'relativeFrequency':.00}
131
+ ])
132
+ self.assertEqual(result,solution)
133
+
134
+ # Test get_node_multilabels_freq
135
+ print('Get node multilabel freq')
136
+ result = self.graph.get_node_multilabel_freq(
137
+ self.queries['database'])
138
+ solution = pd.DataFrame([
139
+ {'nodeLabels':['Person','Actor'],'frequency':1}])
140
+ self.assertEqual(result,solution)
141
+
142
+ # Test get_rela_labels_freq
143
+ print('Get relationship type freq')
144
+ self.graph.execute_write_query_with_data(
145
+ self.queries['queries']['load_role'],
146
+ role_df,
147
+ self.queries['database'])
148
+ result = self.graph.get_rela_type_freq(
149
+ self.queries['database'])
150
+ solution = pd.DataFrame([
151
+ {'relationshipType':'ACTED_IN','frequency':56914,'relativeFrequency':1.0}])
152
+ self.assertEqual(result,solution)
153
+
154
+ # Test get_properties
155
+ print('Get properties')
156
+ query = """
157
+ CALL apoc.meta.data() YIELD label,property,type,elementType
158
+ WHERE type<>'RELATIONSHIP'
159
+ RETURN elementType,label,property,type
160
+ ORDER BY elementType,label,property;
161
+ """
162
+ solution = self.graph.execute_read_query(query,self.queries['database'])
163
+ result = self.graph.get_properties(
164
+ self.queries['database'])
165
+ self.assertEqual(result,solution)
166
+
167
+ # Test constraints
168
+ print('Get constraints')
169
+ query = """
170
+ SHOW CONSTRAINTS
171
+ """
172
+ solution = self.graph.execute_read_query(query,self.queries['database'])
173
+ result = self.graph.get_constraints(
174
+ self.queries['database'])
175
+ self.assertEqual(result,solution)
176
+
177
+ # Test indexes
178
+ print('Get indexes')
179
+ query = """
180
+ SHOW INDEX
181
+ """
182
+ solution = self.graph.execute_read_query(query,self.queries['database'])
183
+ result = self.graph.get_indexes(
184
+ self.queries['database'])
185
+ self.assertEqual(result,solution)
186
+
187
+ # Test indexes
188
+ print('Get rela details')
189
+ result = self.graph.get_rela_source_target_freq(
190
+ self.queries['database'])
191
+ solution = pd.DataFrame([
192
+ {'sourceLabel':'','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914},
193
+ {'sourceLabel':'','relationshipType':'ACTED_IN','targetLabel':'Movie','frequency':56914},
194
+ {'sourceLabel':'Person','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914}
195
+ ]
196
+ )
197
+ solution.index = [2,0,1]
198
+ self.assertEqual(result,solution)
199
+
112
200
  if __name__ == '__main__':
113
201
  unittest.main()
@@ -1,3 +0,0 @@
1
- from setuptools import setup
2
-
3
- setup()
File without changes
File without changes