numbarrow 0.0.0a0__py3-none-any.whl

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.
numbarrow/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = '0.0.0a0'
File without changes
@@ -0,0 +1,108 @@
1
+ import numpy as np
2
+ import pyarrow as pa
3
+
4
+ from functools import singledispatch
5
+ from typing import Union
6
+
7
+ from numbarrow.core.is_null import is_null
8
+ from numbarrow.utils.arrow_array_utils import (
9
+ create_bitmap, create_str_array, structured_array_adapter,
10
+ structured_list_array_adapter, uniform_arrow_array_adapter
11
+ )
12
+ from numbarrow.utils.utils import arrays_viewers
13
+
14
+
15
+ def cast_64bit_date_arrow_to_numpy_array(pa_array: pa.Array, np_dtype: np.dtype):
16
+ """ Can be used to cast PyArrow arrays of date types that are represented by
17
+ 64-bit integers to numpy arrays of various date types (np.datetime64[...],
18
+ which are always represented by 64-bit integers whose meaning is determined
19
+ by the precision, such as, 's', 'ms', 'us').
20
+
21
+ Since underlying data layout of both arrays in int64, a copy is avoided,
22
+
23
+ The associated bitmap (if any) is also returned.
24
+ """
25
+ int64_array = pa_array.cast(pa.int64())
26
+ assert int64_array.buffers()[1].address == pa_array.buffers()[1].address, "got copied"
27
+ bitmap, int64_data = uniform_arrow_array_adapter(int64_array)
28
+ data = int64_data.view(np_dtype)
29
+ assert data.ctypes.data == int64_data.ctypes.data, "got copied"
30
+ return bitmap, data
31
+
32
+
33
+ @singledispatch
34
+ def arrow_array_adapter(pa_array: pa.Array):
35
+ """ Dispatcher for PyArrow array adapters of various types. """
36
+ raise NotImplementedError(f"Not implemented for {pa_array} of type {type(pa_array)} and elements {pa_array.type}")
37
+
38
+
39
+ @arrow_array_adapter.register(pa.BooleanArray)
40
+ def _(pa_array: pa.BooleanArray):
41
+ """ PyArrow stores boolean arrays bit-wise,
42
+ following the same kind of layout it uses
43
+ for bitmaps. This requires creating a copy
44
+ when casting to numpy arrays of booleans.
45
+ """
46
+ bitmap_buf, data_buf = pa_array.buffers()
47
+ data_buf_p = data_buf.address
48
+ num_of_bool_elements = len(pa_array)
49
+ num_of_bytes = num_of_bool_elements // 8
50
+ if num_of_bool_elements % 8 > 0:
51
+ num_of_bytes += 1
52
+ packed_boolean_data_viewer = arrays_viewers[np.uint8]
53
+ packed_boolean_data = packed_boolean_data_viewer(data_buf_p, num_of_bytes)
54
+ data_lst = [not is_null(i, packed_boolean_data) for i in range(num_of_bool_elements)]
55
+ data = np.array(data_lst, dtype=np.bool_)
56
+ bitmap = create_bitmap(bitmap_buf)
57
+ return bitmap, data
58
+
59
+
60
+ @arrow_array_adapter.register(pa.Date32Array)
61
+ def _(pa_array: pa.Date32Array):
62
+ """
63
+ PyArrow Date32 dates are represented by 32bit integers.
64
+ Since all numpy dates are represented by 64bit integers, this
65
+ creates a copy when it re-interprets numpy array of int32 integers
66
+ (number of days since 1970-01-01) as datetime64[D] (int64)"""
67
+ int32_array = pa_array.cast(pa.int32())
68
+ assert int32_array.buffers()[1].address == pa_array.buffers()[1].address, "got copied"
69
+ bitmap, int32_data = uniform_arrow_array_adapter(int32_array)
70
+ data = int32_data.astype(np.dtype("datetime64[D]"))
71
+ assert int32_data.ctypes.data != data.ctypes.data
72
+ return bitmap, data
73
+
74
+
75
+ @arrow_array_adapter.register(pa.Date64Array)
76
+ def _(pa_array: pa.Date64Array):
77
+ return cast_64bit_date_arrow_to_numpy_array(pa_array, np.dtype("datetime64[ms]"))
78
+
79
+
80
+ @arrow_array_adapter.register(pa.lib.DoubleArray)
81
+ @arrow_array_adapter.register(pa.Int32Array)
82
+ @arrow_array_adapter.register(pa.Int64Array)
83
+ def _(pa_array: Union[
84
+ pa.lib.DoubleArray, pa.Int32Array, pa.Int64Array
85
+ ]):
86
+ return uniform_arrow_array_adapter(pa_array)
87
+
88
+
89
+ @arrow_array_adapter.register(pa.ListArray)
90
+ def _(pa_array: pa.ListArray):
91
+ return structured_list_array_adapter(pa_array)
92
+
93
+
94
+ @arrow_array_adapter.register(pa.StructArray)
95
+ def _(pa_array: pa.StructArray):
96
+ return structured_array_adapter(pa_array)
97
+
98
+
99
+ @arrow_array_adapter.register(pa.StringArray)
100
+ def _(pa_array: pa.StringArray):
101
+ return None, create_str_array(pa_array)
102
+
103
+
104
+ @arrow_array_adapter.register(pa.TimestampArray)
105
+ def _(pa_array: pa.TimestampArray):
106
+ timestamp_type: pa.TimestampType = pa_array.type
107
+ timestamp_unit = timestamp_type.unit
108
+ return cast_64bit_date_arrow_to_numpy_array(pa_array, np.dtype(f"datetime64[{timestamp_unit}]"))
@@ -0,0 +1,3 @@
1
+ default_jit_options = {
2
+ "cache": True
3
+ }
@@ -0,0 +1,12 @@
1
+ import numpy as np
2
+ from numba import njit
3
+ from numba.core.types import boolean, int64, Array, uint8
4
+
5
+ from numbarrow.core.configurations import default_jit_options
6
+
7
+
8
+ @njit(boolean(int64, Array(uint8, 1, "C")), **default_jit_options)
9
+ def is_null(index_: int, bitmap: np.ndarray) -> bool:
10
+ byte_for_index = bitmap[index_ // 8]
11
+ bit_position_in_byte = index_ % 8
12
+ return not (byte_for_index >> bit_position_in_byte) % 2
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ import pyarrow as pa
3
+
4
+ from pyspark.broadcast import Broadcast
5
+ from typing import Callable, Dict, List, Optional
6
+
7
+ from numbarrow.core.adapters import arrow_array_adapter
8
+
9
+
10
+ def make_mapinarrow_func(
11
+ main_func: Callable,
12
+ input_columns: Optional[List[str]] = None,
13
+ broadcasts: Optional[Dict] = None
14
+ ):
15
+ """
16
+ Creates a function that can be given as an argument to `mapInArrow`
17
+
18
+ :param main_func: should have the following signature:
19
+ - `data_dict: Dict[str, np.ndarray]`, values are arrays of data of various supported types
20
+ - `bitmap_dict: Dict[str, np.ndarray]`, values are uint8 aligned arrays of bitmap data
21
+ - `broadcasts` Optional[Dict[str, Any]]
22
+ returns: Dict[str, np.ndarray] that will be used to create PyArrow `RecordBatch`
23
+ :param input_columns: optional list of column names that will be expected to be needed for in
24
+ `data_dict` for the calculation done by `main_func`. When not given, all columns in the iterated
25
+ over PySpark DataFrame will be used.
26
+ :param broadcasts: optional dictionary of broadcast values
27
+ """
28
+ broadcasts = broadcasts if broadcasts is not None else {}
29
+
30
+ def _(iterator):
31
+ for batch in iterator:
32
+ data_dict: Dict[str, np.ndarray] = {}
33
+ bitmap_dict: Dict[str, np.ndarray] = {}
34
+ input_columns_ = input_columns if input_columns is not None else batch.schema.names
35
+ for col in input_columns_:
36
+ col_pa: pa.Array = batch.column(col)
37
+ col_bitmap, col_data = arrow_array_adapter(col_pa)
38
+ col_bitmap = col_bitmap if isinstance(col_bitmap, dict) else {} if col_bitmap is None else {col: col_bitmap} # noqa: E501
39
+ col_data = col_data if isinstance(col_data, dict) else {col: col_data}
40
+ bitmap_dict = {**bitmap_dict, **col_bitmap}
41
+ data_dict = {**data_dict, **col_data}
42
+ outputs = main_func(data_dict, bitmap_dict, broadcasts)
43
+ yield pa.RecordBatch.from_pydict({col: pa.array(output) for col, output in outputs.items()})
44
+ return _
File without changes
@@ -0,0 +1,102 @@
1
+ import ctypes
2
+ import numpy as np
3
+ import pyarrow as pa
4
+
5
+ from typing import Dict, Optional, Tuple
6
+
7
+ from numbarrow.utils.utils import arrays_viewers
8
+
9
+
10
+ def create_bitmap(bitmap_buf: Optional[pa.Buffer]):
11
+ """ Create numpy array of uint8 type containing
12
+ bit-map of valid array entries """
13
+ if bitmap_buf is None:
14
+ return None
15
+ bitmap_p = bitmap_buf.address
16
+ bitmap_len = bitmap_buf.size
17
+ bitmap_viewer = arrays_viewers[np.uint8]
18
+ bitmap = bitmap_viewer(bitmap_p, bitmap_len)
19
+ return bitmap
20
+
21
+
22
+ def create_str_array(pa_str_array: pa.StringArray) -> np.ndarray:
23
+ """ Copy data from densely packed `pa.StringArray` into
24
+ padded numpy array of the character sequence type determined
25
+ by the length of the longest string. """
26
+ bitmap_buf, offsets_buf, data_buf = pa_str_array.buffers()
27
+ data_p = data_buf.address
28
+ offsets_p = offsets_buf.address
29
+ offsets_len = offsets_buf.size // np.dtype(np.int32).itemsize
30
+ offsets_array = arrays_viewers[np.int32](offsets_p, offsets_len)
31
+ diffs = np.diff(offsets_array)
32
+ str_array_len = len(diffs)
33
+ item_sz = diffs.max()
34
+ str_array = np.empty((str_array_len,), dtype=f"|U{item_sz}")
35
+ for i in range(len(offsets_array) - 1):
36
+ start = offsets_array[i]
37
+ end = offsets_array[i + 1]
38
+ s = (ctypes.c_char * int(end - start)).from_address(data_p + int(start)).value
39
+ str_array[i] = s
40
+ return str_array
41
+
42
+
43
+ def structured_array_adapter(struct_array: pa.StructArray) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
44
+ """
45
+ NumPy adapter of PyArrow `StructArray`.
46
+
47
+ Returns tuple of two dictionaries, the first dictionary maps names of
48
+ the structure fields to the contiguous bitmap arrays, the second maps
49
+ these names to the contiguous value arrays.
50
+ """
51
+ assert isinstance(struct_array, pa.StructArray)
52
+ data_type: pa.StructType = struct_array.type
53
+ assert isinstance(data_type, pa.StructType)
54
+ bitmaps = {}
55
+ datas = {}
56
+ for field_ind in range(len(data_type)):
57
+ field: pa.Field = data_type[field_ind]
58
+ field_name = field.name
59
+ pa_array = struct_array.field(field_name)
60
+ bitmap, data = uniform_arrow_array_adapter(pa_array)
61
+ bitmaps[field_name] = bitmap
62
+ datas[field_name] = data
63
+ return bitmaps, datas
64
+
65
+
66
+ def structured_list_array_adapter(list_array: pa.ListArray) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
67
+ """
68
+ NumPy adapter of PyArrow array of same-length lists of structures.
69
+
70
+ :param list_array: PyArrow array with elements being of `pa.ListType`.
71
+ Each list is in turn of the same length, and each element of the list
72
+ is of `pa.StructType`.
73
+
74
+ Returns tuple of two dictionaries, the first dictionary maps names of
75
+ the structure fields to the contiguous bitmap array, the second maps
76
+ these names to the contiguous values arrays.
77
+
78
+ Data is not copied as it is uniformly stored in a columnar format,
79
+ that is, the underlying values are stored contiguously in a
80
+ `pa.StructArray`.
81
+ """
82
+ assert isinstance(list_array, pa.ListArray)
83
+ data_values: pa.StructArray = list_array.values
84
+ return structured_array_adapter(data_values)
85
+
86
+
87
+ def uniform_arrow_array_adapter(pa_array: pa.Array) -> Tuple[Optional[np.ndarray], np.ndarray]:
88
+ """ NumPy adapter for PyArrow arrays with uniformly sized elements.
89
+ Returns views over bitmap and data contiguous memory regions as numpy arrays. """
90
+ bitmap_buf, data_buf = pa_array.buffers()
91
+ data_arrow_ty = pa_array.type
92
+ data_np_ty = data_arrow_ty.to_pandas_dtype()
93
+ data_viewer = arrays_viewers.get(data_np_ty, None)
94
+ if data_viewer is None:
95
+ raise ValueError(f"There is no {data_np_ty} in `utils.arrays_viewers`. Add it?")
96
+ data_p = data_buf.address
97
+ data_buf_byte_size = data_buf.size
98
+ data_item_byte_size = np.dtype(data_np_ty).itemsize
99
+ data_len = data_buf_byte_size // data_item_byte_size
100
+ data = data_viewer(data_p, data_len)
101
+ bitmap = create_bitmap(bitmap_buf)
102
+ return bitmap, data
@@ -0,0 +1,31 @@
1
+ import numpy as np
2
+ from numba import carray, from_dtype, int64, intp, njit
3
+ from numba.core.types import Array, voidptr
4
+ from numba.extending import intrinsic
5
+
6
+ from numbarrow.core.configurations import default_jit_options
7
+
8
+
9
+ @intrinsic
10
+ def _ptr_as_int_to_voidptr(typingctx, arg_type):
11
+ def codegen(context, builder, signature, args):
12
+ return builder.inttoptr(args[0], context.get_value_type(voidptr))
13
+ return voidptr(arg_type), codegen
14
+
15
+
16
+ def numpy_array_from_ptr_factory(dtype_):
17
+ @njit(Array(from_dtype(dtype_), 1, "C")(intp, int64), **default_jit_options)
18
+ def _(ptr_as_int: int, sz: int):
19
+ return carray(_ptr_as_int_to_voidptr(ptr_as_int), shape=(sz,), dtype=dtype_)
20
+ return _
21
+
22
+
23
+ arrays_viewers = {
24
+ np_type: numpy_array_from_ptr_factory(np_type) for np_type in [
25
+ np.bool_,
26
+ np.float64,
27
+ np.int32,
28
+ np.int64,
29
+ np.uint8
30
+ ]
31
+ }
@@ -0,0 +1,27 @@
1
+ MIT License (with Citation Clause)
2
+
3
+ Copyright (c) 2025 Mikhail Goykhman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ - The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ - If this software or any derivative work is used, modified, or distributed,
16
+ you must provide proper credit to the original author. This includes:
17
+ - Mentioning the original author in documentation, README files, or any
18
+ publication describing work based on this software.
19
+ - Including a link to the original repository, when applicable.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.1
2
+ Name: numbarrow
3
+ Version: 0.0.0a0
4
+ Author: Mikhail Goykhman
5
+ License: MIT License (with Citation Clause)
6
+
7
+ Copyright (c) 2025 Mikhail Goykhman
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ - The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ - If this software or any derivative work is used, modified, or distributed,
20
+ you must provide proper credit to the original author. This includes:
21
+ - Mentioning the original author in documentation, README files, or any
22
+ publication describing work based on this software.
23
+ - Including a link to the original repository, when applicable.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ SOFTWARE.
32
+
33
+ Keywords: pyarrow,pyspark,numba,numpy
34
+ Requires-Python: ~=3.10
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: llvmlite<0.45.0,>=0.43.0
38
+ Requires-Dist: numba<0.62.0,>=0.60.0
39
+ Requires-Dist: numpy<2.0.0,>=1.22.4
40
+ Requires-Dist: pandas==1.5.0
41
+ Requires-Dist: py4j==0.10.9.5
42
+ Requires-Dist: pyarrow<=15.0.0
43
+ Requires-Dist: pyspark~=3.3.1
44
+ Requires-Dist: python-dateutil==2.9.0.post0
45
+ Requires-Dist: pytz==2025.2
46
+ Requires-Dist: six==1.17.0
47
+
48
+ # numbarrow
49
+ Numba adapters for PyArrow and PySpark
@@ -0,0 +1,14 @@
1
+ numbarrow/__init__.py,sha256=ZOPI6V3g5yKt5gCJVpzpQrEJVh0AfpD9NTTDyJ1J2CU,24
2
+ numbarrow/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ numbarrow/core/adapters.py,sha256=nY5_upQUrNOq-M_4glSjkVp5PNc833-Xckqc8jlWW9Y,4100
4
+ numbarrow/core/configurations.py,sha256=dQe7TN0jJ4HUGYCxYBAMdyMm8dC5rhRVZ1PHRfWD1OE,44
5
+ numbarrow/core/is_null.py,sha256=LcPky2pRIKMtlu2WMteU9bMsKEzZv5Q1HxFGje45GNQ,425
6
+ numbarrow/core/mapinarrow_factory.py,sha256=JQIEnLLlNY50jxerFc6VNeWF2RWkVk52rVn_0NV64cQ,2110
7
+ numbarrow/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ numbarrow/utils/arrow_array_utils.py,sha256=I8yW4wOPlVXEA7lc4_aHXuAfZ-NYz_EnEAsAKdfdFZw,4038
9
+ numbarrow/utils/utils.py,sha256=jRPZjFAwr1iSm9TtYtO4ioF_gDJzYCZ1eb0BEkwMzWg,894
10
+ numbarrow-0.0.0a0.dist-info/LICENSE,sha256=YYgNvjH_p6-1NsdrIqGJnr1GUbZzA_8DxsP6vVfM6nY,1446
11
+ numbarrow-0.0.0a0.dist-info/METADATA,sha256=6DASatw_x9Gq5qm30ph2W9JjVF7osqiOIAxp0tR2q_s,2260
12
+ numbarrow-0.0.0a0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
13
+ numbarrow-0.0.0a0.dist-info/top_level.txt,sha256=MOoPFxa928DKw4fAl7Q9KnV7bNx8LKN3cbzHSpauZSM,10
14
+ numbarrow-0.0.0a0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ numbarrow