arraykit 1.0.8__tar.gz → 1.1.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.
- {arraykit-1.0.8/arraykit.egg-info → arraykit-1.1.0}/PKG-INFO +18 -1
- {arraykit-1.0.8 → arraykit-1.1.0}/README.rst +17 -0
- {arraykit-1.0.8 → arraykit-1.1.0/arraykit.egg-info}/PKG-INFO +18 -1
- {arraykit-1.0.8 → arraykit-1.1.0}/arraykit.egg-info/SOURCES.txt +2 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/setup.py +1 -1
- {arraykit-1.0.8 → arraykit-1.1.0}/src/__init__.py +4 -1
- {arraykit-1.0.8 → arraykit-1.1.0}/src/__init__.pyi +12 -3
- {arraykit-1.0.8 → arraykit-1.1.0}/src/_arraykit.c +24 -3
- {arraykit-1.0.8 → arraykit-1.1.0}/src/auto_map.c +14 -4
- {arraykit-1.0.8 → arraykit-1.1.0}/src/methods.c +133 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/methods.h +9 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/utilities.h +63 -9
- arraykit-1.1.0/test/test_astype_array.py +123 -0
- arraykit-1.1.0/test/test_objectable.py +68 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_util.py +1 -2
- {arraykit-1.0.8 → arraykit-1.1.0}/LICENSE.txt +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/MANIFEST.in +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/arraykit.egg-info/dependency_links.txt +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/arraykit.egg-info/requires.txt +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/arraykit.egg-info/top_level.txt +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/setup.cfg +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/array_go.c +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/array_go.h +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/array_to_tuple.c +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/array_to_tuple.h +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/auto_map.h +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/block_index.c +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/block_index.h +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/delimited_to_arrays.c +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/delimited_to_arrays.h +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/py.typed +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/tri_map.c +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/src/tri_map.h +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_array_go.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_auto_map.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_auto_map_property.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_block_index.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_delimited_to_arrays.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_delimited_to_arrays_integration.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_delimited_to_arrays_property.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_nonzero_1d.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_nonzero_1d_property.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_pyi.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_split_after_count.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_tri_map.py +0 -0
- {arraykit-1.0.8 → arraykit-1.1.0}/test/test_type_discovery.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraykit
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Array utilities for StaticFrame
|
|
5
5
|
Home-page: https://github.com/static-frame/arraykit
|
|
6
6
|
Author: Christopher Ariza, Brandt Bucher, Charles Burkland
|
|
@@ -55,6 +55,23 @@ ArrayKit requires the following:
|
|
|
55
55
|
What is New in ArrayKit
|
|
56
56
|
-------------------------
|
|
57
57
|
|
|
58
|
+
|
|
59
|
+
1.1.0
|
|
60
|
+
............
|
|
61
|
+
|
|
62
|
+
Now building free-threaded compatible wheels for Python 3.13.
|
|
63
|
+
|
|
64
|
+
Added ``is_objectable()`` and ``is_objectable_dt64()``.
|
|
65
|
+
|
|
66
|
+
Added ``astype_array()``.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
1.0.9
|
|
70
|
+
............
|
|
71
|
+
|
|
72
|
+
Updated types in ``pyi`` file.
|
|
73
|
+
|
|
74
|
+
|
|
58
75
|
1.0.8
|
|
59
76
|
............
|
|
60
77
|
|
|
@@ -35,6 +35,23 @@ ArrayKit requires the following:
|
|
|
35
35
|
What is New in ArrayKit
|
|
36
36
|
-------------------------
|
|
37
37
|
|
|
38
|
+
|
|
39
|
+
1.1.0
|
|
40
|
+
............
|
|
41
|
+
|
|
42
|
+
Now building free-threaded compatible wheels for Python 3.13.
|
|
43
|
+
|
|
44
|
+
Added ``is_objectable()`` and ``is_objectable_dt64()``.
|
|
45
|
+
|
|
46
|
+
Added ``astype_array()``.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
1.0.9
|
|
50
|
+
............
|
|
51
|
+
|
|
52
|
+
Updated types in ``pyi`` file.
|
|
53
|
+
|
|
54
|
+
|
|
38
55
|
1.0.8
|
|
39
56
|
............
|
|
40
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraykit
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Array utilities for StaticFrame
|
|
5
5
|
Home-page: https://github.com/static-frame/arraykit
|
|
6
6
|
Author: Christopher Ariza, Brandt Bucher, Charles Burkland
|
|
@@ -55,6 +55,23 @@ ArrayKit requires the following:
|
|
|
55
55
|
What is New in ArrayKit
|
|
56
56
|
-------------------------
|
|
57
57
|
|
|
58
|
+
|
|
59
|
+
1.1.0
|
|
60
|
+
............
|
|
61
|
+
|
|
62
|
+
Now building free-threaded compatible wheels for Python 3.13.
|
|
63
|
+
|
|
64
|
+
Added ``is_objectable()`` and ``is_objectable_dt64()``.
|
|
65
|
+
|
|
66
|
+
Added ``astype_array()``.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
1.0.9
|
|
70
|
+
............
|
|
71
|
+
|
|
72
|
+
Updated types in ``pyi`` file.
|
|
73
|
+
|
|
74
|
+
|
|
58
75
|
1.0.8
|
|
59
76
|
............
|
|
60
77
|
|
|
@@ -27,6 +27,7 @@ src/tri_map.c
|
|
|
27
27
|
src/tri_map.h
|
|
28
28
|
src/utilities.h
|
|
29
29
|
test/test_array_go.py
|
|
30
|
+
test/test_astype_array.py
|
|
30
31
|
test/test_auto_map.py
|
|
31
32
|
test/test_auto_map_property.py
|
|
32
33
|
test/test_block_index.py
|
|
@@ -35,6 +36,7 @@ test/test_delimited_to_arrays_integration.py
|
|
|
35
36
|
test/test_delimited_to_arrays_property.py
|
|
36
37
|
test/test_nonzero_1d.py
|
|
37
38
|
test/test_nonzero_1d_property.py
|
|
39
|
+
test/test_objectable.py
|
|
38
40
|
test/test_pyi.py
|
|
39
41
|
test/test_split_after_count.py
|
|
40
42
|
test/test_tri_map.py
|
|
@@ -31,7 +31,10 @@ from ._arraykit import slice_to_ascending_slice as slice_to_ascending_slice
|
|
|
31
31
|
from ._arraykit import array_to_tuple_array as array_to_tuple_array
|
|
32
32
|
from ._arraykit import array_to_tuple_iter as array_to_tuple_iter
|
|
33
33
|
from ._arraykit import nonzero_1d as nonzero_1d
|
|
34
|
-
|
|
34
|
+
from ._arraykit import is_objectable_dt64 as is_objectable_dt64
|
|
35
|
+
from ._arraykit import is_objectable as is_objectable
|
|
36
|
+
from ._arraykit import astype_array as astype_array
|
|
35
37
|
from ._arraykit import AutoMap as AutoMap
|
|
36
38
|
from ._arraykit import FrozenAutoMap as FrozenAutoMap
|
|
37
39
|
from ._arraykit import NonUniqueError as NonUniqueError
|
|
40
|
+
|
|
@@ -31,6 +31,12 @@ class ErrorInitTypeBlocks(RuntimeError):
|
|
|
31
31
|
def with_traceback(self, tb: Exception) -> Exception: ...
|
|
32
32
|
def __setstate__(self) -> None: ...
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
class NonUniqueError(RuntimeError):
|
|
36
|
+
def __init__(self, *args: tp.Any, **kwargs: tp.Any) -> None: ...
|
|
37
|
+
def with_traceback(self, tb: Exception) -> Exception: ...
|
|
38
|
+
def __setstate__(self) -> None: ...
|
|
39
|
+
|
|
34
40
|
class ArrayGO:
|
|
35
41
|
values: np.ndarray
|
|
36
42
|
def __init__(
|
|
@@ -110,7 +116,7 @@ class BlockIndex:
|
|
|
110
116
|
|
|
111
117
|
|
|
112
118
|
class FrozenAutoMap:
|
|
113
|
-
def __init__(self, labels: tp.Iterable[_TLabel] | np.ndarray, /,) -> None: ...
|
|
119
|
+
def __init__(self, labels: tp.Iterable[_TLabel] | np.ndarray = (), /,) -> None: ...
|
|
114
120
|
def get(self, __key: _TLabel, /,) -> int: ...
|
|
115
121
|
def keys(self) -> tp.Iterator[_TLabel]: ...
|
|
116
122
|
def items(self) -> tp.Iterator[tuple[_TLabel, int]]: ...
|
|
@@ -133,9 +139,9 @@ class FrozenAutoMap:
|
|
|
133
139
|
|
|
134
140
|
|
|
135
141
|
class AutoMap(FrozenAutoMap):
|
|
136
|
-
def __init__(self, labels: tp.Iterable[_TLabel] | np.ndarray, /,) -> None: ...
|
|
142
|
+
def __init__(self, labels: tp.Iterable[_TLabel] | np.ndarray = (), /,) -> None: ...
|
|
137
143
|
def __ior__(self) -> tp.Any: ...
|
|
138
|
-
def add(self, __key:
|
|
144
|
+
def add(self, __key: _TLabel) -> None: ...
|
|
139
145
|
def update(self, __keys: tp.Iterable[_TLabel] | np.ndarray) -> None: ...
|
|
140
146
|
|
|
141
147
|
|
|
@@ -197,6 +203,9 @@ def get_new_indexers_and_screen(indexers: np.ndarray, positions: np.ndarray) ->
|
|
|
197
203
|
def first_true_1d(__array: np.ndarray, *, forward: bool) -> int: ...
|
|
198
204
|
def first_true_2d(__array: np.ndarray, *, forward: bool, axis: int) -> np.ndarray: ...
|
|
199
205
|
def nonzero_1d(__array: np.ndarray, /) -> np.ndarray: ...
|
|
206
|
+
def is_objectable_dt64(__array: np.ndarray, /) -> bool: ...
|
|
207
|
+
def is_objectable(__array: np.ndarray, /) -> bool: ...
|
|
208
|
+
def astype_array(__array: np.ndarray, __dtype: np.dtype | None, /) -> np.ndarray: ...
|
|
200
209
|
def slice_to_ascending_slice(__slice: slice, __size: int) -> slice: ...
|
|
201
210
|
def array_to_tuple_array(__array: np.ndarray) -> np.ndarray: ...
|
|
202
211
|
def array_to_tuple_iter(__array: np.ndarray) -> tp.Iterator[tp.Tuple[tp.Any, ...]]: ...
|
|
@@ -52,6 +52,9 @@ static PyMethodDef arraykit_methods[] = {
|
|
|
52
52
|
NULL},
|
|
53
53
|
{"count_iteration", count_iteration, METH_O, NULL},
|
|
54
54
|
{"nonzero_1d", nonzero_1d, METH_O, NULL},
|
|
55
|
+
{"is_objectable_dt64", is_objectable_dt64, METH_O, NULL},
|
|
56
|
+
{"is_objectable", is_objectable, METH_O, NULL},
|
|
57
|
+
{"astype_array", astype_array, METH_VARARGS, NULL},
|
|
55
58
|
{"isna_element",
|
|
56
59
|
(PyCFunction)isna_element,
|
|
57
60
|
METH_VARARGS | METH_KEYWORDS,
|
|
@@ -72,7 +75,7 @@ static struct PyModuleDef arraykit_module = {
|
|
|
72
75
|
.m_methods = arraykit_methods,
|
|
73
76
|
};
|
|
74
77
|
|
|
75
|
-
PyObject
|
|
78
|
+
PyObject*
|
|
76
79
|
PyInit__arraykit(void)
|
|
77
80
|
{
|
|
78
81
|
import_array();
|
|
@@ -95,6 +98,7 @@ PyInit__arraykit(void)
|
|
|
95
98
|
return NULL;
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
// store a reference to the deepcopy function
|
|
98
102
|
PyObject *copy = PyImport_ImportModule("copy");
|
|
99
103
|
if (copy == NULL) {
|
|
100
104
|
return NULL;
|
|
@@ -105,6 +109,18 @@ PyInit__arraykit(void)
|
|
|
105
109
|
return NULL;
|
|
106
110
|
}
|
|
107
111
|
|
|
112
|
+
// store a year dtype object
|
|
113
|
+
PyObject* dt_year_str = PyUnicode_FromString("datetime64[Y]");
|
|
114
|
+
if (!dt_year_str) return NULL;
|
|
115
|
+
|
|
116
|
+
PyArray_Descr* dt_year = NULL;
|
|
117
|
+
if (!PyArray_DescrConverter2(dt_year_str, &dt_year)) {
|
|
118
|
+
Py_DECREF(dt_year_str);
|
|
119
|
+
return NULL;
|
|
120
|
+
}
|
|
121
|
+
Py_DECREF(dt_year_str);
|
|
122
|
+
|
|
123
|
+
|
|
108
124
|
PyObject *m = PyModule_Create(&arraykit_module);
|
|
109
125
|
if (!m ||
|
|
110
126
|
PyModule_AddStringConstant(m, "__version__", Py_STRINGIFY(AK_VERSION)) ||
|
|
@@ -128,12 +144,17 @@ PyInit__arraykit(void)
|
|
|
128
144
|
PyModule_AddObject(m, "ErrorInitTypeBlocks", ErrorInitTypeBlocks) ||
|
|
129
145
|
PyModule_AddObject(m, "AutoMap", (PyObject *)&AMType) ||
|
|
130
146
|
PyModule_AddObject(m, "FrozenAutoMap", (PyObject *)&FAMType) ||
|
|
131
|
-
PyModule_AddObject(m, "NonUniqueError", NonUniqueError)
|
|
147
|
+
PyModule_AddObject(m, "NonUniqueError", NonUniqueError) ||
|
|
148
|
+
PyModule_AddObject(m, "dt_year", (PyObject *)dt_year)
|
|
132
149
|
){
|
|
133
|
-
|
|
150
|
+
Py_XDECREF(deepcopy);
|
|
151
|
+
Py_XDECREF(dt_year);
|
|
134
152
|
Py_XDECREF(m);
|
|
135
153
|
return NULL;
|
|
136
154
|
}
|
|
155
|
+
#ifdef Py_GIL_DISABLED
|
|
156
|
+
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
157
|
+
#endif
|
|
137
158
|
return m;
|
|
138
159
|
}
|
|
139
160
|
|
|
@@ -426,8 +426,13 @@ fami_iternext(FAMIObject *self)
|
|
|
426
426
|
else {
|
|
427
427
|
PyObject* t = PyTuple_New(2);
|
|
428
428
|
if (!t) { return NULL; }
|
|
429
|
+
#if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+
|
|
430
|
+
PyObject* k = PyList_GetItemRef(self->fam->keys, index);
|
|
431
|
+
#else
|
|
429
432
|
PyObject* k = PyList_GET_ITEM(self->fam->keys, index);
|
|
430
|
-
|
|
433
|
+
Py_XINCREF(k);
|
|
434
|
+
#endif
|
|
435
|
+
if (!k) { return NULL; }
|
|
431
436
|
PyTuple_SET_ITEM(t, 0, k);
|
|
432
437
|
PyTuple_SET_ITEM(t, 1, PyLong_FromSsize_t(index));
|
|
433
438
|
return t;
|
|
@@ -438,8 +443,13 @@ fami_iternext(FAMIObject *self)
|
|
|
438
443
|
return PyArray_ToScalar(PyArray_GETPTR1(self->keys_array, index), self->keys_array);
|
|
439
444
|
}
|
|
440
445
|
else {
|
|
446
|
+
#if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+
|
|
447
|
+
PyObject* yield = PyList_GetItemRef(self->fam->keys, index);
|
|
448
|
+
#else
|
|
441
449
|
PyObject* yield = PyList_GET_ITEM(self->fam->keys, index);
|
|
442
|
-
|
|
450
|
+
Py_XINCREF(yield);
|
|
451
|
+
#endif
|
|
452
|
+
if (!yield) { return NULL; }
|
|
443
453
|
return yield;
|
|
444
454
|
}
|
|
445
455
|
}
|
|
@@ -1302,11 +1312,11 @@ lookup(FAMObject *self, PyObject *key) {
|
|
|
1302
1312
|
return self->table[table_pos].keys_pos;
|
|
1303
1313
|
}
|
|
1304
1314
|
|
|
1305
|
-
// Insert a key_pos, hash pair into the table. Assumes table already has appropriate size. When inserting a new
|
|
1315
|
+
// Insert a key_pos, hash pair into the table. Assumes table already has appropriate size. When inserting a new item, `hash` is -1, forcing a fresh hash to be computed here. Return 0 on success, -1 on error.
|
|
1306
1316
|
static int
|
|
1307
1317
|
insert_obj(
|
|
1308
1318
|
FAMObject *self,
|
|
1309
|
-
PyObject *key,
|
|
1319
|
+
PyObject *key, // NOTE: a borrowed reference
|
|
1310
1320
|
Py_ssize_t keys_pos,
|
|
1311
1321
|
Py_hash_t hash)
|
|
1312
1322
|
{
|
|
@@ -201,6 +201,139 @@ nonzero_1d(PyObject *Py_UNUSED(m), PyObject *a) {
|
|
|
201
201
|
return AK_nonzero_1d(array);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
PyObject*
|
|
205
|
+
is_objectable_dt64(PyObject *m, PyObject *a) {
|
|
206
|
+
AK_CHECK_NUMPY_ARRAY(a);
|
|
207
|
+
PyArrayObject* array = (PyArrayObject*)a;
|
|
208
|
+
|
|
209
|
+
// this returns a new reference
|
|
210
|
+
PyObject* dt_year = PyObject_GetAttrString(m, "dt_year");
|
|
211
|
+
int is_objectable = AK_is_objectable_dt64(array, dt_year);
|
|
212
|
+
Py_DECREF(dt_year);
|
|
213
|
+
|
|
214
|
+
switch (is_objectable) {
|
|
215
|
+
case -1:
|
|
216
|
+
return NULL;
|
|
217
|
+
case 0:
|
|
218
|
+
Py_RETURN_FALSE;
|
|
219
|
+
case 1:
|
|
220
|
+
Py_RETURN_TRUE;
|
|
221
|
+
}
|
|
222
|
+
return NULL;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
PyObject*
|
|
227
|
+
is_objectable(PyObject *m, PyObject *a) {
|
|
228
|
+
AK_CHECK_NUMPY_ARRAY(a);
|
|
229
|
+
PyArrayObject* array = (PyArrayObject*)a;
|
|
230
|
+
|
|
231
|
+
char kind = PyArray_DESCR(array)->kind;
|
|
232
|
+
if ((kind == 'M' || kind == 'm')) {
|
|
233
|
+
// this returns a new reference
|
|
234
|
+
PyObject* dt_year = PyObject_GetAttrString(m, "dt_year");
|
|
235
|
+
int is_objectable = AK_is_objectable_dt64(array, dt_year);
|
|
236
|
+
Py_DECREF(dt_year);
|
|
237
|
+
|
|
238
|
+
switch (is_objectable) {
|
|
239
|
+
case -1:
|
|
240
|
+
return NULL;
|
|
241
|
+
case 0:
|
|
242
|
+
Py_RETURN_FALSE;
|
|
243
|
+
case 1:
|
|
244
|
+
Py_RETURN_TRUE;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
Py_RETURN_TRUE;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Convert array to the dtype provided. NOTE: mutable arrays will be returned unless the input array is immutable and no dtype change is needed
|
|
251
|
+
PyObject*
|
|
252
|
+
astype_array(PyObject* m, PyObject* args) {
|
|
253
|
+
|
|
254
|
+
PyObject* a = NULL;
|
|
255
|
+
PyObject* dtype_spec = Py_None;
|
|
256
|
+
|
|
257
|
+
if (!PyArg_ParseTuple(args, "O!|O:astype_array",
|
|
258
|
+
&PyArray_Type, &a,
|
|
259
|
+
&dtype_spec)) {
|
|
260
|
+
return NULL;
|
|
261
|
+
}
|
|
262
|
+
PyArrayObject* array = (PyArrayObject*)a;
|
|
263
|
+
|
|
264
|
+
PyArray_Descr* dtype = NULL;
|
|
265
|
+
if (dtype_spec == Py_None) {
|
|
266
|
+
dtype = PyArray_DescrFromType(NPY_DEFAULT_TYPE);
|
|
267
|
+
} else {
|
|
268
|
+
if (!PyArray_DescrConverter(dtype_spec, &dtype)) {
|
|
269
|
+
return NULL;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (PyArray_EquivTypes(PyArray_DESCR(array), dtype)) {
|
|
274
|
+
Py_DECREF(dtype);
|
|
275
|
+
|
|
276
|
+
if (PyArray_ISWRITEABLE(array)) {
|
|
277
|
+
PyObject* result = PyArray_NewCopy(array, NPY_ANYORDER);
|
|
278
|
+
if (!result) {
|
|
279
|
+
return NULL;
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
else { // already immutable
|
|
284
|
+
Py_INCREF(a);
|
|
285
|
+
return a;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// if converting to an object
|
|
289
|
+
if (dtype->type_num == NPY_OBJECT) {
|
|
290
|
+
char kind = PyArray_DESCR(array)->kind;
|
|
291
|
+
if ((kind == 'M' || kind == 'm')) {
|
|
292
|
+
PyObject* dt_year = PyObject_GetAttrString(m, "dt_year");
|
|
293
|
+
int is_objectable = AK_is_objectable_dt64(array, dt_year);
|
|
294
|
+
Py_DECREF(dt_year);
|
|
295
|
+
|
|
296
|
+
if (!is_objectable) {
|
|
297
|
+
PyObject* result = PyArray_NewLikeArray(array, NPY_ANYORDER, dtype, 0);
|
|
298
|
+
if (!result) {
|
|
299
|
+
Py_DECREF(dtype);
|
|
300
|
+
return NULL;
|
|
301
|
+
}
|
|
302
|
+
PyObject** data = (PyObject**)PyArray_DATA((PyArrayObject*)result);
|
|
303
|
+
|
|
304
|
+
PyArrayIterObject* it = (PyArrayIterObject*)PyArray_IterNew(a);
|
|
305
|
+
if (!it) {
|
|
306
|
+
Py_DECREF(result);
|
|
307
|
+
return NULL;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
npy_intp i = 0;
|
|
311
|
+
while (it->index < it->size) {
|
|
312
|
+
PyObject* item = PyArray_ToScalar(it->dataptr, array);
|
|
313
|
+
if (!item) {
|
|
314
|
+
Py_DECREF(result);
|
|
315
|
+
Py_DECREF(it);
|
|
316
|
+
return NULL;
|
|
317
|
+
}
|
|
318
|
+
data[i++] = item;
|
|
319
|
+
PyArray_ITER_NEXT(it);
|
|
320
|
+
}
|
|
321
|
+
Py_DECREF(it);
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// all other cases: do a standard cast conversion
|
|
327
|
+
PyObject* result = PyArray_CastToType(array, dtype, 0);
|
|
328
|
+
if (!result) {
|
|
329
|
+
Py_DECREF(dtype);
|
|
330
|
+
return NULL;
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
204
337
|
static char *first_true_1d_kwarg_names[] = {
|
|
205
338
|
"array",
|
|
206
339
|
"forward",
|
|
@@ -47,6 +47,15 @@ resolve_dtype_iter(PyObject *Py_UNUSED(m), PyObject *arg);
|
|
|
47
47
|
PyObject *
|
|
48
48
|
nonzero_1d(PyObject *Py_UNUSED(m), PyObject *a);
|
|
49
49
|
|
|
50
|
+
PyObject *
|
|
51
|
+
is_objectable_dt64(PyObject *m, PyObject *a);
|
|
52
|
+
|
|
53
|
+
PyObject *
|
|
54
|
+
is_objectable(PyObject *m, PyObject *a);
|
|
55
|
+
|
|
56
|
+
PyObject *
|
|
57
|
+
astype_array(PyObject *m, PyObject *args);
|
|
58
|
+
|
|
50
59
|
PyObject *
|
|
51
60
|
first_true_1d(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs);
|
|
52
61
|
|
|
@@ -223,6 +223,69 @@ AK_slice_to_ascending_slice(PyObject* slice, Py_ssize_t size)
|
|
|
223
223
|
-step);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
|
|
227
|
+
static inline NPY_DATETIMEUNIT
|
|
228
|
+
AK_dt_unit_from_array(PyArrayObject* a) {
|
|
229
|
+
// This is based on get_datetime_metadata_from_dtype in the NumPy source, but that function is private. This does not check that the dtype is of the appropriate type.
|
|
230
|
+
PyArray_Descr* dt = PyArray_DESCR(a); // borrowed ref
|
|
231
|
+
PyArray_DatetimeMetaData* dma = &(((PyArray_DatetimeDTypeMetaData *)PyDataType_C_METADATA(dt))->meta);
|
|
232
|
+
return dma->base;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Given a dt64 array, determine if it can be cast to a object without data loss. Returns -1 on error. NOTE: if we use dt_year, must incref first
|
|
236
|
+
static inline int
|
|
237
|
+
AK_is_objectable_dt64(PyArrayObject* a, PyObject* dt_year)
|
|
238
|
+
{
|
|
239
|
+
NPY_DATETIMEUNIT unit = AK_dt_unit_from_array(a);
|
|
240
|
+
switch (unit) {
|
|
241
|
+
case NPY_FR_ERROR:
|
|
242
|
+
case NPY_FR_Y:
|
|
243
|
+
case NPY_FR_M:
|
|
244
|
+
case NPY_FR_W:
|
|
245
|
+
return false;
|
|
246
|
+
case NPY_FR_D:
|
|
247
|
+
case NPY_FR_h:
|
|
248
|
+
case NPY_FR_m:
|
|
249
|
+
case NPY_FR_s:
|
|
250
|
+
case NPY_FR_ms:
|
|
251
|
+
case NPY_FR_us:
|
|
252
|
+
break;
|
|
253
|
+
case NPY_FR_ns:
|
|
254
|
+
case NPY_FR_ps:
|
|
255
|
+
case NPY_FR_fs:
|
|
256
|
+
case NPY_FR_as:
|
|
257
|
+
case NPY_FR_GENERIC:
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
Py_INCREF(dt_year);
|
|
262
|
+
PyObject* a_year = PyArray_CastToType(a, (PyArray_Descr*)dt_year, 0);
|
|
263
|
+
if (!a_year) {
|
|
264
|
+
Py_DECREF(dt_year);
|
|
265
|
+
return -1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
npy_int64* data = (npy_int64*)PyArray_DATA((PyArrayObject*)a_year);
|
|
269
|
+
npy_intp size = PyArray_SIZE((PyArrayObject*)a_year);
|
|
270
|
+
|
|
271
|
+
for (npy_intp i = 0; i < size; ++i) {
|
|
272
|
+
npy_int64 v = data[i];
|
|
273
|
+
if (v == NPY_DATETIME_NAT) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// offset: 1-1970, 9999-1970
|
|
277
|
+
if (v < -1969 || v > 8029) {
|
|
278
|
+
Py_DECREF(a_year);
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
Py_DECREF(a_year);
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
226
289
|
// Given a Boolean, contiguous 1D array, return the index positions in an int64 array. Through experimentation it has been verified that doing full-size allocation of memory provides the best performance at all scales. Using NpyIter, or using, bit masks does not improve performance over pointer arithmetic. Prescanning for all empty is very effective. Note that NumPy benefits from first counting the nonzeros, then allocating only enough data for the expexted number of indices.
|
|
227
290
|
static inline PyObject *
|
|
228
291
|
AK_nonzero_1d(PyArrayObject* array) {
|
|
@@ -319,15 +382,6 @@ AK_nonzero_1d(PyArrayObject* array) {
|
|
|
319
382
|
return final;
|
|
320
383
|
}
|
|
321
384
|
|
|
322
|
-
static inline NPY_DATETIMEUNIT
|
|
323
|
-
AK_dt_unit_from_array(PyArrayObject* a) {
|
|
324
|
-
// This is based on get_datetime_metadata_from_dtype in the NumPy source, but that function is private. This does not check that the dtype is of the appropriate type.
|
|
325
|
-
PyArray_Descr* dt = PyArray_DESCR(a); // borrowed ref
|
|
326
|
-
PyArray_DatetimeMetaData* dma = &(((PyArray_DatetimeDTypeMetaData *)PyDataType_C_METADATA(dt))->meta);
|
|
327
|
-
// PyArray_DatetimeMetaData* dma = &(((PyArray_DatetimeDTypeMetaData *)PyArray_DESCR(a)->c_metadata)->meta);
|
|
328
|
-
return dma->base;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
385
|
static inline NPY_DATETIMEUNIT
|
|
332
386
|
AK_dt_unit_from_scalar(PyDatetimeScalarObject* dts) {
|
|
333
387
|
// Based on convert_pyobject_to_datetime and related usage in datetime.c
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from arraykit import astype_array
|
|
6
|
+
|
|
7
|
+
class TestUnit(unittest.TestCase):
|
|
8
|
+
|
|
9
|
+
def test_astype_array_a1(self) -> None:
|
|
10
|
+
a1 = np.array([10, 20, 30], dtype=np.int64)
|
|
11
|
+
a1.flags.writeable = False
|
|
12
|
+
|
|
13
|
+
a2 = astype_array(a1, np.int64)
|
|
14
|
+
self.assertEqual(id(a1), id(a2))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_astype_array_a2(self) -> None:
|
|
18
|
+
a1 = np.array([10, 20, 30], dtype=np.int64)
|
|
19
|
+
a1.flags.writeable = False
|
|
20
|
+
|
|
21
|
+
a2 = astype_array(a1, np.float64)
|
|
22
|
+
self.assertNotEqual(id(a1), id(a2))
|
|
23
|
+
self.assertEqual(a2.dtype, np.dtype(np.float64))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_astype_array_a3(self) -> None:
|
|
27
|
+
a1 = np.array([False, True, False])
|
|
28
|
+
|
|
29
|
+
a2 = astype_array(a1, np.int8)
|
|
30
|
+
self.assertEqual(a2.dtype, np.dtype(np.int8))
|
|
31
|
+
self.assertTrue(a2.flags.writeable)
|
|
32
|
+
|
|
33
|
+
def test_astype_array_b1(self) -> None:
|
|
34
|
+
a1 = np.array(['2021', '2024'], dtype=np.datetime64)
|
|
35
|
+
|
|
36
|
+
a2 = astype_array(a1, np.object_)
|
|
37
|
+
self.assertEqual(a2.dtype, np.dtype(np.object_))
|
|
38
|
+
self.assertTrue(a2.flags.writeable)
|
|
39
|
+
self.assertEqual(list(a2), [np.datetime64('2021'), np.datetime64('2024')])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_astype_array_b2(self) -> None:
|
|
43
|
+
a1 = np.array(['2021', '1642'], dtype=np.datetime64)
|
|
44
|
+
|
|
45
|
+
a2 = astype_array(a1, np.object_)
|
|
46
|
+
self.assertEqual(a2.dtype, np.dtype(np.object_))
|
|
47
|
+
self.assertTrue(a2.flags.writeable)
|
|
48
|
+
self.assertEqual(list(a2), [np.datetime64('2021'), np.datetime64('1642')])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_astype_array_b3(self) -> None:
|
|
52
|
+
a1 = np.array(['2021', '2024', '1984', '1642'], dtype=np.datetime64).reshape((2, 2))
|
|
53
|
+
|
|
54
|
+
a2 = astype_array(a1, np.object_)
|
|
55
|
+
self.assertEqual(a2.dtype, np.dtype(np.object_))
|
|
56
|
+
self.assertTrue(a2.flags.writeable)
|
|
57
|
+
self.assertEqual(
|
|
58
|
+
list(list(a) for a in a2),
|
|
59
|
+
[[np.datetime64('2021'), np.datetime64('2024')], [np.datetime64('1984'), np.datetime64('1642')]])
|
|
60
|
+
|
|
61
|
+
def test_astype_array_b4(self) -> None:
|
|
62
|
+
a1 = np.array(['2021', '2024', '1532', '1984', '1642', '899'], dtype=np.datetime64).reshape((2, 3))
|
|
63
|
+
|
|
64
|
+
a2 = astype_array(a1, np.object_)
|
|
65
|
+
self.assertEqual(a2.dtype, np.dtype(np.object_))
|
|
66
|
+
self.assertEqual(a2.shape, (2, 3))
|
|
67
|
+
self.assertTrue(a2.flags.writeable)
|
|
68
|
+
self.assertEqual(
|
|
69
|
+
list(list(a) for a in a2),
|
|
70
|
+
[[np.datetime64('2021'), np.datetime64('2024'), np.datetime64('1532')],
|
|
71
|
+
[np.datetime64('1984'), np.datetime64('1642'), np.datetime64('899')]])
|
|
72
|
+
|
|
73
|
+
def test_astype_array_c(self) -> None:
|
|
74
|
+
with self.assertRaises(TypeError):
|
|
75
|
+
_ = astype_array([3, 4, 5], np.int64)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_astype_array_d1(self) -> None:
|
|
79
|
+
a1 = np.array([10, 20, 30], dtype=np.int64)
|
|
80
|
+
a2 = astype_array(a1)
|
|
81
|
+
|
|
82
|
+
self.assertEqual(a2.dtype, np.dtype(np.float64))
|
|
83
|
+
self.assertEqual(a2.shape, (3,))
|
|
84
|
+
self.assertTrue(a2.flags.writeable)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_astype_array_d2(self) -> None:
|
|
88
|
+
a1 = np.array([10, 20, 30], dtype=np.int64)
|
|
89
|
+
a2 = astype_array(a1, None)
|
|
90
|
+
|
|
91
|
+
self.assertEqual(a2.dtype, np.dtype(np.float64))
|
|
92
|
+
self.assertEqual(a2.shape, (3,))
|
|
93
|
+
self.assertTrue(a2.flags.writeable)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_astype_array_d3(self) -> None:
|
|
98
|
+
a1 = np.array([10, 20, 30], dtype=np.int64)
|
|
99
|
+
a2 = astype_array(a1, np.int64)
|
|
100
|
+
|
|
101
|
+
self.assertEqual(a2.dtype, np.dtype(np.int64))
|
|
102
|
+
self.assertEqual(a2.shape, (3,))
|
|
103
|
+
self.assertTrue(a2.flags.writeable)
|
|
104
|
+
|
|
105
|
+
self.assertNotEqual(id(a1), id(a2))
|
|
106
|
+
|
|
107
|
+
def test_astype_array_e(self) -> None:
|
|
108
|
+
a1 = np.array(['2021', '2024', '1997', '1984', '2000', '1999'], dtype='datetime64[ns]').reshape((2, 3))
|
|
109
|
+
|
|
110
|
+
a2 = astype_array(a1, np.object_)
|
|
111
|
+
self.assertEqual(a2.dtype, np.dtype(np.object_))
|
|
112
|
+
self.assertEqual(a2.shape, (2, 3))
|
|
113
|
+
self.assertTrue(a2.flags.writeable)
|
|
114
|
+
self.assertEqual(
|
|
115
|
+
list(list(a) for a in a2),
|
|
116
|
+
[[np.datetime64('2021-01-01T00:00:00.000000000'),
|
|
117
|
+
np.datetime64('2024-01-01T00:00:00.000000000'),
|
|
118
|
+
np.datetime64('1997-01-01T00:00:00.000000000')],
|
|
119
|
+
[np.datetime64('1984-01-01T00:00:00.000000000'),
|
|
120
|
+
np.datetime64('2000-01-01T00:00:00.000000000'),
|
|
121
|
+
np.datetime64('1999-01-01T00:00:00.000000000')]]
|
|
122
|
+
)
|
|
123
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from arraykit import is_objectable_dt64
|
|
6
|
+
from arraykit import is_objectable
|
|
7
|
+
|
|
8
|
+
class TestUnit(unittest.TestCase):
|
|
9
|
+
|
|
10
|
+
def test_is_objectable_a1(self) -> None:
|
|
11
|
+
a1 = np.array(['2022-01-04', '1954-04-12'], dtype=np.datetime64)
|
|
12
|
+
self.assertTrue(is_objectable(a1))
|
|
13
|
+
|
|
14
|
+
def test_is_objectable_a2(self) -> None:
|
|
15
|
+
a1 = np.array(['10000-01-04', '1954-04-12'], dtype=np.datetime64)
|
|
16
|
+
self.assertFalse(is_objectable(a1))
|
|
17
|
+
|
|
18
|
+
def test_is_objectable_b(self) -> None:
|
|
19
|
+
a1 = np.array([10, 20])
|
|
20
|
+
self.assertTrue(is_objectable(a1))
|
|
21
|
+
|
|
22
|
+
def test_is_objectable_c(self) -> None:
|
|
23
|
+
a1 = np.array([True, False])
|
|
24
|
+
self.assertTrue(is_objectable(a1))
|
|
25
|
+
|
|
26
|
+
def test_is_objectable_d(self) -> None:
|
|
27
|
+
a1 = np.array(['b', 'ccc'])
|
|
28
|
+
self.assertTrue(is_objectable(a1))
|
|
29
|
+
|
|
30
|
+
def test_is_objectable_e(self) -> None:
|
|
31
|
+
a1 = np.array(['b', None, False], dtype=object)
|
|
32
|
+
self.assertTrue(is_objectable(a1))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
#---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
def test_is_objectable_dt64_a1(self) -> None:
|
|
38
|
+
a1 = np.array(['2022-01-04', '1954-04-12'], dtype=np.datetime64)
|
|
39
|
+
self.assertTrue(is_objectable_dt64(a1))
|
|
40
|
+
|
|
41
|
+
def test_is_objectable_dt64_a2(self) -> None:
|
|
42
|
+
a1 = np.array(['2022-01-04', '', '1954-04-12'], dtype=np.datetime64)
|
|
43
|
+
self.assertTrue(is_objectable_dt64(a1))
|
|
44
|
+
|
|
45
|
+
def test_is_objectable_dt64_a3(self) -> None:
|
|
46
|
+
a1 = np.array(['2022-01-04', '1954-04-12', '', ''], dtype=np.datetime64)
|
|
47
|
+
self.assertTrue(is_objectable_dt64(a1))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_is_objectable_dt64_b(self) -> None:
|
|
51
|
+
# years are nevery objectable
|
|
52
|
+
a1 = np.array(['2022', '2023'], dtype=np.datetime64)
|
|
53
|
+
self.assertFalse(is_objectable_dt64(a1))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_is_objectable_dt64_c(self) -> None:
|
|
57
|
+
a1 = np.array(['-120-01-01', '2023-04-05'], dtype='datetime64[m]')
|
|
58
|
+
self.assertFalse(is_objectable_dt64(a1))
|
|
59
|
+
|
|
60
|
+
def test_is_objectable_dt64_d(self) -> None:
|
|
61
|
+
a1 = np.array(['2024-01-01', '2023-04-05', '10000-01-01'], dtype='datetime64[s]')
|
|
62
|
+
self.assertFalse(is_objectable_dt64(a1))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_is_objectable_dt64_e(self) -> None:
|
|
66
|
+
a1 = np.array(['2024-01-01', '2023-04-05'], dtype='datetime64[ns]')
|
|
67
|
+
self.assertFalse(is_objectable_dt64(a1))
|
|
68
|
+
|
|
@@ -17,6 +17,7 @@ from arraykit import mloc
|
|
|
17
17
|
from arraykit import immutable_filter
|
|
18
18
|
from arraykit import array_deepcopy
|
|
19
19
|
from arraykit import isna_element
|
|
20
|
+
from arraykit import is_objectable_dt64
|
|
20
21
|
from arraykit import dtype_from_element
|
|
21
22
|
from arraykit import count_iteration
|
|
22
23
|
from arraykit import first_true_1d
|
|
@@ -953,7 +954,5 @@ class TestUnit(unittest.TestCase):
|
|
|
953
954
|
)
|
|
954
955
|
|
|
955
956
|
|
|
956
|
-
|
|
957
|
-
|
|
958
957
|
if __name__ == '__main__':
|
|
959
958
|
unittest.main()
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|