pyneoinstance 2.2.0__tar.gz → 3.0.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-2.2.0/pyneoinstance.egg-info → pyneoinstance-3.0.0}/PKG-INFO +6 -6
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/README.md +6 -6
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/__init__.py +1 -2
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/database/context.py +1 -1
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/database/neo4jdbms.py +76 -63
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/util/__init__.py +1 -1
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/util/functions.py +23 -15
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0/pyneoinstance.egg-info}/PKG-INFO +6 -6
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/SOURCES.txt +1 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyproject.toml +1 -1
- pyneoinstance-3.0.0/test/age_data.csv +300001 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/test_neo4jdb.py +3 -3
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/test_util_functions.py +14 -10
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/LICENSE +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/MANIFEST.in +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/database/__init__.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/fileload/__init__.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/fileload/context.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/fileload/helpers.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance/fileload/loadyaml.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/dependency_links.txt +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/requires.txt +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/top_level.txt +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/setup.cfg +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/setup.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/__init__.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/context.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/cypher_queries.yaml +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/load_files.yaml +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/movies.csv +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/people.csv +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/roles.csv +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/test.py +0 -0
- {pyneoinstance-2.2.0 → pyneoinstance-3.0.0}/test/test_fileload.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyneoinstance
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
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
|
|
@@ -830,7 +830,7 @@ We can update a Neo4j database with data contained in a Pandas Data Frame using
|
|
|
830
830
|
|
|
831
831
|
- _data_: Pandas Data Frame containing data to process.
|
|
832
832
|
- _database_: Optional, database on which to execute the queries.
|
|
833
|
-
-
|
|
833
|
+
- _batchSize_: Optional, number of records per batch use to break the Data Frame in smaller chucks.
|
|
834
834
|
- _parallel_: Whether to execute the query using multiple Python process.
|
|
835
835
|
- _workers_: The number of processes to spawn to execute the queries in parallel.
|
|
836
836
|
- _parameters_: Extra arguments containing optional cypher parameters.
|
|
@@ -910,25 +910,25 @@ node_load_queries = config['loading_queries']['nodes']
|
|
|
910
910
|
person_load_result = graph.execute_write_query_with_data(node_load_queries['Person'], person_data, db_info['database'])
|
|
911
911
|
```
|
|
912
912
|
|
|
913
|
-
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the
|
|
913
|
+
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the _batchSize_ argument the number of records per data batch to break this Data Frame into smaller data batches. These partitions will be loaded in different sequential transactions unless you specified loading them in parallel by setting the _parallel_ argument to true. We will discuss this process in the next section.
|
|
914
914
|
|
|
915
915
|
```python
|
|
916
916
|
person_load_result = graph.execute_write_query_with_data(
|
|
917
917
|
node_load_queries['person'],
|
|
918
918
|
person_data,
|
|
919
919
|
db_info['database'],
|
|
920
|
-
|
|
920
|
+
batchSize = 100000
|
|
921
921
|
)
|
|
922
922
|
```
|
|
923
923
|
## Updating a database with data in a Data Frame: Parallel
|
|
924
924
|
|
|
925
|
-
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the
|
|
925
|
+
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the _batchSize_ argument and set the _parallel_ argument to true. By default, this will use all the available virtual CPUs in the machine. If you need to use less, you can specify how many to use by setting the _workers_ argument.
|
|
926
926
|
|
|
927
927
|
```python
|
|
928
928
|
person_load_result = graph.execute_write_query_with_data(
|
|
929
929
|
node_load_queries['person'],
|
|
930
930
|
person_data,
|
|
931
|
-
|
|
931
|
+
batchSize = 100000,
|
|
932
932
|
parallel = True,
|
|
933
933
|
workers = 5
|
|
934
934
|
)
|
|
@@ -137,7 +137,7 @@ We can update a Neo4j database with data contained in a Pandas Data Frame using
|
|
|
137
137
|
|
|
138
138
|
- _data_: Pandas Data Frame containing data to process.
|
|
139
139
|
- _database_: Optional, database on which to execute the queries.
|
|
140
|
-
-
|
|
140
|
+
- _batchSize_: Optional, number of records per batch use to break the Data Frame in smaller chucks.
|
|
141
141
|
- _parallel_: Whether to execute the query using multiple Python process.
|
|
142
142
|
- _workers_: The number of processes to spawn to execute the queries in parallel.
|
|
143
143
|
- _parameters_: Extra arguments containing optional cypher parameters.
|
|
@@ -217,25 +217,25 @@ node_load_queries = config['loading_queries']['nodes']
|
|
|
217
217
|
person_load_result = graph.execute_write_query_with_data(node_load_queries['Person'], person_data, db_info['database'])
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the
|
|
220
|
+
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the _batchSize_ argument the number of records per data batch to break this Data Frame into smaller data batches. These partitions will be loaded in different sequential transactions unless you specified loading them in parallel by setting the _parallel_ argument to true. We will discuss this process in the next section.
|
|
221
221
|
|
|
222
222
|
```python
|
|
223
223
|
person_load_result = graph.execute_write_query_with_data(
|
|
224
224
|
node_load_queries['person'],
|
|
225
225
|
person_data,
|
|
226
226
|
db_info['database'],
|
|
227
|
-
|
|
227
|
+
batchSize = 100000
|
|
228
228
|
)
|
|
229
229
|
```
|
|
230
230
|
## Updating a database with data in a Data Frame: Parallel
|
|
231
231
|
|
|
232
|
-
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the
|
|
232
|
+
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the _batchSize_ argument and set the _parallel_ argument to true. By default, this will use all the available virtual CPUs in the machine. If you need to use less, you can specify how many to use by setting the _workers_ argument.
|
|
233
233
|
|
|
234
234
|
```python
|
|
235
235
|
person_load_result = graph.execute_write_query_with_data(
|
|
236
236
|
node_load_queries['person'],
|
|
237
237
|
person_data,
|
|
238
|
-
|
|
238
|
+
batchSize = 100000,
|
|
239
239
|
parallel = True,
|
|
240
240
|
workers = 5
|
|
241
241
|
)
|
|
@@ -305,4 +305,4 @@ schema.show('schema.html',notebook=False)
|
|
|
305
305
|
|
|
306
306
|
For more detailed examples please refer to the following articles:
|
|
307
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)
|
|
308
|
+
* Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
|
|
@@ -3,4 +3,4 @@ import os
|
|
|
3
3
|
import sys
|
|
4
4
|
sys.path.insert(0, os.path.abspath(
|
|
5
5
|
os.path.join(os.path.dirname(__file__), '..')))
|
|
6
|
-
from pyneoinstance.util.functions import get_logger,
|
|
6
|
+
from pyneoinstance.util.functions import get_logger,get_batches,get_columns_diff
|
|
@@ -22,7 +22,7 @@ from neo4j.exceptions import ServiceUnavailable
|
|
|
22
22
|
from neo4j.exceptions import AuthError
|
|
23
23
|
from neo4j.exceptions import ClientError
|
|
24
24
|
from neo4j.exceptions import ConfigurationError
|
|
25
|
-
from .context import get_logger,
|
|
25
|
+
from .context import get_logger, get_batches, get_columns_diff
|
|
26
26
|
|
|
27
27
|
def _get_driver(neo_info : Dict[str, str]):
|
|
28
28
|
try:
|
|
@@ -60,54 +60,6 @@ def _read_transaction_function(transaction, query, **kwargs):
|
|
|
60
60
|
data = DataFrame(results.values(), columns=results.keys())
|
|
61
61
|
return data
|
|
62
62
|
|
|
63
|
-
def _execute_write(session, query,
|
|
64
|
-
rows: Optional[Dict[str, Any]] = None,
|
|
65
|
-
parameters: Optional[Dict[str, Any]] = None
|
|
66
|
-
):
|
|
67
|
-
if parameters:
|
|
68
|
-
params = parameters
|
|
69
|
-
else:
|
|
70
|
-
params = {}
|
|
71
|
-
try:
|
|
72
|
-
if rows:
|
|
73
|
-
results = session.execute_write(
|
|
74
|
-
_write_transaction_function, query,
|
|
75
|
-
rows = rows, **params).counters.__dict__
|
|
76
|
-
else:
|
|
77
|
-
results = session.execute_write(
|
|
78
|
-
_write_transaction_function, query,
|
|
79
|
-
**params).counters.__dict__
|
|
80
|
-
except ServiceUnavailable as exception:
|
|
81
|
-
raise ServiceUnavailable() from exception
|
|
82
|
-
except ClientError as exception:
|
|
83
|
-
raise ClientError() from exception
|
|
84
|
-
return results
|
|
85
|
-
|
|
86
|
-
def _execute_write_parallel(neo_info, database, query,
|
|
87
|
-
rows: Optional[Dict[str, Any]] = None,
|
|
88
|
-
parameters: Optional[Dict[str, Any]] = None
|
|
89
|
-
):
|
|
90
|
-
if parameters:
|
|
91
|
-
params = parameters
|
|
92
|
-
else:
|
|
93
|
-
params = {}
|
|
94
|
-
with _get_driver(neo_info) as driver:
|
|
95
|
-
with _get_session(driver, database) as session:
|
|
96
|
-
try:
|
|
97
|
-
if rows:
|
|
98
|
-
results = session.execute_write(
|
|
99
|
-
_write_transaction_function, query,
|
|
100
|
-
rows = rows, **params).counters.__dict__
|
|
101
|
-
else:
|
|
102
|
-
results = session.execute_write(
|
|
103
|
-
_write_transaction_function, query,
|
|
104
|
-
**params).counters.__dict__
|
|
105
|
-
except ServiceUnavailable as exception:
|
|
106
|
-
raise ServiceUnavailable() from exception
|
|
107
|
-
except ClientError as exception:
|
|
108
|
-
raise ClientError() from exception
|
|
109
|
-
return results
|
|
110
|
-
|
|
111
63
|
class Neo4jInstance:
|
|
112
64
|
"""Class use to handle Neo4j requests.
|
|
113
65
|
|
|
@@ -257,7 +209,7 @@ class Neo4jInstance:
|
|
|
257
209
|
with _get_driver(self.neo_info) as driver:
|
|
258
210
|
with _get_session(driver, database) as session:
|
|
259
211
|
for query in queries:
|
|
260
|
-
result = _execute_write(session, query, parameters=params)
|
|
212
|
+
result = self._execute_write(session, query, parameters=params)
|
|
261
213
|
for key, value in result.items():
|
|
262
214
|
if key != '_contains_updates':
|
|
263
215
|
results[key] += value
|
|
@@ -298,7 +250,7 @@ class Neo4jInstance:
|
|
|
298
250
|
def execute_write_queries_with_data(self, queries: List[str],
|
|
299
251
|
data: DataFrame,
|
|
300
252
|
database: Optional[str] = None,
|
|
301
|
-
|
|
253
|
+
batchSize: Optional[int] = 100_000,
|
|
302
254
|
parallel: Optional[bool] = False,
|
|
303
255
|
workers: Optional[int] = None,
|
|
304
256
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -314,8 +266,8 @@ class Neo4jInstance:
|
|
|
314
266
|
database : str, optional
|
|
315
267
|
Name of the Neo4j database of which to execute the transactions.
|
|
316
268
|
If not provided the default database is going to be use.
|
|
317
|
-
|
|
318
|
-
The number of
|
|
269
|
+
batchSize : int, optional
|
|
270
|
+
The number of records per batch or partitions of the data frame.
|
|
319
271
|
parallel : bool, optional
|
|
320
272
|
Wheather to execute the load in parallel.
|
|
321
273
|
workers : int, optional
|
|
@@ -339,10 +291,13 @@ class Neo4jInstance:
|
|
|
339
291
|
When the number of batch sizes to split the DataFrame on
|
|
340
292
|
is larger than the number of rows in it.
|
|
341
293
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
294
|
+
row_num = data.shape[0]
|
|
295
|
+
batchSize = batchSize if batchSize <= row_num else row_num
|
|
296
|
+
msg = f'Partitioning the data in batches of size {batchSize}'
|
|
297
|
+
self.__logger.info(msg)
|
|
298
|
+
data_chunks = get_batches(data, batchSize)
|
|
299
|
+
partitions = len(data_chunks)
|
|
300
|
+
parallel = parallel if len(data_chunks)>1 else False
|
|
346
301
|
if parameters:
|
|
347
302
|
params = parameters
|
|
348
303
|
else:
|
|
@@ -352,6 +307,8 @@ class Neo4jInstance:
|
|
|
352
307
|
workers_num = workers
|
|
353
308
|
else:
|
|
354
309
|
workers_num = mp.cpu_count()
|
|
310
|
+
msg = f'Loading {partitions:,.0f} data chunks using {workers_num} thread(s)'
|
|
311
|
+
self.__logger.info(msg)
|
|
355
312
|
for query in queries:
|
|
356
313
|
col_diff = get_columns_diff(query,data.columns)
|
|
357
314
|
msg = f'This columns are not in your data: {col_diff}'
|
|
@@ -359,11 +316,13 @@ class Neo4jInstance:
|
|
|
359
316
|
self.__logger.warning(msg)
|
|
360
317
|
with mp.Pool(workers_num) as worker_pool:
|
|
361
318
|
results = worker_pool.starmap(
|
|
362
|
-
_execute_write_parallel,
|
|
319
|
+
self._execute_write_parallel,
|
|
363
320
|
[(self.neo_info, database,query, data.iloc[d].to_dict('records'),
|
|
364
321
|
params) for d in data_chunks]
|
|
365
322
|
)
|
|
366
323
|
else:
|
|
324
|
+
msg = f'Loading {partitions:,.0f} data chunks sequentially'
|
|
325
|
+
self.__logger.info(msg)
|
|
367
326
|
results = []
|
|
368
327
|
with _get_driver(self.neo_info) as driver:
|
|
369
328
|
with _get_session(driver, database) as session:
|
|
@@ -374,7 +333,7 @@ class Neo4jInstance:
|
|
|
374
333
|
msg = f'This columns are not in your data: {col_diff}'
|
|
375
334
|
if len(col_diff)!=0:
|
|
376
335
|
self.__logger.warning(msg)
|
|
377
|
-
results.append(_execute_write(session,
|
|
336
|
+
results.append(self._execute_write(session,
|
|
378
337
|
query,
|
|
379
338
|
rows_dict['rows'],
|
|
380
339
|
params))
|
|
@@ -384,12 +343,14 @@ class Neo4jInstance:
|
|
|
384
343
|
self.__results[key] += value
|
|
385
344
|
results = dict(self.__results.copy())
|
|
386
345
|
self.__results.clear()
|
|
346
|
+
msg = f'Completed load with results {results}'
|
|
347
|
+
self.__logger.info(msg)
|
|
387
348
|
return results
|
|
388
349
|
|
|
389
350
|
def execute_write_query_with_data(self,
|
|
390
351
|
query: str, data: DataFrame,
|
|
391
352
|
database: Optional[str] = None,
|
|
392
|
-
|
|
353
|
+
batchSize: Optional[int] = 100_000,
|
|
393
354
|
parallel: Optional[bool] = False,
|
|
394
355
|
workers: Optional[int] = None,
|
|
395
356
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -405,8 +366,8 @@ class Neo4jInstance:
|
|
|
405
366
|
database : str, optional
|
|
406
367
|
Name of the Neo4j database of which to execute the transaction.
|
|
407
368
|
If not provided the default database is going to be use.
|
|
408
|
-
|
|
409
|
-
The number of
|
|
369
|
+
batchSize : int, optional
|
|
370
|
+
The number of records per batch or partitions of the data frame.
|
|
410
371
|
parallel : bool, optional
|
|
411
372
|
Wheather to execute the load in parallel.
|
|
412
373
|
workers : int, optional
|
|
@@ -435,7 +396,7 @@ class Neo4jInstance:
|
|
|
435
396
|
else:
|
|
436
397
|
params = {}
|
|
437
398
|
result = self.execute_write_queries_with_data(
|
|
438
|
-
[query], data,database,
|
|
399
|
+
[query], data,database, batchSize, parallel, workers, params)
|
|
439
400
|
return result
|
|
440
401
|
|
|
441
402
|
def get_node_label_freq(self, database: Optional[str] = None) -> DataFrame:
|
|
@@ -821,3 +782,55 @@ class Neo4jInstance:
|
|
|
821
782
|
rela_parts = relationship.split("|")
|
|
822
783
|
network.add_edge(rela_parts[0],rela_parts[2],title=rela_parts[1])
|
|
823
784
|
return network
|
|
785
|
+
|
|
786
|
+
def _execute_write_parallel(self, neo_info, database, query,
|
|
787
|
+
rows: Optional[Dict[str, Any]] = None,
|
|
788
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
789
|
+
):
|
|
790
|
+
if parameters:
|
|
791
|
+
params = parameters
|
|
792
|
+
else:
|
|
793
|
+
params = {}
|
|
794
|
+
with _get_driver(neo_info) as driver:
|
|
795
|
+
with _get_session(driver, database) as session:
|
|
796
|
+
try:
|
|
797
|
+
if rows:
|
|
798
|
+
results = session.execute_write(
|
|
799
|
+
_write_transaction_function, query,
|
|
800
|
+
rows = rows, **params).counters.__dict__
|
|
801
|
+
else:
|
|
802
|
+
results = session.execute_write(
|
|
803
|
+
_write_transaction_function, query,
|
|
804
|
+
**params).counters.__dict__
|
|
805
|
+
except ServiceUnavailable as exception:
|
|
806
|
+
raise ServiceUnavailable() from exception
|
|
807
|
+
except ClientError as exception:
|
|
808
|
+
raise ClientError() from exception
|
|
809
|
+
msg = f'Successfully loaded partition with resulting in {results}'
|
|
810
|
+
self.__logger.info(msg)
|
|
811
|
+
return results
|
|
812
|
+
|
|
813
|
+
def _execute_write(self, session, query,
|
|
814
|
+
rows: Optional[Dict[str, Any]] = None,
|
|
815
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
816
|
+
):
|
|
817
|
+
if parameters:
|
|
818
|
+
params = parameters
|
|
819
|
+
else:
|
|
820
|
+
params = {}
|
|
821
|
+
try:
|
|
822
|
+
if rows:
|
|
823
|
+
results = session.execute_write(
|
|
824
|
+
_write_transaction_function, query,
|
|
825
|
+
rows = rows, **params).counters.__dict__
|
|
826
|
+
else:
|
|
827
|
+
results = session.execute_write(
|
|
828
|
+
_write_transaction_function, query,
|
|
829
|
+
**params).counters.__dict__
|
|
830
|
+
except ServiceUnavailable as exception:
|
|
831
|
+
raise ServiceUnavailable() from exception
|
|
832
|
+
except ClientError as exception:
|
|
833
|
+
raise ClientError() from exception
|
|
834
|
+
msg = f'Successfully loaded partition with resulting in {results}'
|
|
835
|
+
self.__logger.info(msg)
|
|
836
|
+
return results
|
|
@@ -5,6 +5,8 @@ import functools
|
|
|
5
5
|
import time
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pandas import DataFrame
|
|
8
10
|
from datetime import datetime
|
|
9
11
|
from typing import Tuple, Any, Callable, List
|
|
10
12
|
from urllib.request import urlopen
|
|
@@ -96,28 +98,34 @@ def timing(function : Callable) -> Tuple[Any, float]:
|
|
|
96
98
|
return result, elapsed_time
|
|
97
99
|
return wrap
|
|
98
100
|
|
|
99
|
-
def
|
|
100
|
-
"""
|
|
101
|
-
the load in parallel based on the number of rows of a Data Frame.
|
|
101
|
+
def get_batches(data: DataFrame, batch_size) -> List[List[int]]:
|
|
102
|
+
"""Partition the data indexes in batches of the specified size.
|
|
102
103
|
|
|
103
104
|
Parameters
|
|
104
105
|
----------
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
data : DataFrame
|
|
107
|
+
Pandas DataFrame containing the data
|
|
108
|
+
batch_size : int
|
|
108
109
|
Number of records per partition
|
|
110
|
+
|
|
109
111
|
Returns
|
|
110
112
|
-------
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
List[List[int, bool]]
|
|
114
|
+
List containing the list of indexes of the DataFrame per partition.
|
|
113
115
|
"""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
records = data.shape[0]
|
|
117
|
+
indexes = data.index
|
|
118
|
+
partitions = records // batch_size
|
|
119
|
+
if partitions < 2:
|
|
120
|
+
batches = [list(indexes)]
|
|
121
|
+
else:
|
|
122
|
+
remainder = records % batch_size
|
|
123
|
+
array = np.array(
|
|
124
|
+
indexes[:records-remainder]).reshape(partitions,-1)
|
|
125
|
+
batches = array.tolist()
|
|
126
|
+
if remainder > 0:
|
|
127
|
+
batches.append(indexes[-remainder:])
|
|
128
|
+
return batches
|
|
121
129
|
|
|
122
130
|
def get_columns_diff(query: str, columns: List[str]) -> List[str]:
|
|
123
131
|
"""Get a list of the columns that a Cypher query is trying to access that are
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyneoinstance
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
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
|
|
@@ -830,7 +830,7 @@ We can update a Neo4j database with data contained in a Pandas Data Frame using
|
|
|
830
830
|
|
|
831
831
|
- _data_: Pandas Data Frame containing data to process.
|
|
832
832
|
- _database_: Optional, database on which to execute the queries.
|
|
833
|
-
-
|
|
833
|
+
- _batchSize_: Optional, number of records per batch use to break the Data Frame in smaller chucks.
|
|
834
834
|
- _parallel_: Whether to execute the query using multiple Python process.
|
|
835
835
|
- _workers_: The number of processes to spawn to execute the queries in parallel.
|
|
836
836
|
- _parameters_: Extra arguments containing optional cypher parameters.
|
|
@@ -910,25 +910,25 @@ node_load_queries = config['loading_queries']['nodes']
|
|
|
910
910
|
person_load_result = graph.execute_write_query_with_data(node_load_queries['Person'], person_data, db_info['database'])
|
|
911
911
|
```
|
|
912
912
|
|
|
913
|
-
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the
|
|
913
|
+
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the _batchSize_ argument the number of records per data batch to break this Data Frame into smaller data batches. These partitions will be loaded in different sequential transactions unless you specified loading them in parallel by setting the _parallel_ argument to true. We will discuss this process in the next section.
|
|
914
914
|
|
|
915
915
|
```python
|
|
916
916
|
person_load_result = graph.execute_write_query_with_data(
|
|
917
917
|
node_load_queries['person'],
|
|
918
918
|
person_data,
|
|
919
919
|
db_info['database'],
|
|
920
|
-
|
|
920
|
+
batchSize = 100000
|
|
921
921
|
)
|
|
922
922
|
```
|
|
923
923
|
## Updating a database with data in a Data Frame: Parallel
|
|
924
924
|
|
|
925
|
-
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the
|
|
925
|
+
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the _batchSize_ argument and set the _parallel_ argument to true. By default, this will use all the available virtual CPUs in the machine. If you need to use less, you can specify how many to use by setting the _workers_ argument.
|
|
926
926
|
|
|
927
927
|
```python
|
|
928
928
|
person_load_result = graph.execute_write_query_with_data(
|
|
929
929
|
node_load_queries['person'],
|
|
930
930
|
person_data,
|
|
931
|
-
|
|
931
|
+
batchSize = 100000,
|
|
932
932
|
parallel = True,
|
|
933
933
|
workers = 5
|
|
934
934
|
)
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyneoinstance"
|
|
9
|
-
version = "
|
|
9
|
+
version = "3.0.0"
|
|
10
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" }]
|