diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c4a5359..b77abba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.17.2] - 2024-04-24 ### Changed: - DAG Constructor: `list_to_dag` and `dict_to_dag` does not rely on `dataframe_to_dag` as pandas dataframe operation is phased out. ### Fixed: - DAG Constructor: Handle cases where reserved keywords are part of attribute upon creation and throw error accordingly. +- [#224] Tree/DAG Constructor: Null checks to not interpret 0 as null, this affects `dataframe_to_tree_by_relation`, +`add_dataframe_to_tree_by_path`, `add_dataframe_to_tree_by_name`, `dataframe_to_tree`, and `dataframe_to_dag`. +This will also affect showing/printing of trees when `attr_omit_null` is set to True. ## [0.17.1] - 2024-04-23 ### Fixed @@ -550,7 +555,8 @@ ignore null attribute columns. - Utility Iterator: Tree traversal methods. - Workflow To Do App: Tree use case with to-do list implementation. -[Unreleased]: https://github.com/kayjan/bigtree/compare/0.17.1...HEAD +[Unreleased]: https://github.com/kayjan/bigtree/compare/0.17.2...HEAD +[0.17.2]: https://github.com/kayjan/bigtree/compare/0.17.1...0.17.2 [0.17.1]: https://github.com/kayjan/bigtree/compare/0.17.0...0.17.1 [0.17.0]: https://github.com/kayjan/bigtree/compare/0.16.4...0.17.0 [0.16.4]: https://github.com/kayjan/bigtree/compare/0.16.3...0.16.4 diff --git a/bigtree/__init__.py b/bigtree/__init__.py index c595b33b..810fb7be 100644 --- a/bigtree/__init__.py +++ b/bigtree/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.17.1" +__version__ = "0.17.2" from bigtree.binarytree.construct import list_to_binarytree from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag diff --git a/bigtree/utils/assertions.py b/bigtree/utils/assertions.py index 76237bd2..71526808 100644 --- a/bigtree/utils/assertions.py +++ b/bigtree/utils/assertions.py @@ -206,7 +206,7 @@ def isnull(value: Any) -> bool: """ import math - if not value or (isinstance(value, float) and math.isnan(value)): + if value is None or (isinstance(value, float) and math.isnan(value)): return True return False diff --git a/tests/dag/test_construct.py b/tests/dag/test_construct.py index d73b3146..66ea14f9 100644 --- a/tests/dag/test_construct.py +++ b/tests/dag/test_construct.py @@ -180,6 +180,42 @@ def test_dataframe_to_dag_reverse(): assert_dag_structure_root(dag) assert_dag_structure_root_attr(dag) + @staticmethod + def test_dataframe_to_dag_zero_attribute(): + from bigtree.utils.iterators import dag_iterator + + data = pd.DataFrame( + [ + ["a", None, 0], + ["b", None, None], + ["c", "a", -1], + ["c", "b", -1], + ["d", "a", 40], + ["d", "c", 40], + ["e", "d", 35], + ["f", "c", 38], + ["f", "d", 38], + ["g", "c", 10], + ["h", "g", 6], + ], + columns=["child", "parent", "value"], + ) + dag = dataframe_to_dag(data) + assert_dag_structure_root(dag) + for parent, _ in dag_iterator(dag): + match parent.name: + case "a": + assert hasattr( + parent, "value" + ), "Check a attribute, expected value attribute" + assert parent.value == 0, "Check a value, expected 0" + case "b": + assert not hasattr( + parent, "value" + ), "Check b attribute, expected no value attribute" + case "c": + assert parent.value == -1, "Check c value, expected -1" + def test_dataframe_to_dag_empty_row_error(self): with pytest.raises(ValueError) as exc_info: dataframe_to_dag(pd.DataFrame(columns=["child", "parent", "age"])) diff --git a/tests/tree/test_construct.py b/tests/tree/test_construct.py index ed30920a..06431024 100644 --- a/tests/tree/test_construct.py +++ b/tests/tree/test_construct.py @@ -616,6 +616,32 @@ def test_add_dataframe_to_tree_by_path_col_name(self): assert_tree_structure_node_root(self.root) def test_add_dataframe_to_tree_by_path_col_name_reverse(self): + data = pd.DataFrame( + [ + ["a", 0], + ["a/b", None], + ["a/c", -1], + ["a/b/d", 40], + ["a/b/e", 35], + ["a/c/f", 38], + ["a/b/e/g", 10], + ["a/b/e/h", 6], + ], + columns=["PATH", "value"], + ) + add_dataframe_to_tree_by_path(self.root, data) + assert_tree_structure_basenode_root(self.root) + assert_tree_structure_node_root(self.root) + assert hasattr( + self.root, "value" + ), "Check root attribute, expected value attribute" + assert self.root.value == 0, "Check root value, expected 0" + assert not hasattr( + self.root["b"], "value" + ), "Check b attribute, expected no value attribute" + assert self.root["c"].value == -1, "Check c value, expected -1" + + def test_add_dataframe_to_tree_by_path_zero_attribute(self): add_dataframe_to_tree_by_path( self.root, self.data[["age", "PATH"]], @@ -1025,6 +1051,32 @@ def test_add_dataframe_to_tree_by_name_col_name_reverse(self): assert_tree_structure_basenode_root_attr(self.root) assert_tree_structure_node_root(self.root) + def test_add_dataframe_to_tree_by_name_zero_attribute(self): + data = pd.DataFrame( + [ + ["a", 0], + ["b", None], + ["c", -1], + ["d", 40], + ["e", 35], + ["f", 38], + ["g", 10], + ["h", 6], + ], + columns=["NAME", "value"], + ) + add_dataframe_to_tree_by_name(self.root, data) + assert_tree_structure_basenode_root(self.root) + assert_tree_structure_node_root(self.root) + assert hasattr( + self.root, "value" + ), "Check root attribute, expected value attribute" + assert self.root.value == 0, "Check root value, expected 0" + assert not hasattr( + self.root["b"], "value" + ), "Check b attribute, expected no value attribute" + assert self.root["c"].value == -1, "Check c value, expected -1" + def test_add_dataframe_to_tree_by_name_empty_error(self): with pytest.raises(ValueError) as exc_info: add_dataframe_to_tree_by_name(self.root, pd.DataFrame()) @@ -2075,6 +2127,30 @@ def test_dataframe_to_tree_no_attribute(): root = dataframe_to_tree(path_data) assert_tree_structure_basenode_root(root) + @staticmethod + def test_dataframe_to_tree_zero_attribute(): + path_data = pd.DataFrame( + [ + ["a", 0], + ["a/b", None], + ["a/c", -1], + ["a/b/d", 1], + ["a/b/e", 1], + ["a/c/f", 1], + ["a/b/e/g", 1], + ["a/b/e/h", 1], + ], + columns=["PATH", "value"], + ) + root = dataframe_to_tree(path_data) + assert_tree_structure_basenode_root(root) + assert hasattr(root, "value"), "Check root attribute, expected value attribute" + assert root.value == 0, "Check root value, expected 0" + assert not hasattr( + root["b"], "value" + ), "Check b attribute, expected no value attribute" + assert root["c"].value == -1, "Check c value, expected -1" + @staticmethod def test_dataframe_to_tree_empty_row_error(): path_data = pd.DataFrame(columns=["PATH", "age"]) @@ -2427,6 +2503,31 @@ def test_dataframe_to_tree_by_relation_col_name_reverse(self): assert_tree_structure_basenode_root_attr(root) assert_tree_structure_node_root(root) + @staticmethod + def test_dataframe_to_tree_by_relation_zero_attribute(): + relation_data = pd.DataFrame( + [ + ["a", None, 0], + ["b", "a", None], + ["c", "a", -1], + ["d", "b", 40], + ["e", "b", 35], + ["f", "c", 38], + ["g", "e", 10], + ["h", "e", 6], + ], + columns=["child", "parent", "value"], + ) + root = dataframe_to_tree_by_relation(relation_data) + assert_tree_structure_basenode_root(root) + assert_tree_structure_node_root(root) + assert hasattr(root, "value"), "Check root attribute, expected value attribute" + assert root.value == 0, "Check root value, expected 0" + assert not hasattr( + root["b"], "value" + ), "Check b attribute, expected no value attribute" + assert root["c"].value == -1, "Check c value, expected -1" + @staticmethod def test_dataframe_to_tree_by_relation_empty_row_error(): relation_data = pd.DataFrame(columns=["child", "parent"]) diff --git a/tests/tree/test_export.py b/tests/tree/test_export.py index c7556ea2..5d3535a3 100644 --- a/tests/tree/test_export.py +++ b/tests/tree/test_export.py @@ -119,7 +119,7 @@ def test_print_tree_attr_omit_null_true(tree_node_negative_null_attr): "│ └── e\n" "│ ├── g [age=10]\n" "│ └── h\n" - "└── c\n" + "└── c [age=0]\n" " └── f\n" ) assert_print_statement(