Skip to content

Commit 893cb26

Browse files
committed
Fix graph_objects ignoring the mask of numpy masked arrays
1 parent 84498cc commit 893cb26

3 files changed

Lines changed: 43 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2424
- Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution!
2525
- Fix `plotly.io.write_images` ignoring `layout.width`/`layout.height` (and the template's) and exporting every figure at the default 700x500 size; it now uses the same width/height priority as `write_image` and `to_image` [[#5784](https://github.com/plotly/plotly.py/pull/5784)], with thanks to @breken-ai for the contribution!
2626
- Fix color utilities mutating the list of colors passed to them [[#5836](https://github.com/plotly/plotly.py/pull/5836)], with thanks to @Enesuygurs for the contribution!
27+
- Fix `graph_objects` ignoring the mask of numpy masked arrays passed to data array properties such as `x` and `y` [[#XXXX](https://github.com/plotly/plotly.py/pull/XXXX)], with thanks to @LiShuochen for the contribution!
2728

2829

2930
## [7.1.0] - 2026-09-15

‎_plotly_utils/basevalidators.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,17 @@ def validate_coerce(self, v):
417417
if is_none_or_typed_array_spec(v):
418418
pass
419419
elif is_homogeneous_array(v):
420+
ma = get_module("numpy.ma", should_load=False)
421+
if ma and ma.isMaskedArray(v) and v.dtype.kind in "biufU" and v.mask.any():
422+
np = get_module("numpy")
423+
# Replace masked values with NaN (numeric arrays) or None (boolean
424+
# and string arrays) so that they are treated as missing values
425+
if v.dtype.kind in "iu":
426+
v = v.astype("float64")
427+
if v.dtype.kind == "f":
428+
v = v.filled(np.nan)
429+
else:
430+
v = np.where(v.mask, None, v.data.astype("object"))
420431
v = copy_to_readonly_numpy_array(v)
421432
elif is_simple_array(v):
422433
v = to_scalar_or_list(v)

‎tests/test_plotly_utils/validators/test_dataarray_validator.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,37 @@ def test_validator_acceptance_homogeneous(val, validator):
4242
assert np.array_equal(validator.present(coerce_val), val)
4343

4444

45+
@pytest.mark.parametrize(
46+
"val,expected",
47+
[
48+
(np.ma.array([1, 2, 3], mask=[0, 1, 0]), np.array([1, np.nan, 3])),
49+
(
50+
np.ma.array([1, 2, 3], mask=[0, 1, 0], dtype="float32"),
51+
np.array([1, np.nan, 3], dtype="float32"),
52+
),
53+
(np.ma.array([1, 2, 3], mask=True), np.array([np.nan, np.nan, np.nan])),
54+
(np.ma.array([1, 2, 3], mask=False), np.array([1, 2, 3])),
55+
(
56+
np.ma.array([[1, 2], [3, 4]], mask=[[0, 1], [0, 0]]),
57+
np.array([[1, np.nan], [3, 4]]),
58+
),
59+
(
60+
np.ma.array(["a", "b", "c"], mask=[0, 1, 0]),
61+
np.array(["a", None, "c"], dtype="object"),
62+
),
63+
(
64+
np.ma.array([True, False, True], mask=[0, 1, 0]),
65+
np.array([True, None, True], dtype="object"),
66+
),
67+
],
68+
)
69+
def test_validator_acceptance_masked(val, expected, validator):
70+
coerce_val = validator.validate_coerce(val)
71+
assert not np.ma.isMaskedArray(coerce_val)
72+
assert coerce_val.dtype == expected.dtype
73+
np.testing.assert_array_equal(coerce_val, expected)
74+
75+
4576
# Rejection
4677
@pytest.mark.parametrize("val", ["Hello", 23, set(), {}])
4778
def test_rejection(val, validator):

0 commit comments

Comments
 (0)