Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions chaco/data_range_1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# Major library imports
from math import ceil, floor, log

from numpy import compress, inf, isinf, isnan, ndarray
from numpy import compress, errstate, inf, isinf, isnan, ndarray

# Enthought library imports
from traits.api import Bool, CFloat, Enum, Float, Property, Trait, Callable
Expand Down Expand Up @@ -123,9 +123,15 @@ def mask_data(self, data):

Implements AbstractDataRange.
"""
return (data.view(ndarray) >= self._low_value) & (
data.view(ndarray) <= self._high_value
)
with errstate(invalid="ignore"):
# Running under context because the data array may contain NaNs.
# These are strictly invalid for comparison and Numpy would emit
# a warning. Since we are happy with the default behavior (NaNs
# become "False" in the mask), we silence the warning.
mask = (data.view(ndarray) >= self._low_value) & (
data.view(ndarray) <= self._high_value
)
return mask

def bound_data(self, data):
"""Returns a tuple of indices for the start and end of the first run
Expand Down
13 changes: 8 additions & 5 deletions chaco/data_range_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
Defines the DataRange2D class.
"""

from numpy import compress, inf, transpose
from numpy import compress, errstate, transpose

# Enthought library imports
from traits.api import (
Any,
Bool,
CFloat,
Instance,
Property,
Trait,
Tuple,
observe,
)
Expand Down Expand Up @@ -96,8 +94,13 @@ def mask_data(self, data):
Implements AbstractDataRange.
"""
x_points, y_points = transpose(data)
x_mask = (x_points >= self.low[0]) & (x_points <= self.high[0])
y_mask = (y_points >= self.low[1]) & (y_points <= self.high[1])
with errstate(invalid="ignore"):
# Running under context because the data array may contain NaNs.
# These are strictly invalid for comparison and Numpy would emit
# a warning. Since we are happy with the default behavior (NaNs
# become "False" in the mask), we silence the warning.
x_mask = (x_points >= self.low[0]) & (x_points <= self.high[0])
y_mask = (y_points >= self.low[1]) & (y_points <= self.high[1])
return x_mask & y_mask

def bound_data(self, data):
Expand Down
44 changes: 44 additions & 0 deletions chaco/tests/test_datarange_1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Thanks for using Enthought open source!

import unittest
import warnings

from numpy import arange, array, zeros, inf
from numpy.testing import assert_equal
Expand All @@ -17,6 +18,8 @@

from chaco.api import DataRange1D, ArrayDataSource

NAN = float("nan")


class Foo(HasTraits):
"""
Expand Down Expand Up @@ -232,6 +235,15 @@ def test_clip_data(self):
r = DataRange1D(low=2.0, high=2.5)
assert_equal(len(r.clip_data(ary)), 0)

# Test the case with nans. Additionally require that no warnings are
# emitted.
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
r = DataRange1D(low=2.0, high=10.0)
ary = array([1, 3, NAN, 9.8, 10.2, 12])
assert_equal(r.clip_data(ary), array([3.0, 9.8]))
self.assertEqual(len(w), 0)

def test_mask_data(self):
r = DataRange1D(low=2.0, high=10.0)
ary = array([1, 3, 4, 9.8, 10.2, 12])
Expand All @@ -246,6 +258,30 @@ def test_mask_data(self):
r = DataRange1D(low=2.0, high=2.5)
assert_equal(r.mask_data(ary), zeros(len(ary)))

def test_mask_data_containing_nans(self):
# Given
r = DataRange1D(low=2.0, high=10.0)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")

# When
has_nans = array([1, 3, 9.8, NAN, 12])
# Then
assert_equal(r.mask_data(has_nans), array([0, 1, 1, 0, 0], "b"))

# When
all_nans = array([NAN, NAN, NAN])
# Then
assert_equal(r.mask_data(all_nans), array([0, 0, 0], "b"))

# Then (treating nans should come with no warnings)
# NOTE: This assertion may pass because the warning has been correctly
# silenced by us (useful test), but it may also pass because the
# warning has been inactivated by the "only warn once" Python rule
# (test ineffective, false negative). Clearing the registry only for
# test purposes is not feasible: https://bugs.python.org/issue21724
self.assertEqual(len(w), 0)

def test_bound_data(self):
r = DataRange1D(low=2.9, high=6.1)
ary = arange(10)
Expand All @@ -256,6 +292,14 @@ def test_bound_data(self):
bounds = r.bound_data(ary)
assert_equal(bounds, (7, 11))

# test data with nan (expected: nan breaks a run)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
ary = array([1, 2, 3, 4, NAN, 6, 7])
bounds = r.bound_data(ary)
assert_equal(bounds, (2, 3))
self.assertEqual(len(w), 0)

def test_custom_bounds_func(self):
def custom_func(low, high, margin, tight_bounds):
assert_equal(low, 0.0)
Expand Down
38 changes: 38 additions & 0 deletions chaco/tests/test_datarange_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
# Thanks for using Enthought open source!

import unittest
import warnings

from numpy import alltrue, arange, array, ravel, transpose, zeros, inf, isinf
from numpy.testing import assert_equal, assert_

from chaco.api import DataRange2D, GridDataSource, PointDataSource

NAN = float("nan")


class DataRange2DTestCase(unittest.TestCase):
def test_empty_range(self):
Expand Down Expand Up @@ -205,6 +208,41 @@ def test_mask_data(self):
r = DataRange2D(low=[2.0, 5.0], high=[2.5, 9.0])
assert_equal(r.mask_data(ary), zeros(len(ary)))

def test_mask_data_containing_nans(self):
# Given
r = DataRange2D(low=[2.0, 3.0], high=[12.0, 13.0])
ary_1 = array(
[
[NAN, 1.0],
[NAN, 4.0],
[NAN, NAN],
[25.1, NAN],
[5.0, 6.0],
[12.5, 6.0]
]
)
expected_mask_1 = array([0, 0, 0, 0, 1, 0], "b")
ary_2 = array([[NAN, NAN], [NAN, NAN]])
expected_mask_2 = array([0, 0], "b")

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")

# When
mask_1 = r.mask_data(ary_1)
mask_2 = r.mask_data(ary_2)

# Then
assert_equal(mask_1, expected_mask_1)
assert_equal(mask_2, expected_mask_2)

# This assertion may pass because the warning has been correctly
# silenced by us (useful test), but it may also pass because the
# warning has been inactivated by the "only warn once" Python rule
# (test ineffective, false negative). Clearing the registry only for
# test purposes is not feasible: https://bugs.python.org/issue21724
self.assertEqual(len(w), 0)


def assert_close_(desired, actual):
diff_allowed = 1e-5
Expand Down