mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-10 05:53:07 +08:00
BUG: Tests/bugfixes for LabelArray slicing.
- Fixes a bug where __setitem__ was not called when setting with a slice on Python 2 (__setslice__ was called instead), which caused strange behavior when setting an empty string. This is fixed by overriding __setslice__ and forwarding to __setitem__. - Fixes a bug where __getitem__ returned an instance of np.void when returning a scalar. We now correctly return an entry from our categoricals.
This commit is contained in:
@@ -222,6 +222,8 @@ class LabelArrayTestCase(ZiplineTestCase):
|
||||
def test_reject_ufuncs(self):
|
||||
"""
|
||||
The internal values of a LabelArray should be opaque to numpy ufuncs.
|
||||
|
||||
Test that all unfuncs fail.
|
||||
"""
|
||||
def assert_ufunc_failure(exc):
|
||||
self.assertEqual(str(exc), 'Not implemented for this type')
|
||||
@@ -245,3 +247,76 @@ class LabelArrayTestCase(ZiplineTestCase):
|
||||
assert_ufunc_failure(e)
|
||||
else:
|
||||
self.assertIs(ret, NotImplemented)
|
||||
|
||||
@parameter_space(
|
||||
__fail_fast=True,
|
||||
val=['', 'a', 'not in the array', None],
|
||||
missing_value=['', 'a', 'not in the array', None],
|
||||
)
|
||||
def test_setitem_scalar(self, val, missing_value):
|
||||
arr = LabelArray(self.strs, missing_value=missing_value)
|
||||
|
||||
if not arr.has_label(val):
|
||||
self.assertTrue(
|
||||
(val == 'not in the array')
|
||||
or (val is None and missing_value is not None)
|
||||
)
|
||||
for slicer in [(0, 0), (0, 1), 1]:
|
||||
with self.assertRaises(ValueError):
|
||||
arr[slicer] = val
|
||||
return
|
||||
|
||||
arr[0, 0] = val
|
||||
self.assertEqual(arr[0, 0], val)
|
||||
|
||||
arr[0, 1] = val
|
||||
self.assertEqual(arr[0, 1], val)
|
||||
|
||||
arr[1] = val
|
||||
if val == missing_value:
|
||||
self.assertTrue(arr.is_missing()[1].all())
|
||||
else:
|
||||
self.assertTrue((arr[1] == val).all())
|
||||
self.assertTrue((arr[1].as_string_array() == val).all())
|
||||
|
||||
arr[:, -1] = val
|
||||
if val == missing_value:
|
||||
self.assertTrue(arr.is_missing()[:, -1].all())
|
||||
else:
|
||||
self.assertTrue((arr[:, -1] == val).all())
|
||||
self.assertTrue((arr[:, -1].as_string_array() == val).all())
|
||||
|
||||
arr[:] = val
|
||||
if val == missing_value:
|
||||
self.assertTrue(arr.is_missing().all())
|
||||
else:
|
||||
self.assertFalse(arr.is_missing().any())
|
||||
self.assertTrue((arr == val).all())
|
||||
|
||||
def test_setitem_array(self):
|
||||
arr = LabelArray(self.strs, missing_value=None)
|
||||
orig_arr = arr.copy()
|
||||
|
||||
# Write a row.
|
||||
self.assertFalse(
|
||||
(arr[0] == arr[1]).all(),
|
||||
"This test doesn't test anything because rows 0"
|
||||
" and 1 are already equal!"
|
||||
)
|
||||
arr[0] = arr[1]
|
||||
for i in range(arr.shape[1]):
|
||||
self.assertEqual(arr[0, i], arr[1, i])
|
||||
|
||||
# Write a column.
|
||||
self.assertFalse(
|
||||
(arr[:, 0] == arr[:, 1]).all(),
|
||||
"This test doesn't test anything because columns 0"
|
||||
" and 1 are already equal!"
|
||||
)
|
||||
arr[:, 0] = arr[:, 1]
|
||||
for i in range(arr.shape[0]):
|
||||
self.assertEqual(arr[i, 0], arr[i, 1])
|
||||
|
||||
# Write the whole array.
|
||||
arr[:] = orig_arr
|
||||
check_arrays(arr, orig_arr)
|
||||
|
||||
@@ -189,10 +189,7 @@ class LabelArray(ndarray):
|
||||
"""
|
||||
View codes as a LabelArray and set LabelArray metadata on the result.
|
||||
"""
|
||||
ret = codes.view(
|
||||
type=cls,
|
||||
dtype=np.void(codes.dtype.itemsize),
|
||||
)
|
||||
ret = codes.view(type=cls, dtype=np.void)
|
||||
ret._categories = categories
|
||||
ret._reverse_categories = reverse_categories
|
||||
ret._missing_value = missing_value
|
||||
@@ -213,6 +210,9 @@ class LabelArray(ndarray):
|
||||
# This is a property because it should be immutable.
|
||||
return self._missing_value
|
||||
|
||||
def has_label(self, value):
|
||||
return value in self.reverse_categories
|
||||
|
||||
def __array_finalize__(self, obj):
|
||||
"""
|
||||
Called by Numpy after array construction.
|
||||
@@ -333,6 +333,25 @@ class LabelArray(ndarray):
|
||||
),
|
||||
)
|
||||
|
||||
def __setslice__(self, i, j, sequence):
|
||||
"""
|
||||
This method was deprecated in Python 2.0. It predates slice objects,
|
||||
but Python 2.7.11 still uses it if you implement it, which ndarray
|
||||
does. In newer Pythons, __setitem__ is always called, but we need to
|
||||
manuallly forward in py2.
|
||||
"""
|
||||
self.__setitem__(slice(i, j), sequence)
|
||||
|
||||
def __getitem__(self, indexer):
|
||||
result = super(LabelArray, self).__getitem__(indexer)
|
||||
if result.ndim:
|
||||
# Result is still a LabelArray, so we can just return it.
|
||||
return result
|
||||
|
||||
# Result is a scalar value, which will be an instance of np.void.
|
||||
# Map it back to one of our category entries.
|
||||
return self.categories[result.view(int)]
|
||||
|
||||
def is_missing(self):
|
||||
"""
|
||||
Like isnan, but checks for locations where we store missing values.
|
||||
|
||||
Reference in New Issue
Block a user