π Tree DemonstrationΒΆ
Here are some codes to get started.
Construct TreeΒΆ
Nodes can have attributes if they are initialized from Node, dictionary, or pandas DataFrame.
1. From NodeΒΆ
Nodes can be linked to each other in the following ways:
Using
parentandchildrensetter methodsDirectly passing
parentorchildrenargumentUsing bitshift operator with the convention
parent >> childorchild << parentUsing
.append(child)or.extend([child1, child2])methods
from bigtree import Node, tree_to_dot
root = Node("a")
b = Node("b")
c = Node("c")
d = Node("d")
root.children = [b, c]
d.parent = b
root.show()
# a
# βββ b
# β βββ d
# βββ c
root.hshow()
# ββ b βββ d
# β a ββ€
# ββ c
graph = tree_to_dot(root, node_colour="gold")
graph.write_png("assets/demo/tree.png")

from bigtree import Node
root = Node("a")
b = Node("b")
c = Node("c")
d = Node("d")
root >> b
root >> c
d << b
root.show()
# a
# βββ b
# β βββ d
# βββ c
Alternatively, we can directly pass parent or children argument.
from bigtree import Node
b = Node("b")
c = Node("c")
d = Node("d", parent=b)
root = Node("a", children=[b, c])
root.show(style="ascii")
# a
# |-- b
# | +-- d
# +-- c
2. From strΒΆ
Construct nodes only. Newick string notation supports parsing attributes.
from bigtree import str_to_tree, newick_to_tree
tree_str = """
a
βββ b
β βββ d
β βββ e
β βββ g
β βββ h
βββ c
βββ f
"""
root = str_to_tree(tree_str)
root.show()
# a
# βββ b
# β βββ d
# β βββ e
# β βββ g
# β βββ h
# βββ c
# βββ f
newick_str = "((d,(g,h)e)b,(f)c)a"
root = newick_to_tree(newick_str)
root.show()
# a
# βββ b
# β βββ d
# β βββ e
# β βββ g
# β βββ h
# βββ c
# βββ f
3. From listΒΆ
Construct nodes only, list can contain either full paths or tuples of parent-child names.
from bigtree import list_to_tree, list_to_tree_by_relation
root = list_to_tree(["a/b/d", "a/c"])
root.show()
# a
# βββ b
# β βββ d
# βββ c
root = list_to_tree_by_relation([("a", "b"), ("a", "c"), ("b", "d")])
root.show()
# a
# βββ b
# β βββ d
# βββ c
4. From nested dictionaryΒΆ
Construct nodes using path where key is path and value is dict of node attribute names and attribute values.
Dictionary can also be a recursive structure where key is node attribute names and value is node attribute values,
and list of children (recursive).
from bigtree import dict_to_tree, nested_dict_to_tree
path_dict = {
"a": {"age": 90},
"a/b": {"age": 65},
"a/c": {"age": 60},
"a/b/d": {"age": 40},
}
root = dict_to_tree(path_dict)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# β βββ d [age=40]
# βββ c [age=60]
path_dict = {
"name": "a",
"age": 90,
"children": [
{
"name": "b",
"age": 65,
"children": [
{"name": "d", "age": 40},
],
},
{"name": "c", "age": 60},
],
}
root = nested_dict_to_tree(path_dict)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# β βββ d [age=40]
# βββ c [age=60]
5. From pandas DataFrameΒΆ
Construct nodes with attributes, pandas DataFrame can contain either path column or parent-child columns, and attribute columns.
import pandas as pd
from bigtree import dataframe_to_tree, dataframe_to_tree_by_relation
data = pd.DataFrame(
[
["a", 90],
["a/b", 65],
["a/c", 60],
["a/b/d", 40],
],
columns=["path", "age"],
)
root = dataframe_to_tree(data)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# β βββ d [age=40]
# βββ c [age=60]
data = pd.DataFrame(
[
["a", None, 90],
["b", "a", 65],
["c", "a", 60],
["d", "b", 40],
],
columns=["child", "parent", "age"],
)
root = dataframe_to_tree_by_relation(data)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# β βββ d [age=40]
# βββ c [age=60]
If tree is already created, nodes can still be added using path string, dictionary, and pandas DataFrame! Attributes can be added to existing nodes using a dictionary or pandas DataFrame.
Print TreeΒΆ
After tree is constructed, it can be viewed by printing to console using show or hshow method directly,
for vertical and horizontal orientation respectively.
Alternatively, the print_tree or hprint_tree method can be used.
from bigtree import Node, print_tree, hprint_tree
root = Node("a", age=90, gender="F")
b = Node("b", age=65, gender="M", parent=root)
c = Node("c", age=60, gender="M", parent=root)
d = Node("d", age=40, gender="F", parent=b)
e = Node("e", age=35, gender="M", parent=b)
print_tree(root)
# a
# βββ b
# β βββ d
# β βββ e
# βββ c
hprint_tree(root)
# ββ d
# ββ b ββ€
# β a ββ€ ββ e
# ββ c
# Print subtree
print_tree(root, node_name_or_path="b")
# b
# βββ d
# βββ e
print_tree(root, max_depth=2)
# a
# βββ b
# βββ c
# Print attributes
print_tree(root, attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# β βββ d [age=40]
# β βββ e [age=35]
# βββ c [age=60]
print_tree(root, attr_list=["age"], attr_bracket=["*(", ")"])
# a *(age=90)
# βββ b *(age=65)
# β βββ d *(age=40)
# β βββ e *(age=35)
# βββ c *(age=60)
print_tree(root, all_attrs=True)
# a [age=90, gender=F]
# βββ b [age=65, gender=M]
# β βββ d [age=40, gender=F]
# β βββ e [age=35, gender=M]
# βββ c [age=60, gender=M]
# Available styles
print_tree(root, style="ansi")
# a
# |-- b
# | |-- d
# | `-- e
# `-- c
print_tree(root, style="ascii")
# a
# |-- b
# | |-- d
# | +-- e
# +-- c
print_tree(root, style="const")
# a
# βββ b
# β βββ d
# β βββ e
# βββ c
print_tree(root, style="const_bold")
# a
# β£ββ b
# β β£ββ d
# β βββ e
# βββ c
print_tree(root, style="rounded")
# a
# βββ b
# β βββ d
# β β°ββ e
# β°ββ c
print_tree(root, style="double")
# a
# β ββ b
# β β ββ d
# β βββ e
# βββ c
print_tree(
root,
style="custom",
custom_style=("β ", "ββ ", "β°β "),
)
# a
# ββ b
# β ββ d
# β β°β e
# β°β c
Tree Attributes and OperationsΒΆ
Note that using BaseNode or Node as superclass inherits the default class attributes (properties)
and operations (methods).
from bigtree import str_to_tree
# Initialize tree
tree_str = """
a
βββ b
β βββ d
β βββ e
β βββ f
β βββ h
β βββ i
βββ c
βββ g
"""
root = str_to_tree(tree_str)
# Accessing children
node_b = root["b"]
node_e = root["b"]["e"]
Below are the tables of attributes available to BaseNode and Node classes.
Attributes wrt self |
Code |
Returns |
|---|---|---|
Check if root |
|
True |
Check if leaf node |
|
False |
Check depth of node |
|
2 |
Check depth of tree |
|
4 |
Get root of tree |
|
Node(/a, ) |
Get node path |
|
(Node(/a, ), Node(/a/b, )) |
Get node name (only for |
|
βbβ |
Get node path name (only for |
|
β/a/bβ |
Attributes wrt structure |
Code |
Returns |
|---|---|---|
Get child/children |
|
(Node(/a/b, ), Node(/a/c, )) |
Get parent |
|
Node(/a/b, ) |
Get siblings |
|
(Node(/a/b/d, ), Node(/a/b/f, )) |
Get left sibling |
|
Node(/a/b/d, ) |
Get right sibling |
|
Node(/a/b/f, ) |
Get ancestors (lazy evaluation) |
|
[Node(/a/b, ), Node(/a, )] |
Get descendants (lazy evaluation) |
|
[Node(/a/b/d, ), Node(/a/b/e, ), Node(/a/b/f, ), Node(/a/b/f/h, ), Node(/a/b/f/i, )] |
Get leaves (lazy evaluation) |
|
[Node(/a/b/d, ), Node(/a/b/e, ), Node(/a/b/f/h, ), Node(/a/b/f/i, )] |
Below is the table of operations available to BaseNode and Node classes.
Operations |
Code |
Returns |
|---|---|---|
Visualize tree (only for |
|
None |
Visualize tree (horizontally) (only for |
|
None |
Get node information |
|
[(βnameβ, βaβ)] |
Find path from one node to another |
|
[Node(/a, ), Node(/a/b, ), Node(/a/b/e, )] |
Add child to node |
|
None |
Add multiple children to node |
|
None |
Set attribute(s) |
|
None |
Get attribute |
|
βroot-tagβ |
Copy tree |
|
None |
Sort children |
|
None |
Traverse TreeΒΆ
Tree can be traversed using the following traversal methods.
from bigtree import (
Node,
levelorder_iter,
levelordergroup_iter,
postorder_iter,
preorder_iter,
zigzag_iter,
zigzaggroup_iter,
)
root = Node("a")
b = Node("b", parent=root)
c = Node("c", parent=root)
d = Node("d", parent=b)
e = Node("e", parent=b)
root.show()
# a
# βββ b
# β βββ d
# β βββ e
# βββ c
[node.name for node in preorder_iter(root)]
# ['a', 'b', 'd', 'e', 'c']
[node.name for node in postorder_iter(root)]
# ['d', 'e', 'b', 'c', 'a']
[node.name for node in levelorder_iter(root)]
# ['a', 'b', 'c', 'd', 'e']
[[node.name for node in node_group] for node_group in levelordergroup_iter(root)]
# [['a'], ['b', 'c'], ['d', 'e']]
[node.name for node in zigzag_iter(root)]
# ['a', 'c', 'b', 'd', 'e']
[[node.name for node in node_group] for node_group in zigzaggroup_iter(root)]
# [['a'], ['c', 'b'], ['d', 'e']]
Modify TreeΒΆ
Nodes can be shifted (with or without replacement) or copied from one path to another, changes the tree in-place.
from bigtree import list_to_tree, shift_nodes, shift_and_replace_nodes
root = list_to_tree(["Downloads/Pictures", "Downloads/photo1.jpg", "Downloads/file1.doc"])
root.show()
# Downloads
# βββ Pictures
# βββ photo1.jpg
# βββ file1.doc
shift_nodes(
tree=root,
from_paths=["photo1.jpg", "Downloads/file1.doc"],
to_paths=["Downloads/Pictures/photo1.jpg", "Downloads/Files/file1.doc"],
)
root.show()
# Downloads
# βββ Pictures
# β βββ photo1.jpg
# βββ Files
# βββ file1.doc
shift_and_replace_nodes(
tree=root,
from_paths=["Downloads/Files"],
to_paths=["Downloads/Pictures/photo1.jpg"],
)
root.show()
# Downloads
# βββ Pictures
# βββ Files
# βββ file1.doc
from bigtree import list_to_tree, copy_nodes
root = list_to_tree(["Downloads/Pictures", "Downloads/photo1.jpg", "Downloads/file1.doc"])
root.show()
# Downloads
# βββ Pictures
# βββ photo1.jpg
# βββ file1.doc
copy_nodes(
tree=root,
from_paths=["photo1.jpg", "Downloads/file1.doc"],
to_paths=["Downloads/Pictures/photo1.jpg", "Downloads/Files/file1.doc"],
)
root.show()
# Downloads
# βββ Pictures
# β βββ photo1.jpg
# βββ photo1.jpg
# βββ file1.doc
# βββ Files
# βββ file1.doc
Nodes can also be copied (with or without replacement) between two different trees.
from bigtree import Node, copy_nodes_from_tree_to_tree, copy_and_replace_nodes_from_tree_to_tree, list_to_tree
root = list_to_tree(["Downloads/Pictures", "Downloads/photo1.jpg", "Downloads/file1.doc"])
root.show()
# Downloads
# βββ Pictures
# βββ photo1.jpg
# βββ file1.doc
root_other = Node("Documents")
copy_nodes_from_tree_to_tree(
from_tree=root,
to_tree=root_other,
from_paths=["Downloads/Pictures", "photo1.jpg", "file1.doc"],
to_paths=["Documents/Pictures", "Documents/Pictures/photo1.jpg", "Documents/Files/file1.doc"],
)
root_other.show()
# Documents
# βββ Pictures
# β βββ photo1.jpg
# βββ Files
# βββ file1.doc
root_other = Node("Documents")
picture_folder = Node("Pictures", parent=root_other)
photo2 = Node("photo2.jpg", parent=picture_folder)
file2 = Node("file2.doc", parent=root_other)
root_other.show()
# Documents
# βββ Pictures
# β βββ photo2.jpg
# βββ file2.doc
copy_and_replace_nodes_from_tree_to_tree(
from_tree=root,
to_tree=root_other,
from_paths=["Downloads/photo1.jpg", "Downloads/file1.doc"],
to_paths=["Documents/Pictures/photo2.jpg", "Documents/file2.doc"],
)
root_other.show()
# Documents
# βββ Pictures
# β βββ photo1.jpg
# βββ file1.doc
Tree SearchΒΆ
One or multiple nodes can be searched based on name, path, attribute value, or user-defined condition.
To find a single node:
from bigtree import Node, find, find_name, find_path, find_relative_path, find_full_path, find_attr
root = Node("a", age=90)
b = Node("b", age=65, parent=root)
c = Node("c", age=60, parent=root)
d = Node("d", age=40, parent=c)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# βββ c [age=60]
# βββ d [age=40]
find(root, lambda node: node.age == 60)
# Node(/a/c, age=60)
find_name(root, "d")
# Node(/a/c/d, age=40)
find_relative_path(c, "../b") # relative path
# (Node(/a/b, age=65),)
find_path(root, "/c/d") # partial path
# Node(/a/c/d, age=40)
find_full_path(root, "a/c/d") # full path
# Node(/a/c/d, age=40)
find_attr(root, "age", 40)
# Node(/a/c/d, age=40)
To find multiple nodes:
from bigtree import Node, findall, find_names, find_relative_path, find_paths, find_attrs
root = Node("a", age=90)
b = Node("b", age=65, parent=root)
c = Node("c", age=60, parent=root)
d = Node("c", age=40, parent=c)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# βββ c [age=60]
# βββ c [age=40]
findall(root, lambda node: node.age >= 65)
# (Node(/a, age=90), Node(/a/b, age=65))
find_names(root, "c")
# (Node(/a/c, age=60), Node(/a/c/c, age=40))
find_relative_path(c, "../*") # relative path
# (Node(/a/b, age=65), Node(/a/c, age=60))
find_paths(root, "/c") # partial path
# (Node(/a/c, age=60), Node(/a/c/c, age=40))
find_attrs(root, "age", 40)
# (Node(/a/c/c, age=40),)
It is also possible to search for one or more child node(s) based on attributes, and the search will be faster as this does not require traversing the whole tree to find the node(s).
from bigtree import Node, find_children, find_child, find_child_by_name
root = Node("a", age=90)
b = Node("b", age=65, parent=root)
c = Node("c", age=60, parent=root)
d = Node("c", age=40, parent=c)
root.show(attr_list=["age"])
# a [age=90]
# βββ b [age=65]
# βββ c [age=60]
# βββ c [age=40]
find_children(root, lambda node: node.age >= 60)
# (Node(/a/b, age=65), Node(/a/c, age=60))
find_child(root, lambda node: node.name == "c")
# Node(/a/c, age=60)
find_child_by_name(root, "c")
# Node(/a/c, age=60)
find_child_by_name(c, "c")
# Node(/a/c/c, age=40)
Helper UtilityΒΆ
There following are helper functions for
1. Cloning tree to another Node typeΒΆ
from bigtree import BaseNode, Node, clone_tree
# Cloning tree from `BaseNode` to `Node` type
root = BaseNode(name="a")
b = BaseNode(name="b", parent=root)
clone_tree(root, Node)
# Node(/a, )
2. Getting subtree (smaller tree with different root)ΒΆ
from bigtree import str_to_tree, get_subtree
root = str_to_tree("""
a
βββ b
β βββ d
β βββ e
βββ c
βββ f
""")
# Getting subtree with root b
root_subtree = get_subtree(root, "b")
root_subtree.show()
# b
# βββ d
# βββ e
3. Pruning tree (smaller tree with same root)ΒΆ
from bigtree import str_to_tree, prune_tree
root = str_to_tree("""
a
βββ b
β βββ d
β βββ e
βββ c
βββ f
""")
# Prune tree to only path a/b
root_pruned = prune_tree(root, "a/b")
root_pruned.show()
# a
# βββ b
# βββ d
# βββ e
# Prune tree to exactly path a/b
root_pruned = prune_tree(root, "a/b", exact=True)
root_pruned.show()
# a
# βββ b
4. Getting difference between two treesΒΆ
from bigtree import str_to_tree, get_tree_diff
root = str_to_tree("""
a
βββ b
β βββ d
β βββ e
βββ c
βββ f
""")
# Get difference between two trees
root_other = str_to_tree("""
a
βββ b
β βββ d
βββ c
βββ g
""")
tree_diff = get_tree_diff(root, root_other)
tree_diff.show()
# a
# βββ b
# β βββ e (-)
# βββ c
# βββ f (-)
# βββ g (+)
tree_diff = get_tree_diff(root, root_other, only_diff=False)
tree_diff.show()
# a
# βββ b
# β βββ d
# β βββ e (-)
# βββ c
# βββ f (-)
# βββ g (+)
Export TreeΒΆ
Tree can be exported to another data type.
from bigtree import Node
root = Node("a", age=90)
b = Node("b", age=65, parent=root)
c = Node("c", age=60, parent=root)
d = Node("d", age=40, parent=b)
e = Node("e", age=35, parent=b)
root.show()
# a
# βββ b
# β βββ d
# β βββ e
# βββ c
1. Export to Newick string notationΒΆ
from bigtree import tree_to_newick
tree_to_newick(root)
# '((d,e)b,c)a'
2. Export to nested dictionaryΒΆ
from bigtree import tree_to_dict
tree_to_dict(
root,
name_key="name",
parent_key="parent",
attr_dict={"age": "person age"}
)
# {
# '/a': {'name': 'a', 'parent': None, 'person age': 90},
# '/a/b': {'name': 'b', 'parent': 'a', 'person age': 65},
# '/a/b/d': {'name': 'd', 'parent': 'b', 'person age': 40},
# '/a/b/e': {'name': 'e', 'parent': 'b', 'person age': 35},
# '/a/c': {'name': 'c', 'parent': 'a', 'person age': 60}
# }
3. Export to nested recursive dictionaryΒΆ
from bigtree import tree_to_nested_dict
tree_to_nested_dict(root, all_attrs=True)
# {
# 'name': 'a',
# 'age': 90,
# 'children': [
# {
# 'name': 'b',
# 'age': 65,
# 'children': [
# {
# 'name': 'd',
# 'age': 40
# },
# {
# 'name': 'e',
# 'age': 35
# }
# ]
# },
# {
# 'name': 'c',
# 'age': 60
# }
# ]
# }
4. Export to pandas DataFrameΒΆ
from bigtree import tree_to_dataframe
tree_to_dataframe(
root,
name_col="name",
parent_col="parent",
path_col="path",
attr_dict={"age": "person age"}
)
# path name parent person age
# 0 /a a None 90
# 1 /a/b b a 65
# 2 /a/b/d d b 40
# 3 /a/b/e e b 35
# 4 /a/c c a 60
5. Export to dot (and png)ΒΆ
from bigtree import tree_to_dot
graph = tree_to_dot(root, node_colour="gold")
graph.write_png("assets/demo/dot.png")
dot.png

6. Export to Pillow (and png)ΒΆ
from bigtree import tree_to_pillow
pillow_image = tree_to_pillow(root)
pillow_image.save("assets/demo/pillow.png")
pillow.png

7. Export to Mermaid Flowchart (and md)ΒΆ
from bigtree import tree_to_mermaid
mermaid_md = tree_to_mermaid(root)
print(mermaid_md)
Mermaid flowchart
%%{ init: { 'flowchart': { 'curve': 'basis' } } }%%
flowchart TB
0("a") --> 0-0("b")
0-0 --> 0-0-0("d")
0-0 --> 0-0-1("e")
0("a") --> 0-1("c")
classDef default stroke-width:1