From 67a8701dbf45e4ece8ebcd4077acfac52501a536 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Thu, 17 Jul 2025 09:36:59 -0600 Subject: [PATCH 1/3] Fix transpose of BoolTypeArray, NativeEndiannessArray Closes #10536 --- doc/whats-new.rst | 3 +++ xarray/coding/variables.py | 6 ++++++ xarray/tests/test_backends.py | 13 ++++++++++++- xarray/tests/test_conventions.py | 9 +++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 605175e32d2..9d015cb7068 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -28,6 +28,9 @@ Bug fixes - Fix Pydap Datatree backend testing. Testing now compares elements of (unordered) two sets (before, lists) (:pull:`10525`). By `Miguel Jimenez-Urias `_. +- Fix transpose of boolean arrays read from disk. (:issue:`10536`) + By `Deepak Cherian `_. + Documentation ~~~~~~~~~~~~~ diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py index eff08c74500..bff0f529124 100644 --- a/xarray/coding/variables.py +++ b/xarray/coding/variables.py @@ -70,6 +70,9 @@ def __getitem__(self, key) -> Self: def get_duck_array(self): return duck_array_ops.astype(self.array.get_duck_array(), dtype=self.dtype) + def transpose(self, order): + return type(self)(self.array.transpose(order)) + class BoolTypeArray(indexing.ExplicitlyIndexedNDArrayMixin): """Decode arrays on the fly from integer to boolean datatype @@ -111,6 +114,9 @@ def __getitem__(self, key) -> Self: def get_duck_array(self): return duck_array_ops.astype(self.array.get_duck_array(), dtype=self.dtype) + def transpose(self, order): + return type(self)(self.array.transpose(order)) + def _apply_mask( data: np.ndarray, diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 6997be200b1..93329b2297d 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -308,7 +308,15 @@ def create_encoded_unsigned_false_masked_scaled_data(dtype: np.dtype) -> Dataset def create_boolean_data() -> Dataset: attributes = {"units": "-"} - return Dataset({"x": ("t", [True, False, False, True], attributes)}) + return Dataset( + { + "x": ( + ("t", "x"), + [[False, True, False, True], [True, False, False, True]], + attributes, + ) + } + ) class TestCommon: @@ -726,6 +734,9 @@ def test_roundtrip_boolean_dtype(self) -> None: with self.roundtrip(actual) as actual2: assert_identical(original, actual2) assert actual2["x"].dtype == "bool" + with self.roundtrip(actual) as actual3: + # GH10536 + assert_identical(original.transpose(), actual3.transpose()) def test_orthogonal_indexing(self) -> None: in_memory = create_test_data() diff --git a/xarray/tests/test_conventions.py b/xarray/tests/test_conventions.py index ce792c83740..6ab45ca12a5 100644 --- a/xarray/tests/test_conventions.py +++ b/xarray/tests/test_conventions.py @@ -37,6 +37,10 @@ def test_booltype_array(self) -> None: assert bx.dtype == bool assert_array_equal(bx, np.array([True, False, True, True, False], dtype=bool)) + x = np.array([[1, 0, 1], [0, 1, 0]], dtype="i1") + bx = coding.variables.BoolTypeArray(x) + assert_array_equal(bx.transpose((1, 0)), x.transpose((1, 0))) + class TestNativeEndiannessArray: def test(self) -> None: @@ -47,6 +51,11 @@ def test(self) -> None: assert a.dtype == expected[:].dtype assert_array_equal(a, expected) + x = np.arange(6, dtype=">i8").reshape((2, 3)) + a = coding.variables.NativeEndiannessArray(x) + expected = np.arange(6, dtype="int64").reshape((2, 3)) + assert_array_equal(a.transpose((1, 0)), expected.transpose((1, 0))) + def test_decode_cf_with_conflicting_fill_missing_value() -> None: expected = Variable(["t"], [np.nan, np.nan, 2], {"units": "foobar"}) From d85d61f9094d6d9f2f6f7aafddd26588b30b9582 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Thu, 17 Jul 2025 10:02:24 -0600 Subject: [PATCH 2/3] Fix mypy --- xarray/tests/test_conventions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_conventions.py b/xarray/tests/test_conventions.py index 6ab45ca12a5..fb292775c64 100644 --- a/xarray/tests/test_conventions.py +++ b/xarray/tests/test_conventions.py @@ -51,10 +51,10 @@ def test(self) -> None: assert a.dtype == expected[:].dtype assert_array_equal(a, expected) - x = np.arange(6, dtype=">i8").reshape((2, 3)) - a = coding.variables.NativeEndiannessArray(x) + y = np.arange(6, dtype=">i8").reshape((2, 3)) + b = coding.variables.NativeEndiannessArray(y) expected = np.arange(6, dtype="int64").reshape((2, 3)) - assert_array_equal(a.transpose((1, 0)), expected.transpose((1, 0))) + assert_array_equal(b.transpose((1, 0)), expected.transpose((1, 0))) def test_decode_cf_with_conflicting_fill_missing_value() -> None: From decd74623ae897ef5ca38fa6e6289212329a6271 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 18 Jul 2025 14:32:46 -0600 Subject: [PATCH 3/3] fix mypy --- xarray/tests/test_conventions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_conventions.py b/xarray/tests/test_conventions.py index fb292775c64..4b3729e2724 100644 --- a/xarray/tests/test_conventions.py +++ b/xarray/tests/test_conventions.py @@ -53,8 +53,8 @@ def test(self) -> None: y = np.arange(6, dtype=">i8").reshape((2, 3)) b = coding.variables.NativeEndiannessArray(y) - expected = np.arange(6, dtype="int64").reshape((2, 3)) - assert_array_equal(b.transpose((1, 0)), expected.transpose((1, 0))) + expected2 = np.arange(6, dtype="int64").reshape((2, 3)) + assert_array_equal(b.transpose((1, 0)), expected2.transpose((1, 0))) def test_decode_cf_with_conflicting_fill_missing_value() -> None: