diff --git a/datatree/datatree.py b/datatree/datatree.py index f2618d2a..52049f47 100644 --- a/datatree/datatree.py +++ b/datatree/datatree.py @@ -505,6 +505,11 @@ 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_hollow(self) -> bool: + """True if only leaf nodes contain data.""" + return not any(node.has_data for node in self.subtree if not node.is_leaf) + @property def variables(self) -> Mapping[Hashable, Variable]: """Low level interface to node contents as dict of Variable objects. diff --git a/datatree/tests/test_datatree.py b/datatree/tests/test_datatree.py index 9dd601c8..e9f373d7 100644 --- a/datatree/tests/test_datatree.py +++ b/datatree/tests/test_datatree.py @@ -136,6 +136,16 @@ def test_has_data(self): john = DataTree(name="john", data=None) assert not john.has_data + def test_is_hollow(self): + john = DataTree(data=xr.Dataset({"a": 0})) + assert john.is_hollow + + eve = DataTree(children={"john": john}) + assert eve.is_hollow + + eve.ds = xr.Dataset({"a": 1}) + assert not eve.is_hollow + class TestVariablesChildrenNameCollisions: def test_parent_already_has_variable_with_childs_name(self): diff --git a/docs/source/api.rst b/docs/source/api.rst index 54a98639..215105ef 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -64,6 +64,7 @@ This interface echoes that of ``xarray.Dataset``. DataTree.has_data DataTree.has_attrs DataTree.is_empty + DataTree.is_hollow .. diff --git a/docs/source/hierarchical-data.rst b/docs/source/hierarchical-data.rst index f74a635d..51bcea56 100644 --- a/docs/source/hierarchical-data.rst +++ b/docs/source/hierarchical-data.rst @@ -369,6 +369,25 @@ You can see this tree is similar to the ``dt`` object above, except that it is m (If you want to keep the name of the root node, you will need to add the ``name`` kwarg to :py:class:`from_dict`, i.e. ``DataTree.from_dict(non_empty_nodes, name=dt.root.name)``.) +.. _Tree Contents: + +Tree Contents +------------- + +Hollow Trees +~~~~~~~~~~~~ + +A concept that can sometimes be useful is that of a "Hollow Tree", which means a tree with data stored only at the leaf nodes. +This is useful because certain useful tree manipulation operations only make sense for hollow trees. + +You can check if a tree is a hollow tree by using the :py:meth:`~DataTree.is_hollow` property. +We can see that the Simpson's family is not hollow because the data variable ``"age"`` is present at some nodes which +have children (i.e. Abe and Homer). + +.. ipython:: python + + simpsons.is_hollow + .. _manipulating trees: Manipulating Trees diff --git a/docs/source/whats-new.rst b/docs/source/whats-new.rst index c3606f19..5163fdd6 100644 --- a/docs/source/whats-new.rst +++ b/docs/source/whats-new.rst @@ -25,6 +25,8 @@ New Features - New :py:meth:`DataTree.match` method for glob-like pattern matching of node paths. (:pull:`267`) By `Tom Nicholas `_. +- New :py:meth:`DataTree.is_hollow` property for checking if data is only contained at the leaf nodes. (:pull:`272`) + By `Tom Nicholas `_. - Indicate which node caused the problem if error encountered while applying user function using :py:func:`map_over_subtree` (:issue:`190`, :pull:`264`). Only works when using python 3.11 or later. By `Tom Nicholas `_.