From 16b92e3486a9d8370056102d0a7281a4b4c85966 Mon Sep 17 00:00:00 2001 From: aladinor Date: Fri, 1 Aug 2025 12:12:41 -0500 Subject: [PATCH 01/32] =?UTF-8?q?Add=20DataTree.is=5Fdata=5Fempty=20proper?= =?UTF-8?q?ty=20and=20.prune()=20method=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=E2=94=82=20=E2=94=82=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=E2=94=82=20=E2=94=82=20=20=20-=20Add=20is=5Fdata?= =?UTF-8?q?=5Fempty=20property=20to=20check=20if=20node=20contains=20data?= =?UTF-8?q?=20variables=20with=20actual=20data=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20?= =?UTF-8?q?=E2=94=82=20=20=20-=20Add=20prune()=20method=20to=20remove=20em?= =?UTF-8?q?pty=20nodes=20while=20preserving=20tree=20structure=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20=E2=94=82=20=20?= =?UTF-8?q?=20-=20Include=20comprehensive=20tests=20covering=20basic=20pru?= =?UTF-8?q?ning,=20intermediate=20nodes,=20and=20filtering=20scenarios=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20=E2=94=82=20?= =?UTF-8?q?=20=20-=20Useful=20for=20cleaning=20up=20DataTree=20after=20tim?= =?UTF-8?q?e-based=20filtering=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xarray/core/datatree.py | 43 +++++++++++++++++++++ xarray/tests/test_datatree.py | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index afef2f20094..3f146b2aaa6 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -686,6 +686,13 @@ def is_empty(self) -> bool: """False if node contains any data or attrs. Does not look at children.""" return not (self.has_data or self.has_attrs) + @property + def is_data_empty(self) -> bool: + """False if node contains any data variables with actual data. Does not look at children.""" + if not self._data_variables: + return True + return not any(var.size > 0 for var in self._data_variables.values()) + @property def is_hollow(self) -> bool: """True if only leaf nodes contain data.""" @@ -1448,6 +1455,42 @@ def filter_like(self, other: DataTree) -> DataTree: other_keys = {key for key, _ in other.subtree_with_keys} return self.filter(lambda node: node.relative_to(self) in other_keys) + def prune(self) -> DataTree: + """ + Remove empty nodes from the tree. + + Returns a new tree containing only nodes that contain data variables. + Intermediate nodes are kept if they are required to support non-empty children. + + Returns + ------- + DataTree + A new tree with empty nodes removed. + + See Also + -------- + filter + is_data_empty + + Examples + -------- + >>> dt = xr.DataTree.from_dict( + ... { + ... "/a": xr.Dataset({"foo": ("x", [1, 2])}), + ... "/b": xr.Dataset(), # empty dataset + ... } + ... ) + >>> dt.prune() + + Group: / + └── Group: /a + Dimensions: (x: 2) + Dimensions without coordinates: x + Data variables: + foo (x) int64 16B 1 2 + """ + return self.filter(lambda node: not node.is_data_empty) + def match(self, pattern: str) -> DataTree: """ Return nodes with paths matching pattern. diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 2bf079a7cbd..ce405af4105 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -1942,6 +1942,77 @@ def test_filter(self) -> None: ) assert_identical(actual, expected) + def test_is_data_empty(self) -> None: + ds_with_data = xr.Dataset({"foo": ("x", [1, 2])}) + dt_with_data = DataTree(dataset=ds_with_data) + assert dt_with_data.is_data_empty is False + + ds_coords_only = xr.Dataset(coords={"x": [1, 2]}) + dt_coords_only = DataTree(dataset=ds_coords_only) + assert dt_coords_only.is_data_empty is True + + dt_empty = DataTree() + assert dt_empty.is_data_empty is True + + ds_zero_size = xr.Dataset({"var": ("time", [])}) + dt_zero_size = DataTree(dataset=ds_zero_size) + assert dt_zero_size.is_data_empty is True + + def test_prune_basic(self) -> None: + # Test basic pruning of empty nodes + tree = DataTree.from_dict( + {"/a": xr.Dataset({"foo": ("x", [1, 2])}), "/b": xr.Dataset()} + ) + + pruned = tree.prune() + + assert "a" in pruned.children + assert "b" not in pruned.children + assert_identical( + pruned.children["a"].to_dataset(), tree.children["a"].to_dataset() + ) + + def test_prune_with_intermediate_nodes(self) -> None: + tree = DataTree.from_dict( + { + "/": xr.Dataset(), + "/group1": xr.Dataset(), + "/group1/subA": xr.Dataset({"temp": ("x", [1, 2])}), + "/group1/subB": xr.Dataset(), + "/group2": xr.Dataset(), + } + ) + + pruned = tree.prune() + + # Check structure + assert "group1" in pruned.children + assert "subA" in pruned.children["group1"].children + assert "subB" not in pruned.children["group1"].children + assert "group2" not in pruned.children + + def test_prune_after_filtering(self) -> None: + import pandas as pd + + ds1 = xr.Dataset( + {"foo": ("time", [1, 2, 3, 4, 5])}, + coords={"time": pd.date_range("2023-01-01", periods=5, freq="D")}, + ) + ds2 = xr.Dataset( + {"var": ("time", [1, 2, 3, 4, 5])}, + coords={"time": pd.date_range("2023-01-04", periods=5, freq="D")}, + ) + + tree = DataTree.from_dict({"a": ds1, "b": ds2}) + filtered = tree.sel(time=slice("2023-01-01", "2023-01-03")) + + assert "b" in filtered.children + assert filtered.children["b"].is_data_empty is True + + pruned = filtered.prune() + assert "a" in pruned.children + assert "b" not in pruned.children + class TestIndexing: def test_isel_siblings(self) -> None: From 0b5ee3b32ee2dc63ee6f5e259b0d64a2961c6604 Mon Sep 17 00:00:00 2001 From: aladinor Date: Fri, 1 Aug 2025 12:40:46 -0500 Subject: [PATCH 02/32] documenting changes in whats-new.rst file --- doc/whats-new.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 06a3c2cb22d..c2411af09be 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -12,6 +12,11 @@ v2025.07.2 (unreleased) New Features ~~~~~~~~~~~~ +- Added :py:attr:`DataTree.is_data_empty` property to check if a node contains data variables with actual data (:issue:`10590`, :pull:`10598`). + By `Alfonso Ladino Date: Fri, 1 Aug 2025 12:43:15 -0500 Subject: [PATCH 03/32] removing blank lines --- xarray/tests/test_datatree.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index ce405af4105..903712787a9 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -1959,7 +1959,6 @@ def test_is_data_empty(self) -> None: assert dt_zero_size.is_data_empty is True def test_prune_basic(self) -> None: - # Test basic pruning of empty nodes tree = DataTree.from_dict( {"/a": xr.Dataset({"foo": ("x", [1, 2])}), "/b": xr.Dataset()} ) @@ -1985,7 +1984,6 @@ def test_prune_with_intermediate_nodes(self) -> None: pruned = tree.prune() - # Check structure assert "group1" in pruned.children assert "subA" in pruned.children["group1"].children assert "subB" not in pruned.children["group1"].children From ba73805bded6501a5829b6b38c6dd09678ee31e9 Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 19:16:38 -0500 Subject: [PATCH 04/32] removing new property instead using data_vars and fixing corresponding test --- xarray/core/datatree.py | 58 +++++++++++++++++++++++++++-------- xarray/tests/test_datatree.py | 32 ++++++++++--------- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 3f146b2aaa6..f7834df6e99 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -686,13 +686,6 @@ def is_empty(self) -> bool: """False if node contains any data or attrs. Does not look at children.""" return not (self.has_data or self.has_attrs) - @property - def is_data_empty(self) -> bool: - """False if node contains any data variables with actual data. Does not look at children.""" - if not self._data_variables: - return True - return not any(var.size > 0 for var in self._data_variables.values()) - @property def is_hollow(self) -> bool: """True if only leaf nodes contain data.""" @@ -1455,13 +1448,19 @@ def filter_like(self, other: DataTree) -> DataTree: other_keys = {key for key, _ in other.subtree_with_keys} return self.filter(lambda node: node.relative_to(self) in other_keys) - def prune(self) -> DataTree: + def prune(self, drop_size_zero_vars: bool = True) -> DataTree: """ Remove empty nodes from the tree. - Returns a new tree containing only nodes that contain data variables. + Returns a new tree containing only nodes that contain data variables with actual data. Intermediate nodes are kept if they are required to support non-empty children. + Parameters + ---------- + drop_size_zero_vars : bool, default True + If True, also considers variables with zero size as empty. + If False, keeps nodes with data variables even if they have zero size. + Returns ------- DataTree @@ -1470,14 +1469,13 @@ def prune(self) -> DataTree: See Also -------- filter - is_data_empty Examples -------- >>> dt = xr.DataTree.from_dict( ... { ... "/a": xr.Dataset({"foo": ("x", [1, 2])}), - ... "/b": xr.Dataset(), # empty dataset + ... "/b": xr.Dataset(), ... } ... ) >>> dt.prune() @@ -1488,8 +1486,44 @@ def prune(self) -> DataTree: Dimensions without coordinates: x Data variables: foo (x) int64 16B 1 2 + + With zero-size variables: + + >>> dt_zero = xr.DataTree.from_dict( + ... { + ... "/a": xr.Dataset({"foo": ("x", [1, 2])}), + ... "/b": xr.Dataset({"empty": ("dim", [])}), + ... } + ... ) + >>> dt_zero.prune() + + Group: / + └── Group: /a + Dimensions: (x: 2) + Dimensions without coordinates: x + Data variables: + foo (x) int64 16B 1 2 + + >>> dt_zero.prune(drop_size_zero_vars=False) + + Group: / + ├── Group: /a + │ Dimensions: (x: 2) + │ Dimensions without coordinates: x + │ Data variables: + │ foo (x) int64 16B 1 2 + └── Group: /b + Dimensions: (dim: 0) + Dimensions without coordinates: dim + Data variables: + empty (dim) float64 0B """ - return self.filter(lambda node: not node.is_data_empty) + if drop_size_zero_vars: + return self.filter( + lambda node: len(node.data_vars) > 0 + and any(var.size > 0 for var in node.data_vars.values()) + ) + return self.filter(lambda node: len(node.data_vars) > 0) def match(self, pattern: str) -> DataTree: """ diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 903712787a9..e383777583c 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -1942,21 +1942,23 @@ def test_filter(self) -> None: ) assert_identical(actual, expected) - def test_is_data_empty(self) -> None: - ds_with_data = xr.Dataset({"foo": ("x", [1, 2])}) - dt_with_data = DataTree(dataset=ds_with_data) - assert dt_with_data.is_data_empty is False - - ds_coords_only = xr.Dataset(coords={"x": [1, 2]}) - dt_coords_only = DataTree(dataset=ds_coords_only) - assert dt_coords_only.is_data_empty is True - - dt_empty = DataTree() - assert dt_empty.is_data_empty is True + def test_prune_with_zero_size_vars(self) -> None: + tree = DataTree.from_dict( + { + "/a": xr.Dataset({"foo": ("x", [1, 2])}), + "/b": xr.Dataset({"empty": ("dim", [])}), + "/c": xr.Dataset(), + } + ) + pruned_default = tree.prune() + assert "a" in pruned_default.children + assert "b" in pruned_default.children + assert "c" not in pruned_default.children - ds_zero_size = xr.Dataset({"var": ("time", [])}) - dt_zero_size = DataTree(dataset=ds_zero_size) - assert dt_zero_size.is_data_empty is True + pruned_strict = tree.prune(drop_size_zero_vars=True) + assert "a" in pruned_strict.children + assert "b" not in pruned_strict.children + assert "c" not in pruned_strict.children def test_prune_basic(self) -> None: tree = DataTree.from_dict( @@ -2005,7 +2007,7 @@ def test_prune_after_filtering(self) -> None: filtered = tree.sel(time=slice("2023-01-01", "2023-01-03")) assert "b" in filtered.children - assert filtered.children["b"].is_data_empty is True + assert len(filtered.children["b"].data_vars) == 1 pruned = filtered.prune() assert "a" in pruned.children From 6a9664b22474dd51296e487138575f72b2a22578 Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 19:17:29 -0500 Subject: [PATCH 05/32] removing .is_empty_data entry --- doc/whats-new.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index c2411af09be..3e95e94d71e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -12,8 +12,6 @@ v2025.07.2 (unreleased) New Features ~~~~~~~~~~~~ -- Added :py:attr:`DataTree.is_data_empty` property to check if a node contains data variables with actual data (:issue:`10590`, :pull:`10598`). - By `Alfonso Ladino Date: Mon, 4 Aug 2025 19:20:06 -0500 Subject: [PATCH 06/32] updating github url --- doc/whats-new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 3e95e94d71e..670b65a4e55 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -14,7 +14,7 @@ New Features ~~~~~~~~~~~~ - Added :py:meth:`DataTree.prune` method to remove empty nodes while preserving tree structure. Useful for cleaning up DataTree after time-based filtering operations (:issue:`10590`, :pull:`10598`). - By `Alfonso Ladino `_. Breaking changes From d4b39702a8208377940316bb5ad4af4a5cd8b64e Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 19:27:27 -0500 Subject: [PATCH 07/32] fixing test accordingly --- xarray/tests/test_datatree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index e383777583c..46ce6e5a46a 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -1952,12 +1952,12 @@ def test_prune_with_zero_size_vars(self) -> None: ) pruned_default = tree.prune() assert "a" in pruned_default.children - assert "b" in pruned_default.children + assert "b" not in pruned_default.children assert "c" not in pruned_default.children - pruned_strict = tree.prune(drop_size_zero_vars=True) + pruned_strict = tree.prune(drop_size_zero_vars=False) assert "a" in pruned_strict.children - assert "b" not in pruned_strict.children + assert "b" in pruned_strict.children assert "c" not in pruned_strict.children def test_prune_basic(self) -> None: From 443183441bc33f935a498c683fb73a9a8799683d Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 19:31:34 -0500 Subject: [PATCH 08/32] fixing doctest --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index f7834df6e99..a0c8096933e 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1513,7 +1513,7 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: │ Data variables: │ foo (x) int64 16B 1 2 └── Group: /b - Dimensions: (dim: 0) + Dimensions: (dim: 0) Dimensions without coordinates: dim Data variables: empty (dim) float64 0B From 7fc2e8be0334a93e76264b034c1e7543c30064d8 Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 19:42:29 -0500 Subject: [PATCH 09/32] fixing doctest --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index a0c8096933e..fbba01c980a 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1516,7 +1516,7 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: Dimensions: (dim: 0) Dimensions without coordinates: dim Data variables: - empty (dim) float64 0B + empty (dim) float64 0B """ if drop_size_zero_vars: return self.filter( From 6e1956fb8da9225276de4154ea85124772b6a495 Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 19:44:47 -0500 Subject: [PATCH 10/32] fixing doctest --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index fbba01c980a..a0c8096933e 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1516,7 +1516,7 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: Dimensions: (dim: 0) Dimensions without coordinates: dim Data variables: - empty (dim) float64 0B + empty (dim) float64 0B """ if drop_size_zero_vars: return self.filter( From 6f2028687184b5d7197f4b8a809a7b41d0ede23e Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 20:07:04 -0500 Subject: [PATCH 11/32] replacing doctest --- xarray/core/datatree.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index a0c8096933e..c71c4a83fcf 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1487,15 +1487,17 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: Data variables: foo (x) int64 16B 1 2 - With zero-size variables: + The ``drop_size_zero_vars`` parameter controls whether variables + with zero size are considered empty: - >>> dt_zero = xr.DataTree.from_dict( + >>> dt_with_empty = xr.DataTree.from_dict( ... { ... "/a": xr.Dataset({"foo": ("x", [1, 2])}), - ... "/b": xr.Dataset({"empty": ("dim", [])}), + ... "/b": xr.Dataset({"bar": ("x", [])}), ... } ... ) - >>> dt_zero.prune() + >>> # Default behavior removes nodes with zero-size variables + >>> dt_with_empty.prune() Group: / └── Group: /a @@ -1503,20 +1505,6 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: Dimensions without coordinates: x Data variables: foo (x) int64 16B 1 2 - - >>> dt_zero.prune(drop_size_zero_vars=False) - - Group: / - ├── Group: /a - │ Dimensions: (x: 2) - │ Dimensions without coordinates: x - │ Data variables: - │ foo (x) int64 16B 1 2 - └── Group: /b - Dimensions: (dim: 0) - Dimensions without coordinates: dim - Data variables: - empty (dim) float64 0B """ if drop_size_zero_vars: return self.filter( From 6da389d05efed60da07f67e043f7eb31d7a08f43 Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 20:10:06 -0500 Subject: [PATCH 12/32] replacing doctest --- xarray/core/datatree.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index c71c4a83fcf..49ab102e597 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1497,14 +1497,19 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: ... } ... ) >>> # Default behavior removes nodes with zero-size variables - >>> dt_with_empty.prune() + >>> dt_with_empty.prune(drop_size_zero_vars=False) Group: / - └── Group: /a - Dimensions: (x: 2) + ├── Group: /a + │ Dimensions: (x: 2) + │ Dimensions without coordinates: x + │ Data variables: + │ foo (x) int64 16B 1 2 + └── Group: /b + Dimensions: (x: 0) Dimensions without coordinates: x Data variables: - foo (x) int64 16B 1 2 + bar (x) float64 0B """ if drop_size_zero_vars: return self.filter( From d7f85b8d9272d34551d5d385e472088e0255273d Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 20:10:40 -0500 Subject: [PATCH 13/32] removing empty line --- xarray/core/datatree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 49ab102e597..da52dc984f0 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1496,7 +1496,6 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: ... "/b": xr.Dataset({"bar": ("x", [])}), ... } ... ) - >>> # Default behavior removes nodes with zero-size variables >>> dt_with_empty.prune(drop_size_zero_vars=False) Group: / From ecf186d03c271024e8d4788e201a4223b768ffc1 Mon Sep 17 00:00:00 2001 From: aladinor Date: Mon, 4 Aug 2025 20:17:22 -0500 Subject: [PATCH 14/32] removing empty line --- xarray/core/datatree.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index da52dc984f0..1edaf86d1f8 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1496,7 +1496,9 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: ... "/b": xr.Dataset({"bar": ("x", [])}), ... } ... ) - >>> dt_with_empty.prune(drop_size_zero_vars=False) + >>> dt_with_empty.prune( + ... drop_size_zero_vars=False + ... ) # doctest: +NORMALIZE_WHITESPACE Group: / ├── Group: /a From 03c78fa5569a61b450551abc4432392a298bf1e9 Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Tue, 5 Aug 2025 10:04:22 -0500 Subject: [PATCH 15/32] Update xarray/core/datatree.py Co-authored-by: Tom Nicholas --- xarray/core/datatree.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 1edaf86d1f8..fc2bc5f8bf5 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1512,12 +1512,13 @@ def prune(self, drop_size_zero_vars: bool = True) -> DataTree: Data variables: bar (x) float64 0B """ + non_empty_cond: Callable[DataTree, [DataTree]] if drop_size_zero_vars: - return self.filter( - lambda node: len(node.data_vars) > 0 - and any(var.size > 0 for var in node.data_vars.values()) - ) - return self.filter(lambda node: len(node.data_vars) > 0) + non_empty_cond = lambda node: len(node.data_vars) > 0 and any(var.size > 0 for var in node.data_vars.values()) + else: + non_empty_cond = lambda node: len(node.data_vars) > 0 + + return self.filter(cond) def match(self, pattern: str) -> DataTree: """ From 61617555dd9c14aa1dd5360952558a9d21d72078 Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Tue, 5 Aug 2025 10:04:31 -0500 Subject: [PATCH 16/32] Update xarray/core/datatree.py Co-authored-by: Tom Nicholas --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index fc2bc5f8bf5..e1e18b95c51 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1448,7 +1448,7 @@ def filter_like(self, other: DataTree) -> DataTree: other_keys = {key for key, _ in other.subtree_with_keys} return self.filter(lambda node: node.relative_to(self) in other_keys) - def prune(self, drop_size_zero_vars: bool = True) -> DataTree: + def prune(self, drop_size_zero_vars: bool = False) -> DataTree: """ Remove empty nodes from the tree. From a7cd8d56a284813456096c1e451cfc0b926344bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:04:44 +0000 Subject: [PATCH 17/32] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/core/datatree.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index e1e18b95c51..1c9a4e4d7d6 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1514,10 +1514,12 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: """ non_empty_cond: Callable[DataTree, [DataTree]] if drop_size_zero_vars: - non_empty_cond = lambda node: len(node.data_vars) > 0 and any(var.size > 0 for var in node.data_vars.values()) + non_empty_cond = lambda node: len(node.data_vars) > 0 and any( + var.size > 0 for var in node.data_vars.values() + ) else: non_empty_cond = lambda node: len(node.data_vars) > 0 - + return self.filter(cond) def match(self, pattern: str) -> DataTree: From 338c76ab7963e559a52d8a44fa9a4f9ceeb95b34 Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 5 Aug 2025 10:41:11 -0500 Subject: [PATCH 18/32] improving doctests --- xarray/core/datatree.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 1c9a4e4d7d6..435c3daf88e 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1457,7 +1457,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Parameters ---------- - drop_size_zero_vars : bool, default True + drop_size_zero_vars : bool, default False If True, also considers variables with zero size as empty. If False, keeps nodes with data variables even if they have zero size. @@ -1475,30 +1475,11 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: >>> dt = xr.DataTree.from_dict( ... { ... "/a": xr.Dataset({"foo": ("x", [1, 2])}), - ... "/b": xr.Dataset(), - ... } - ... ) - >>> dt.prune() - - Group: / - └── Group: /a - Dimensions: (x: 2) - Dimensions without coordinates: x - Data variables: - foo (x) int64 16B 1 2 - - The ``drop_size_zero_vars`` parameter controls whether variables - with zero size are considered empty: - - >>> dt_with_empty = xr.DataTree.from_dict( - ... { - ... "/a": xr.Dataset({"foo": ("x", [1, 2])}), ... "/b": xr.Dataset({"bar": ("x", [])}), + ... "/c": xr.Dataset(), ... } ... ) - >>> dt_with_empty.prune( - ... drop_size_zero_vars=False - ... ) # doctest: +NORMALIZE_WHITESPACE + >>> dt.prune() Group: / ├── Group: /a @@ -1511,6 +1492,18 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Dimensions without coordinates: x Data variables: bar (x) float64 0B + + The ``drop_size_zero_vars`` parameter controls whether variables + with zero size are considered empty: + + >>> dt_with_empty.prune(drop_size_zero_vars=True) + + Group: / + └── Group: /a + Dimensions: (x: 2) + Dimensions without coordinates: x + Data variables: + foo (x) int64 16B 1 2 """ non_empty_cond: Callable[DataTree, [DataTree]] if drop_size_zero_vars: @@ -1520,7 +1513,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: else: non_empty_cond = lambda node: len(node.data_vars) > 0 - return self.filter(cond) + return self.filter(non_empty_cond) def match(self, pattern: str) -> DataTree: """ From aea2e67a813d03351eb6ffb9c23ee354430bf0df Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 5 Aug 2025 10:54:51 -0500 Subject: [PATCH 19/32] fixing typo --- xarray/core/datatree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 435c3daf88e..f6732998ae4 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1496,7 +1496,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: - >>> dt_with_empty.prune(drop_size_zero_vars=True) + >>> dt.prune(drop_size_zero_vars=True) Group: / └── Group: /a @@ -1505,7 +1505,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Data variables: foo (x) int64 16B 1 2 """ - non_empty_cond: Callable[DataTree, [DataTree]] + non_empty_cond: Callable[[DataTree], bool] if drop_size_zero_vars: non_empty_cond = lambda node: len(node.data_vars) > 0 and any( var.size > 0 for var in node.data_vars.values() From 730c7aa4c1ff3072e490893f12a2ca000654e1aa Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 5 Aug 2025 10:55:29 -0500 Subject: [PATCH 20/32] refactoring test accodingly to Tom's suggestion --- xarray/tests/test_datatree.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 46ce6e5a46a..47a934f5e22 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -1950,15 +1950,23 @@ def test_prune_with_zero_size_vars(self) -> None: "/c": xr.Dataset(), } ) + pruned_default = tree.prune() - assert "a" in pruned_default.children - assert "b" not in pruned_default.children - assert "c" not in pruned_default.children - - pruned_strict = tree.prune(drop_size_zero_vars=False) - assert "a" in pruned_strict.children - assert "b" in pruned_strict.children - assert "c" not in pruned_strict.children + expected_default = DataTree.from_dict( + { + "/a": xr.Dataset({"foo": ("x", [1, 2])}), + "/b": xr.Dataset({"empty": ("dim", [])}), + } + ) + assert_identical(pruned_default, expected_default) + + pruned_strict = tree.prune(drop_size_zero_vars=True) + expected_strict = DataTree.from_dict( + { + "/a": xr.Dataset({"foo": ("x", [1, 2])}), + } + ) + assert_identical(pruned_strict, expected_strict) def test_prune_basic(self) -> None: tree = DataTree.from_dict( From 7aa25c988085e45beb25abbe8b849ca058ef8519 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 06:37:42 -0500 Subject: [PATCH 21/32] fixing test_prune_after_filtering --- xarray/tests/test_datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 47a934f5e22..6ef1ee4cc0b 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -2017,7 +2017,7 @@ def test_prune_after_filtering(self) -> None: assert "b" in filtered.children assert len(filtered.children["b"].data_vars) == 1 - pruned = filtered.prune() + pruned = filtered.prune(drop_size_zero_vars=True) assert "a" in pruned.children assert "b" not in pruned.children From d99ee78372e57bb5dd04212e2fa2fa021e10d4a4 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 06:54:11 -0500 Subject: [PATCH 22/32] refactoring test to use assert_identical --- xarray/tests/test_datatree.py | 47 +++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 6ef1ee4cc0b..7337a142e0e 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -1942,6 +1942,19 @@ def test_filter(self) -> None: ) assert_identical(actual, expected) + def test_prune_basic(self) -> None: + tree = DataTree.from_dict( + {"/a": xr.Dataset({"foo": ("x", [1, 2])}), "/b": xr.Dataset()} + ) + + pruned = tree.prune() + + assert "a" in pruned.children + assert "b" not in pruned.children + assert_identical( + pruned.children["a"].to_dataset(), tree.children["a"].to_dataset() + ) + def test_prune_with_zero_size_vars(self) -> None: tree = DataTree.from_dict( { @@ -1968,19 +1981,6 @@ def test_prune_with_zero_size_vars(self) -> None: ) assert_identical(pruned_strict, expected_strict) - def test_prune_basic(self) -> None: - tree = DataTree.from_dict( - {"/a": xr.Dataset({"foo": ("x", [1, 2])}), "/b": xr.Dataset()} - ) - - pruned = tree.prune() - - assert "a" in pruned.children - assert "b" not in pruned.children - assert_identical( - pruned.children["a"].to_dataset(), tree.children["a"].to_dataset() - ) - def test_prune_with_intermediate_nodes(self) -> None: tree = DataTree.from_dict( { @@ -1988,16 +1988,17 @@ def test_prune_with_intermediate_nodes(self) -> None: "/group1": xr.Dataset(), "/group1/subA": xr.Dataset({"temp": ("x", [1, 2])}), "/group1/subB": xr.Dataset(), - "/group2": xr.Dataset(), + "/group2": xr.Dataset({"empty": ("dim", [])}), } ) - pruned = tree.prune() - - assert "group1" in pruned.children - assert "subA" in pruned.children["group1"].children - assert "subB" not in pruned.children["group1"].children - assert "group2" not in pruned.children + expected_tree = DataTree.from_dict( + { + "/group1/subA": xr.Dataset({"temp": ("x", [1, 2])}), + "/group2": xr.Dataset({"empty": ("dim", [])}), + } + ) + assert_identical(pruned, expected_tree) def test_prune_after_filtering(self) -> None: import pandas as pd @@ -2018,8 +2019,10 @@ def test_prune_after_filtering(self) -> None: assert len(filtered.children["b"].data_vars) == 1 pruned = filtered.prune(drop_size_zero_vars=True) - assert "a" in pruned.children - assert "b" not in pruned.children + expected_tree = DataTree.from_dict( + {"a": ds1.sel(time=slice("2023-01-01", "2023-01-03"))} + ) + assert_identical(pruned, expected_tree) class TestIndexing: From 82ea57e44ca23cda8783dbfb6e70ee623c5d5dcb Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 07:43:32 -0500 Subject: [PATCH 23/32] refactoring test to use assert)_equal --- xarray/tests/test_datatree.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 7337a142e0e..efa25386440 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -2001,23 +2001,20 @@ def test_prune_with_intermediate_nodes(self) -> None: assert_identical(pruned, expected_tree) def test_prune_after_filtering(self) -> None: - import pandas as pd + from pandas import date_range ds1 = xr.Dataset( {"foo": ("time", [1, 2, 3, 4, 5])}, - coords={"time": pd.date_range("2023-01-01", periods=5, freq="D")}, + coords={"time": date_range("2023-01-01", periods=5, freq="D")}, ) ds2 = xr.Dataset( {"var": ("time", [1, 2, 3, 4, 5])}, - coords={"time": pd.date_range("2023-01-04", periods=5, freq="D")}, + coords={"time": date_range("2023-01-04", periods=5, freq="D")}, ) tree = DataTree.from_dict({"a": ds1, "b": ds2}) filtered = tree.sel(time=slice("2023-01-01", "2023-01-03")) - assert "b" in filtered.children - assert len(filtered.children["b"].data_vars) == 1 - pruned = filtered.prune(drop_size_zero_vars=True) expected_tree = DataTree.from_dict( {"a": ds1.sel(time=slice("2023-01-01", "2023-01-03"))} From 7cd493df74373a3f0af4c8865c85757f7042efc8 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 08:43:49 -0500 Subject: [PATCH 24/32] adding reference to .prune method in Subsetting Tree Nodes --- doc/user-guide/hierarchical-data.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/user-guide/hierarchical-data.rst b/doc/user-guide/hierarchical-data.rst index a350b7851de..adedbf53664 100644 --- a/doc/user-guide/hierarchical-data.rst +++ b/doc/user-guide/hierarchical-data.rst @@ -453,6 +453,8 @@ The result is a new tree, containing only the nodes matching the condition. (Yes, under the hood :py:meth:`~xarray.DataTree.filter` is just syntactic sugar for the pattern we showed you in :ref:`iterating over trees` !) +If you want to filter out empty nodes you can use :py:meth:`~xarray.DataTree.prune`. + .. _Tree Contents: Tree Contents From 82238100c31f362bb0b492b6435e96579b341dcd Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 08:48:08 -0500 Subject: [PATCH 25/32] adding # doctest: +NORMALIZE_WHITESPACE to avoid error with trailing space --- xarray/core/datatree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index f6732998ae4..fb358dd213b 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1479,7 +1479,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: ... "/c": xr.Dataset(), ... } ... ) - >>> dt.prune() + >>> dt.prune() # doctest: +NORMALIZE_WHITESPACE Group: / ├── Group: /a @@ -1496,7 +1496,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: - >>> dt.prune(drop_size_zero_vars=True) + >>> dt.prune(drop_size_zero_vars=True) # doctest: +NORMALIZE_WHITESPACE Group: / └── Group: /a From bbcaf91c06f867f22e179092433886625e849100 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 08:58:18 -0500 Subject: [PATCH 26/32] Fix doctest trailing space issue in prune method --- xarray/core/datatree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index fb358dd213b..f6732998ae4 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1479,7 +1479,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: ... "/c": xr.Dataset(), ... } ... ) - >>> dt.prune() # doctest: +NORMALIZE_WHITESPACE + >>> dt.prune() Group: / ├── Group: /a @@ -1496,7 +1496,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: - >>> dt.prune(drop_size_zero_vars=True) # doctest: +NORMALIZE_WHITESPACE + >>> dt.prune(drop_size_zero_vars=True) Group: / └── Group: /a From 219a4e659cdba2ed70f17f09c2bd9aed4d8d8b96 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 6 Aug 2025 09:10:28 -0500 Subject: [PATCH 27/32] trial 2 fixing trailing space --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index f6732998ae4..2c1d3fc20d5 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1491,7 +1491,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Dimensions: (x: 0) Dimensions without coordinates: x Data variables: - bar (x) float64 0B + bar (x) float64 0B The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: From 27c964a0dd6c9a85c792072de1ac7ae93a038e0e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:10:58 +0000 Subject: [PATCH 28/32] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 2c1d3fc20d5..f6732998ae4 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1491,7 +1491,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Dimensions: (x: 0) Dimensions without coordinates: x Data variables: - bar (x) float64 0B + bar (x) float64 0B The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: From 55e12a35cf76125c683d18a3025c6d94fd93ff80 Mon Sep 17 00:00:00 2001 From: aladinor Date: Sun, 10 Aug 2025 17:56:07 -0500 Subject: [PATCH 29/32] normalizing whitespace for doctest --- xarray/core/datatree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 2c1d3fc20d5..f7499b1c4bd 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1479,7 +1479,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: ... "/c": xr.Dataset(), ... } ... ) - >>> dt.prune() + >>> dt.prune() # doctest: +NORMALIZE_WHITESPACE Group: / ├── Group: /a @@ -1491,7 +1491,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Dimensions: (x: 0) Dimensions without coordinates: x Data variables: - bar (x) float64 0B + bar (x) float64 0B The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: From 678f45b3aafd853af39308c96568efda7de18b66 Mon Sep 17 00:00:00 2001 From: aladinor Date: Sun, 10 Aug 2025 18:02:22 -0500 Subject: [PATCH 30/32] normalizing whitespace and adding ellipsis for doctest --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 169be2e57af..f02498058c0 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1481,7 +1481,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: ... "/c": xr.Dataset(), ... } ... ) - >>> dt.prune() # doctest: +NORMALIZE_WHITESPACE + >>> dt.prune() # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE+NORMALIZE_WHITESPACE Group: / ├── Group: /a From 1e95e56e6ff746b41286e5e6e36f01e7d0e8fa56 Mon Sep 17 00:00:00 2001 From: aladinor Date: Sun, 10 Aug 2025 18:03:41 -0500 Subject: [PATCH 31/32] normalizing whitespace and adding ellipsis for doctest --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index f02498058c0..feff3849d39 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1481,7 +1481,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: ... "/c": xr.Dataset(), ... } ... ) - >>> dt.prune() # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE+NORMALIZE_WHITESPACE + >>> dt.prune() # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE Group: / ├── Group: /a From e4ae620ca8d0e8aac547caf4d48b582897c1eeb4 Mon Sep 17 00:00:00 2001 From: aladinor Date: Sun, 10 Aug 2025 18:07:58 -0500 Subject: [PATCH 32/32] normalizing whitespace and adding ellipsis for doctest --- xarray/core/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index feff3849d39..af71ec6c23d 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1493,7 +1493,7 @@ def prune(self, drop_size_zero_vars: bool = False) -> DataTree: Dimensions: (x: 0) Dimensions without coordinates: x Data variables: - bar (x) float64 0B + bar (x) float64 0B... The ``drop_size_zero_vars`` parameter controls whether variables with zero size are considered empty: