Tree

class baltic.tree.Tree(treeType)

Represent a phylogenetic tree and provide traversal, manipulation, and plotting helpers.

Initialize an empty baltic tree.

A Tree stores baltic.branchLike.BranchLike descendants such as baltic.node.Node and baltic.leaf.Leaf in a flat Objects list alongside a root reference.

The tree starts empty: root is None and Objects is empty, while curNode holds a placeholder node that add_node() and add_leaf() attach the first branches to. Most users build trees with baltic.baltic.make_tree() or the loaders in baltic.io rather than constructing one directly.

Parameters

treeType{“divergence”, “time”}

Interpretation of branch lengths in the tree. "divergence" makes height authoritative, "time" makes absoluteTime authoritative.

Returns

None

Raises

AssertionError

If treeType is neither "divergence" nor "time".

Examples

>>> import baltic as bt
>>> ll = bt.Tree("divergence")
>>> ll.treeType
'divergence'
add_leaf(i, name)

Create a new Leaf with appropriate parent-child links; add it to the tree.

Parameters

iint

Unique index into the tree.

namestr

Name of the new leaf.

Notes

If the tree does not have a root (i.e. a tree with no branches), then the new leaf will be set as the root of the tree.

After the new leaf is added, self.curNode will update to the newly created leaf. Because a leaf cannot take children, adding another branch requires reassigning self.curNode first.

Returns

None

The tree is modified in place.

Raises

TypeError

If the current node of the tree to which the new leaf will be added is not itself a valid node (e.g. if self.curNode is a Leaf).

Examples

>>> import baltic as bt
>>> ll = bt.Tree(treeType="divergence")
>>> ll.add_node(1)
>>> ll.add_leaf(2, "LeafA")
>>> ll.root.index
1
>>> ll.curNode.index
2
>>> ll.curNode.name
'LeafA'
add_node(i)

Create a new internal Node with appropriate parent-child links; add it to the tree.

Parameters

iint

Unique index into the tree.

Notes

If the tree does not have a .root (i.e. a tree with no branches), then the new node will be set as the root of the tree.

After the new node is added, self.curNode will update to the newly created node.

Returns

None

The tree is modified in place.

Raises

TypeError

If the current node of the tree to which the new node will be added is not itself a valid node (e.g. if self.curNode is a Leaf).

Examples

>>> import baltic as bt
>>> ll = bt.Tree(treeType="divergence")
>>> ll.add_node(1)
>>> ll.add_node(2)
>>> print(ll.root.index)
1
>>> print(ll.curNode.index)
2
add_reticulation(name)

Create and attach a new reticulation edge below the current node.

This creates a baltic.reticulation.Reticulation.

Notes

The new reticulation’s index is set to name rather than to an integer, and self.curNode moves onto it. Since a reticulation is leaf-like, the next add_node() or add_leaf() would try to attach a child to it and raise TypeError; reassign self.curNode first.

Parameters

namestr

Name assigned to the new reticulation object. Also used as its index.

Returns

None

The tree is modified in place.

Examples

>>> import baltic as bt
>>> ll = bt.Tree("divergence")
>>> ll.add_node(1)
>>> ll.add_reticulation("ret1")
>>> ll.curNode.name
'ret1'
collapse_branches(collapseIfFxn=<function Tree.<lambda>>, designatedNodes=[])

Collapse branches according to a determined function, creating polytomies.

Unlike most manipulation methods, this does not modify the tree: it works on a deep copy and returns that, leaving the original untouched. A collapsed node’s children are reattached to its parent and its branch length is added to each of theirs, so tree length is preserved. Collapsing is repeated until no node satisfies the condition.

The tree must have been traversed first, since node heights order the collapsing.

Parameters

collapseIfFxncallable, optional

Function deciding whether a node should be collapsed. By default, branches with posterior support at or below 0.5 are collapsed (lambda x: x.traits["posterior"] <= 0.5), which raises KeyError on a tree whose nodes carry no posterior trait – pass an explicit function for non-BEAST trees.

designatedNodeslist[Node], optional

Explicit list of nodes to collapse instead of using collapseIfFxn.

Warning

This parameter does not currently work. Its guard compares the nodes you pass against the root of the internal deep copy, which is never the same object, so any non-empty list raises AssertionError: Root node was designated for deletion. Use collapseIfFxn – for example lambda n: n.index in {...} – until this is fixed.

Returns

Tree

A new, collapsed tree. The original is unchanged.

Raises

AssertionError

If the requested collapsing would remove every internal branch, or if designatedNodes is non-empty (see the warning above).

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> for node in ll.get_internal():
...     node.traits["posterior"] = 1.0
>>> ll.find_MRCA("A", "B").traits["posterior"] = 0.0
>>> collapsed = ll.collapse_branches(collapseIfFxn=lambda n: n.traits["posterior"] <= 0.5)
>>> len(collapsed.get_internal()) < len(ll.get_internal())
True
collapse_subtree_to_clade(cl, givenName, widthFunction=<function Tree.<lambda>>)

Replace a subtree with a collapsed Clade placeholder.

The subtree’s branches are removed from the tree and stored on the clade, so the operation is reversible with restore_all_collapsed_subtrees(). The clade inherits the node’s index, length, height, absolute time and traits; the tree is re-traversed and re-sorted before returning. If the tree has a tipMap, an entry is added for the new clade.

Requires a traversed tree, since the clade’s width and descendant set come from the node’s leaves.

Parameters

clNode

Root of the subtree to collapse. Must be an internal node.

givenNamestr

Name assigned to the collapsed clade object.

widthFunctioncallable, optional

Function computing the visual width of the collapsed clade. By default the number of descendant tips is used.

Returns

Clade

The newly created collapsed clade. The tree itself is modified in place.

Raises

AssertionError

If cl is not an internal node, or if collapsing it would consume the entire tree.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> cl = ll.collapse_subtree_to_clade(ll.find_MRCA("A", "B"), "AB clade")
>>> cl.name
'AB clade'
condense_tree(cutoffs=None, protectedTips=[], widthFxn=None)

Collapse eligible subtrees into clade placeholders.

Each qualifying node is replaced via collapse_subtree_to_clade() and named "collapsed clade <n>". Only the outermost qualifying nodes are collapsed, so clades are never nested. The tree is modified in place, and the collapsing is reversible with restore_all_collapsed_subtrees().

Requires a traversed tree, since eligibility is decided from each node’s leaves.

Parameters

cutoffstuple, optional

Inclusive (min_size, max_size) bounds, in descendant tips, for collapsible clades. By default (3, int(0.2 * number_of_tips)).

Note

On a tree with fewer than 15 tips that default makes the upper bound smaller than the lower one, so nothing qualifies and the call is a silent no-op. Pass cutoffs explicitly for small trees.

protectedTipslist, optional

Tip names that must remain outside collapsed clades. Any node whose descendants include one of these is skipped.

widthFxncallable, optional

Function computing the visual width of each collapsed clade. By default a clade occupies as much vertical space as its tip count.

Returns

Tree

The same tree object, modified in place. The return value is a convenience for chaining, not a copy.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0):1.0,E:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> _ = ll.condense_tree(cutoffs=(2, 3))
>>> any(branch.__class__.__name__ == "Clade" for branch in ll.Objects)
True
count_lineages_at_time(t, timeAttr='absoluteTime', inclusionConditionFxn=<function Tree.<lambda>>)

Count branches spanning a given time value.

A branch is counted when parent_time < t <= branch_time, so the interval is open at the parent’s end and closed at the branch’s own. A branch whose parent has no time assigned is skipped, which excludes the root.

This is typically used after set_absolute_time().

Parameters

tfloat

Time point at which to count extant lineages.

timeAttrstr, default=”absoluteTime”

Branch attribute to use as the time coordinate. Pass "height" to count lineages on a divergence tree.

inclusionConditionFxncallable, optional

Predicate selecting which branches contribute to the count. By default every spanning branch counts.

Returns

int

Number of matching lineages.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:2.0);", treeType="time")
>>> _ = ll.traverse_tree()
>>> ll.set_absolute_time(2020.0)
>>> ll.count_lineages_at_time(2019.5)
3
>>> ll.count_lineages_at_time(2018.5)
2
explode_tree(trait=None, customFxn=None, stem=True)

Split a tree into subtrees at trait transitions or custom breakpoints.

A new subtree starts at the root and at every branch the split rule marks; each is extracted with subtree(), so the results are deep copies and editing them does not affect this tree.

Exactly one of trait or customFxn must be given.

Parameters

traitstr, optional

Trait name used to define subtree boundaries when its value differs from the parent’s. Raises KeyError if any branch lacks the trait.

customFxncallable, optional

Custom predicate that marks branches starting new subtrees.

stembool, default=True

If True, include the stem branch leading into each extracted subtree.

Returns

list[Tree]

Extracted subtrees. A split point whose subtree contains no leaves is logged as an error and dropped, so this can be shorter than the number of branches the rule marked.

Raises

ValueError

If both trait and customFxn are given, or neither.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> subtrees = ll.explode_tree(customFxn=lambda k: k.is_node() and len(k.leaves) == 3, stem=False)
>>> len(subtrees)
2
find_MRCA(*descendants)

Find the most recent common ancestor of a list of descendant nodes.

Descendants may be passed as separate arguments, as a single list, as tip name strings, or as branch objects – but strings and branches cannot be mixed in one call.

Parameters

*descendantsBranchLike or str

Descendant branches or tip names whose MRCA is being searched. A single list argument is unpacked. Mixing strings and branch objects raises AttributeError.

Returns

BranchLike or None

The most recent common ancestor: a Node in the usual case, but the branch itself when only one descendant is given, and None when no descendants are given at all.

Raises

AssertionError

If any tip name cannot be found in the tree.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> mrca = ll.find_MRCA("A", "B")
>>> sorted(mrca.leaves)
['A', 'B']
>>> ll.find_MRCA("A", "B", "C", "D") is ll.root
True
fix_hanging_nodes()

Remove internal nodes that no longer have children.

Removal repeats until no childless nodes remain, so a node orphaned by the removal of its only child is cleaned up in the same call. Hanging nodes otherwise make traverse_tree() raise AttributeError.

This cleanup step is often needed after subtree() or collapse_branches().

Returns

None

The tree is modified in place. Heights are not recomputed – call traverse_tree() afterwards if you need them current.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> hanging = ll.find_MRCA("A", "B")
>>> hanging.children = []
>>> ll.fix_hanging_nodes()
>>> hanging not in ll.Objects
True
get_all_tip_TMRCAs()

Compute the pairwise TMRCA matrix for all tips in a time tree.

Values are the absoluteTime of each pair’s common ancestor – a date on the tree’s calendar, not an elapsed duration. A tip paired with itself is 0.0 rather than its own date, and a pair sharing no common ancestor stays None.

Requires a traversed tree, since the descendant sets come from each node’s leaves, and absolute dates must already be assigned with set_absolute_time().

Returns

dict[str, dict[str, float]]

Nested dictionary keyed by tip names in both directions, so matrix[a][b] and matrix[b][a] are both populated.

Raises

AssertionError

If the tree’s treeType is not "time".

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:2.0);", treeType="time")
>>> _ = ll.traverse_tree()
>>> ll.set_absolute_time(2020.0)
>>> tmrcas = ll.get_all_tip_TMRCAs()
>>> tmrcas["A"]["B"]
2019.0
>>> tmrcas["A"]["A"]
0.0
get_branches(filterFxn=<function Tree.<lambda>>, failIfNoResults=True)

Return branches matching a predicate.

Unlike get_external() or get_internal(), this can return any baltic.branchLike.BranchLike subclass.

Parameters

filterFxncallable, optional

Predicate used to select branches. By default every branch is returned.

failIfNoResultsbool, default=True

If True, raise when nothing matches. If False, log a warning and return an empty list instead.

Returns

list[BranchLike]

Matching branches, in Objects order.

Raises

Exception

If no branch matches and failIfNoResults is True.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> selected = ll.get_branches(lambda k: getattr(k, "length", 0) >= 1.0)
>>> len(selected) >= 3
True
get_external(filterFxn=None, onlyLeaves=True)

Return external branches from the tree.

The result contains baltic.leaf.Leaf objects and, when requested, other leaf-like placeholders such as baltic.clade.Clade.

Parameters

filterFxncallable, optional

Additional predicate applied to candidate branches. By default no additional filtering is done.

onlyLeavesbool, default=True

If True, return only true leaves; otherwise include all leaf-like objects such as collapsed clades and reticulations.

Returns

list[BranchLike]

External branches satisfying the filter, in Objects order.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> [tip.name for tip in ll.get_external(lambda k: k.name != "B")]
['A', 'C']
get_internal(filterFxn=None)

Return internal nodes from the tree.

Parameters

filterFxncallable, optional

Additional predicate applied to nodes. By default every internal node is returned.

Returns

list[Node]

Internal nodes satisfying the filter, in Objects order.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> len(ll.get_internal())
2
get_leaf(tipName)

Look up a single leaf by name.

Only true leaves are searched, so collapsed clades are not found; use get_external() with onlyLeaves=False to include them.

Parameters

tipNamestr

Tip name to match. Matching is exact.

Returns

Leaf

The matching leaf.

Raises

AssertionError

If tipName is not a string, or if the number of matching tips is anything other than one. A missing name is an error rather than None, and duplicate tip names are rejected the same way.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> ll.get_leaf("B").name
'B'
get_parameter_list(statistic, useTraitsDict=False, filterFxn=None)

Collect attribute or trait values across branches.

This helper is often paired with get_branches().

Parameters

statisticstr

Attribute name or trait key to extract.

useTraitsDictbool, default=False

If True, read statistic from each branch’s traits dictionary instead of as an attribute.

filterFxncallable, optional

Predicate selecting which branches to inspect. By default every branch is inspected.

Returns

list

Extracted values. Branches that lack the attribute or trait are skipped silently, so the result can be shorter than the number of branches inspected and its entries do not line up positionally with any branch list. An unknown statistic yields an empty list rather than an error.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:2.0):1.0,C:3.0);", treeType="divergence")
>>> sorted(ll.get_parameter_list("length"))
[0.0, 1.0, 1.0, 2.0, 3.0]

Only the three tips carry a name, so the list is shorter than the five branches in the tree:

>>> sorted(ll.get_parameter_list("name")), len(ll.Objects)
(['A', 'B', 'C'], 5)
make_single_type()

Remove singleton internal nodes by merging them into their descendants.

A singleton is an internal node with exactly one child, as produced by multitype (structured-coalescent) BEAST analyses. Each is spliced out and its branch length added to the child, leaving a strictly branching tree. Requires heights, so call traverse_tree() first.

sort_branches() is called on the way out, so plotting coordinates are already refreshed when this returns.

Returns

None

The tree is modified in place.

Examples

>>> import baltic as bt
>>> ll = bt.Tree("divergence")
>>> ll.add_node(1)
>>> ll.add_node(2)
>>> ll.add_leaf(3, "A")
>>> ll.curNode = ll.root
>>> ll.add_leaf(4, "B")
>>> _ = ll.traverse_tree()
>>> ll.make_single_type()
>>> all(len(node.children) != 1 for node in ll.get_internal())
True
midpoint_root(fixSingletons=True)

Reroot the tree at the midpoint of the longest tip-to-tip path.

The longest path is found by rerooting on every tip in turn and recording the greatest resulting tip height, so the cost grows with the number of tips and the tree is repeatedly rerooted along the way. The tree is modified in place throughout; if this raises part-way, the tree is left rooted wherever the search had reached rather than in its original state. Work on a copy if that matters.

Inherits the restrictions of reroot(), so it is divergence-trees only.

Parameters

fixSingletonsbool, default=True

If True, collapse singleton nodes after rerooting. Passed through to reroot().

Returns

Tree

The same tree object, rerooted. The return value is a convenience for chaining, not a copy.

Raises

AttributeError

If the tree’s treeType is "time", via reroot().

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:3.0):1.0,C:1.0);", treeType="divergence")
>>> ll.sort_branches()
>>> _ = ll.midpoint_root()
>>> isinstance(ll.root, bt.node.Node)
True

Attribution

Adapted from Bio.Phylo.BaseTree.Tree.root_at_midpoint in Biopython. The original method was implemented by Eric Talevich and its traversal was subsequently optimized by Brandon Invergo. Modified for baltic tree and branch objects.

Copyright (C) 2009 Eric Talevich and the Biopython contributors.

The upstream file is distributed, at the recipient’s choice, under the Biopython License Agreement or the BSD 3-Clause License.

Source: https://github.com/biopython/biopython/blob/master/Bio/Phylo/BaseTree.py License: https://github.com/biopython/biopython/blob/master/LICENSE.rst

plot_aligned_tip_labels(ax, xSpace=0.005, connectingLines=True, **kwargs)

Plot tip labels aligned to a common terminal coordinate.

Labels are placed a fixed distance past the furthest tip rather than at each tip, with optional dotted guide lines connecting the two.

Warning

Rectangular and circular layouts are supported. Passing treeType="unrooted" warns and then raises UnboundLocalError while building the connecting lines; pass connectingLines=False to use an unrooted layout.

Parameters

axmatplotlib.axes.Axes

Axes on which to draw labels.

xSpacefloat, default=0.005

Extra horizontal offset, expressed as a fraction of tree height. 0 places labels level with the furthest tip, 1 a full tree height beyond it.

connectingLinesbool, default=True

If True, draw guide lines between tips and aligned labels.

**kwargsdict, optional

Additional keyword arguments forwarded to plot_text(). xCoordinateFxn and normaliseHeight are computed here and cannot be overridden – passing either warns and discards it.

Returns

matplotlib.axes.Axes

The input axes.

Examples

>>> import matplotlib.pyplot as plt
>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.5);", treeType="divergence")
>>> fig, ax = plt.subplots()
>>> ll.plot_aligned_tip_labels(ax, xSpace=0.05)
<...Axes...>
plot_exploded_tree(ax, trait=None, customFxn=None, stem=True, subtreeSortFxn=None, targetFxn=None, xCoordinateFxn=None, colour=None, colourFxn=None, tipPoints=True, originPoint=True, verticalSpace=2, width=None, widthFxn=None, pointSize=None, pointSizeFxn=None, outline=True, outlineSize=None, outlineSizeFxn=None, outlineColour=None, outlineColourFxn=None, padNodes=None, orientation='horizontal', connectionType='baltic', **kwargs)

Plot subtrees extracted by trait transitions or custom split rules.

Subtrees are generated with explode_tree(), which requires exactly one of trait or customFxn and works on deep copies, so this leaves the original tree’s structure alone.

Parameters

axmatplotlib.axes.Axes

Axes on which to draw the exploded tree view.

traitstr, optional

Trait used to determine subtree boundaries.

customFxncallable, optional

Custom predicate defining subtree boundaries.

stembool, optional

If True, include the stem branch for each extracted subtree.

subtreeSortFxncallable, optional

Function used to order subtrees vertically.

targetFxncallable, optional

Predicate selecting branches to plot within each subtree.

xCoordinateFxncallable, optional

X-coordinate function used for each subtree.

colour, colourFxn, width, widthFxn, pointSize, pointSizeFxn, outline, outlineSize, outlineSizeFxn, outlineColour, outlineColourFxnoptional

Styling controls for branches and optional points.

tipPointsbool, optional

If True, mark subtree tips.

originPointbool, optional

If True, mark each subtree root.

verticalSpacefloat, optional

Space inserted between consecutive subtrees.

padNodesdict, optional

Additional spacing applied when plotting each subtree.

orientation{“horizontal”, “vertical”}, optional

Orientation of the exploded view.

connectionType{“baltic”, “direct”, “elbow”}, optional

Branch connection style used for each subtree.

**kwargsdict, optional

Additional keyword arguments forwarded to subtree plotting calls.

Returns

matplotlib.axes.Axes

The input axes.

Raises

ValueError

If both a scalar and its *Fxn counterpart are given for any of width, point size, outline size or colour; or, via explode_tree(), if both or neither of trait and customFxn is given.

Examples

>>> import matplotlib.pyplot as plt
>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> ll.sort_branches()
>>> for branch in ll.Objects:
...     branch.traits["group"] = "left" if getattr(branch, "name", "").startswith(("A", "B")) else "right"
>>> fig, ax = plt.subplots()
>>> ll.plot_exploded_tree(ax, trait="group", colourFxn=lambda k: "steelblue")
<...Axes...>
plot_points(ax, targetFxn=None, xCoordinateFxn=None, yCoordinateFxn=None, pointSize=None, pointSizeFxn=None, colour=None, colourFxn=None, outline=True, outlineSize=None, outlineSizeFxn=None, outlineColour=None, outlineColourFxn=None, padNodes=None, treeType='rectangular', orientation='horizontal', circStart=None, circFrac=None, inwardSpace=None, normaliseHeight=None, recomputeCoordinates=True, **kwargs)

Plot markers on selected branches.

This helper complements plot_tree().

Parameters

axmatplotlib.axes.Axes

Axes on which to draw points.

targetFxncallable, optional

Predicate selecting branches to mark.

xCoordinateFxn, yCoordinateFxncallable, optional

Coordinate functions for point placement.

pointSize, pointSizeFxn, colour, colourFxn, outline, outlineSize, outlineSizeFxn, outlineColour, outlineColourFxnoptional

Marker styling controls.

padNodesdict, optional

Additional spacing used when computing coordinates.

treeType{“rectangular”, “circular”, “unrooted”}, optional

Layout in which to place markers.

orientation{“horizontal”, “vertical”}, optional

Orientation for rectangular layouts.

circStart, circFrac, inwardSpace, normaliseHeightoptional

Circular-layout controls.

recomputeCoordinatesbool, default=True

If True, recompute coordinates before plotting. This reassigns the tree’s x/y attributes, so the call modifies the tree.

**kwargsdict, optional

Additional keyword arguments forwarded to Axes.scatter.

Returns

tuple[matplotlib.axes.Axes, dict]

The input axes and a dictionary mapping each plotted branch to its (x, y) coordinates.

Examples

>>> import matplotlib.pyplot as plt
>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.5);", treeType="divergence")
>>> fig, ax = plt.subplots()
>>> ax, coords = ll.plot_points(ax, targetFxn=lambda k: k.is_leaf(), pointSize=60)
>>> sorted((k.name, xy) for k, xy in coords.items())
[('A', (2.0, 2.5)), ('B', (2.0, 1.5)), ('C', (1.5, 0.5))]
plot_text(ax, targetFxn=None, xCoordinateFxn=None, xSpace=0.005, yCoordinateFxn=None, ySpace=0.0, textContentFxn=None, colour=None, colourFxn=None, treeType='rectangular', orientation='horizontal', padNodes=None, circStart=0.0, circFrac=1.0, inwardSpace=0.0, normaliseHeight=None, cladeEndAttrFxn=None, recomputeCoordinates=True, **kwargs)

Plot text labels on branches in rectangular, circular, or unrooted layouts.

This helper is commonly used alongside plot_tree().

Parameters

axmatplotlib.axes.Axes

Axes on which to draw labels.

targetFxncallable, optional

Predicate selecting branches to label.

xCoordinateFxn, yCoordinateFxncallable, optional

Coordinate functions for label placement.

xSpace, ySpacefloat, optional

Extra spacing offsets applied to the default coordinates.

textContentFxncallable, optional

Function returning label text for each branch.

colour, colourFxnoptional

Fixed colour or callable returning a colour for each branch.

treeType{“rectangular”, “circular”, “unrooted”}, optional

Layout used for plotting labels.

orientation{“horizontal”, “vertical”}, optional

Orientation for rectangular labels.

padNodesdict, optional

Additional spacing to apply to selected subtrees.

circStart, circFrac, inwardSpace, normaliseHeightoptional

Circular-layout controls.

cladeEndAttrFxncallable, optional

Function returning the far edge of collapsed clades.

recomputeCoordinatesbool, default=True

If True, recompute tree coordinates before plotting. This reassigns the tree’s x/y attributes, so the call modifies the tree.

**kwargsdict, optional

Additional keyword arguments forwarded to Axes.text.

Returns

matplotlib.axes.Axes

The input axes.

Examples

>>> import matplotlib.pyplot as plt
>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.5);", treeType="divergence")
>>> fig, ax = plt.subplots()
>>> ll.plot_text(ax, targetFxn=lambda k: k.is_leaf())
<...Axes...>
plot_tree(ax, targetFxn=None, xCoordinateFxn=None, yCoordinateFxn=None, width=None, widthFxn=None, connectionType=None, colour=None, colourFxn=None, orientation=None, padNodes=None, treeType='rectangular', circStart=None, circFrac=None, inwardSpace=None, normaliseHeight=None, precision=None, plotClades=True, cladeColour=None, cladeEndAttrFxn=None, cladeStyle='equal', cladeShape=None, cladeBaseWidth=0.001, recomputeCoordinates=True, autoSort=True, **kwargs)

Plot the tree in rectangular, circular, or unrooted form.

Use plot_text() and plot_points() to layer labels and markers onto the same geometry.

Parameters

axmatplotlib.axes.Axes

Axes on which to draw the tree.

targetFxncallable, optional

Predicate selecting which branches to draw.

xCoordinateFxn, yCoordinateFxncallable, optional

Coordinate functions for branch endpoints.

width, widthFxn, colour, colourFxnoptional

Branch styling controls.

connectionType{“baltic”, “direct”, “elbow”}, optional

Branch connection style for rooted layouts.

orientation{“horizontal”, “vertical”}, optional

Orientation for rectangular layouts.

padNodesdict, optional

Additional spacing applied to selected subtrees.

treeType{“rectangular”, “circular”, “unrooted”}, optional

Layout to render.

circStart, circFrac, inwardSpace, normaliseHeight, precisionoptional

Circular- and unrooted-layout controls.

plotCladesbool, optional

If True, draw collapsed clades.

cladeColour, cladeEndAttrFxn, cladeStyle, cladeShape, cladeBaseWidthoptional

Styling and geometry options for collapsed clades.

recomputeCoordinatesbool, default=True

If True, recompute branch coordinates before plotting.

autoSortbool, default=True

If True, sort branches before drawing. Note this reorders the tree’s own child lists – see the note below.

**kwargsdict, optional

Additional keyword arguments forwarded to the line collection.

Returns

matplotlib.axes.Axes

The input axes.

Examples

>>> import matplotlib.pyplot as plt
>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.5);", treeType="divergence")
>>> fig, ax = plt.subplots()
>>> ll.plot_tree(ax, colourFxn=lambda k: "firebrick" if k.is_leaf() else "k")
<...Axes...>

Note

Despite being a plotting call, this modifies the tree by default: with autoSort=True it calls sort_branches(), which reorders every node’s children, and with recomputeCoordinates=True it reassigns all x/y coordinates. Pass autoSort=False to draw a tree in its existing order.

project_circular_point(x, y, circStart=0.0, circFrac=1.0, inwardSpace=0.0, normaliseHeight=None)

Project a tree-space coordinate onto a circular tree layout.

This method applies the same radial normalization and angular projection used by plot_tree(), making it suitable for placing annotations and other artists on circular tree axes.

Parameters

xfloat

Coordinate along the informative tree axis, usually height for divergence trees or absoluteTime for time trees.

yfloat

Coordinate along the non-informative tree axis.

circStartfloat, optional

Fraction of the circle at which plotting begins.

circFracfloat, optional

Fraction of the full circle used by the layout.

inwardSpacefloat, optional

Radial spacing applied before normalization. Negative values use the outward-facing convention of circular tree plots.

normaliseHeightcallable, optional

Function mapping the informative coordinate to radial distance. By default, coordinates are normalized over the tree’s height or absolute-time range.

Returns

tuple[float, float]

Cartesian coordinates in the circular tree’s data space.

Raises

ValueError

If circFrac is not positive; if the tree has no y span because coordinates were never assigned; if no branch carries the coordinate attribute implied by treeType; or if every branch shares one coordinate, leaving no radial span to normalize over.

Examples

Coordinates must already be assigned, by a plotting call or directly:

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.5);", treeType="divergence")
>>> ll._assign_tree_coordinates()
>>> tip = ll.get_leaf("A")
>>> tuple(round(value, 6) for value in ll.project_circular_point(tip.height, tip.y))
(-0.866025, 0.5)
reduce_tree(tipsToKeep)

Extract the minimal subtree spanning a set of retained tips.

Every ancestor on the path from a retained tip to the root is kept, so root-to-tip distances are preserved exactly. Nodes whose other children were pruned are not collapsed, so the reduced tree generally contains singleton nodes; run make_single_type() afterwards for a strictly branching result.

Requires a traversed tree, since branches are ordered by height; call traverse_tree() or sort_branches() first or this raises TypeError. Duplicate entries in tipsToKeep are ignored.

Parameters

tipsToKeeplist[BranchLike]

Leaf-like branches from this tree that should remain in the reduced tree. Must be branch objects from this tree, not tip names.

Raises

ValueError

If no tips are supplied, an input is not leaf-like, or an input does not belong to this tree.

Returns

Tree

Deep-copied reduced tree containing the embedding of the retained tips.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> ll.sort_branches()
>>> reduced = ll.reduce_tree([ll.get_leaf("A"), ll.get_leaf("D")])
>>> sorted(tip.name for tip in reduced.get_external())
['A', 'D']

Distances survive the reduction, at the cost of leaving singleton nodes behind:

>>> reduced.get_leaf("A").height == ll.get_leaf("A").height
True
>>> any(len(node.children) == 1 for node in reduced.get_internal())
True
rename_tips(tipNameMap=None)

Rename leaf nodes using a mapping.

Renamed tips are visible through get_external(). Only true leaves are renamed, so collapsed clades keep their names.

Parameters

tipNameMapdict, optional

Mapping from current tip name to replacement name. Every tip in the tree must appear as a key. If omitted, self.tipMap is used.

Returns

None

Tips are renamed in place.

Raises

ValueError

If tipNameMap is omitted and the tree has no tipMap.

KeyError

If a tip’s current name is missing from the mapping. Tips are renamed as they are visited, so a partial mapping leaves the tree partly renamed.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> ll.rename_tips({"A": "sample_1", "B": "sample_2", "C": "sample_3"})
>>> sorted(tip.name for tip in ll.get_external())
['sample_1', 'sample_2', 'sample_3']
reroot(branch=None, branchFrac=0.5, fixSingletons=True)

Reroot the tree on a branch or branch midpoint.

Only divergence trees can be rerooted: moving the root would invalidate the calibration of a time tree, so one raises AttributeError instead. Total tree length is preserved, and this is checked before returning.

The tree is modified in place. If the current root carries a non-zero branch length, it is silently reset to 0.0 (with a warning logged) since a root stub is a plotting convenience rather than real divergence.

Parameters

branchBranchLike, optional

Branch on which the new root should be placed. If omitted, midpoint rooting is used via midpoint_root(). Passing the current root logs a warning and returns the tree unchanged.

branchFracfloat, default=0.5

Where along branch to place the new root, as a fraction of its length measured from the branch’s parent end. 0.0 puts the root at the top of the branch, 1.0 at the branch itself.

fixSingletonsbool, default=True

If True, run make_single_type() afterwards to splice out the singleton node the old root becomes.

Returns

Tree

The same tree object, rerooted. The return value is a convenience for chaining, not a copy.

Raises

AttributeError

If the tree’s treeType is "time".

AssertionError

If total tree length changed during rerooting, which would indicate a branch-length bookkeeping error.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:2.0);", treeType="divergence")
>>> ll.sort_branches()
>>> branch = ll.get_leaf("C")
>>> _ = ll.reroot(branch=branch, branchFrac=0.5)
>>> ll.root is not None
True

Attribution

Adapted from Bio.Phylo.BaseTree.Tree.root_with_outgroup in Biopython, originally implemented by Eric Talevich. Modified for baltic branch objects, parent references, singleton removal, and branch-fraction placement.

Copyright (C) 2009 Eric Talevich and the Biopython contributors.

The upstream file is distributed, at the recipient’s choice, under the Biopython License Agreement or the BSD 3-Clause License.

Source: https://github.com/biopython/biopython/blob/master/Bio/Phylo/BaseTree.py License: https://github.com/biopython/biopython/blob/master/LICENSE.rst

rescale(factor)

Multiply all branch lengths by a constant factor.

Heights are recomputed immediately via traverse_tree().

Parameters

factorfloat

Scaling factor applied to every branch length.

Returns

None

Branches are modified in place.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(A:1.0,B:2.0);", treeType="divergence")
>>> ll.rescale(2.0)
>>> sorted(ll.get_parameter_list("length"))
[0.0, 2.0, 4.0]
restore_all_collapsed_subtrees()

Restore every previously collapsed clade back into its original subtree.

This reverses collapse_subtree_to_clade(). All clades are restored, and the loop repeats until none remain, so clades nested inside a restored subtree are expanded too. Any tipMap entries created for the clades are removed, and the tree is re-traversed before returning.

Returns

None

The tree is modified in place.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> _ = ll.collapse_subtree_to_clade(ll.find_MRCA("A", "B"), "AB clade")
>>> ll.restore_all_collapsed_subtrees()
>>> any(branch.name == "AB clade" for branch in ll.get_external(onlyLeaves=False))
False
root_by_regression(stat: str = 'r^2', forcePositive: bool = True, nJobs: int | None = None, baseRefineIters: int = 20, refineItersPerTip: int = 10, maxRefineIters: int = 400, returnRefinedDates: bool = True)

Reroot the tree by continuous root-to-tip regression.

For each represented branch, the optimal root position is solved analytically rather than sampled on a grid. Tips with date ranges are alternately projected into their ranges and refitted until convergence.

The tree is rerooted in place, and tips whose dates were uncertain have their absoluteTime overwritten with the inferred value. Requires a divergence tree whose tips carry dates, as produced by loading with absoluteTime=True.

Progress is written to standard output through a dedicated handler on the baltic.tree.root_by_regression logger, independent of the root logger’s configuration.

Parameters

stat{“r^2”, “correlation”, “sum of squares”}, default=”r^2”

Regression statistic to optimize. "sum of squares" is minimized; the other statistics are maximized.

forcePositivebool, default=True

If true, exclude root positions with a negative regression slope.

nJobsint or None, optional

Number of worker threads used to evaluate branches. None or a non-positive value uses the available CPU count.

baseRefineItersint, default=20

Base number of ranged-date refinement iterations.

refineItersPerTipint, default=10

Additional ranged-date iterations allowed per uncertain tip.

maxRefineItersint, default=400

Upper limit on ranged-date refinement iterations.

returnRefinedDatesbool, default=True

If true, return (tree, refined_dates). Otherwise return the tree alone. Note the default is the tuple.

Returns

Tree or tuple

The same tree object, rerooted, and – when returnRefinedDates is true – a dict of inferred dates for the tips that had ranges. The dict is empty when every tip date was already exact. A tree with fewer than three tips is returned unchanged, with a warning logged.

Raises

ValueError

If stat is not one of the three supported statistics, if the tree is not a divergence tree, or if the refinement iteration limits are not positive.

Warns

RuntimeWarning

If ranged-date refinement hits its iteration limit without converging.

Notes

Exact dates require one closed-form solve per branch. Date ranges use iterative projection. The previous implementation remains available as root_by_regression_legacy(), which differs in several respects – see its docstring before switching.

Examples

Progress lines go to standard output, so this example captures them to keep the doctest readable.

>>> import io
>>> import baltic as bt
>>> from contextlib import redirect_stdout
>>> handle = io.StringIO("((A|2020-01-01:0.1,B|2020-06-01:0.2):0.3,C|2021-01-01:0.4);")
>>> ll = bt.io.load_newick(handle, treeType="divergence", absoluteTime=True, variableDate=True)
>>> with redirect_stdout(io.StringIO()):
...     rooted, inferred = ll.root_by_regression(nJobs=1)
>>> rooted is ll
True
>>> inferred
{}
root_by_regression_legacy(stat: str = 'r^2', forcePositive: bool = True, nJobs: int | None = None, baseRefineIters: int = 20, refineItersPerTip: int = 10, maxRefineIters: int = 400, returnRefinedDates: bool = True)

Reroot using the preserved legacy root-to-tip regression search.

Superseded by root_by_regression(), which solves each branch in closed form instead of searching, runs in threads, and validates its arguments more consistently. Prefer that method for new code; this one is kept so earlier results can be reproduced.

Every non-root branch is evaluated as a candidate root, each in a worker process. The tree is rerooted in place and uncertain tips have their absoluteTime overwritten with the best-fitting inferred value.

Warning

Because the work is dispatched with concurrent.futures.ProcessPoolExecutor, on platforms that spawn rather than fork (macOS and Windows) the calling code must be guarded by if __name__ == "__main__":. Without it the pool dies with BrokenProcessPool, and it generally cannot be called straight from a notebook or an interactive session. This applies even with nJobs=1. root_by_regression() uses threads and has no such restriction.

Parameters

stat{“r^2”, “correlation”, “sum of squares”}, default=”r^2”

Which regression stat to optimize.

forcePositivebool, default=True

Forbid date inference to allow negative branch lengths.

nJobsint or None, optional

Number of parallel worker processes to use for the search. None or a non-positive value uses the available CPU count.

baseRefineItersint, default=20

Minimum number of Monte Carlo iterations.

refineItersPerTipint, default=10

Additional Monte Carlo iterations per tip.

maxRefineItersint, default=400

Maximum number of Monte Carlo iterations.

returnRefinedDatesbool, default=True

True if the best-fitting inferred dates should be returned.

Returns

Tree or tuple

The same tree object, rerooted, and – when returnRefinedDates is true – a dict mapping tip names to the best-fitting dates. A tree with no candidate branches is returned bare, without the dict, regardless of returnRefinedDates.

Raises

AssertionError

If stat is not one of the three supported statistics, or if no tip carries an absoluteTimeRange (load the tree with absoluteTime=True). Note that root_by_regression() raises ValueError for the equivalent argument error.

ValueError

If only some tips carry an absoluteTimeRange; the range of every tip is differenced, so a mix of set and unset ranges fails.

Examples

>>> import io
>>> import baltic as bt
>>> handle = io.StringIO("((A|2020-01-01:0.1,B|2020-06-01:0.2):0.3,C|2021-01-01:0.4);")
>>> ll = bt.io.load_newick(handle, treeType="divergence", absoluteTime=True, variableDate=True)
>>> rooted, inferred_dates = ll.root_by_regression_legacy(nJobs=1)
>>> isinstance(inferred_dates, dict)
True

The final two steps are skipped rather than run: the process pool requires a __main__ guard, which a doctest cannot provide. Run them from a script shaped like this instead:

if __name__ == "__main__":
    rooted, inferred_dates = ll.root_by_regression_legacy(nJobs=1)
set_absolute_time(mostRecentSamplingDate, justLeaves=False)

Assign absolute times to branches from their heights.

Each branch’s absoluteTime becomes mostRecentSamplingDate - treeHeight + height, so heights must already be set: call traverse_tree() first, or this raises TypeError on the unset heights. The tree’s mostRecent attribute is set to the latest assigned date. Branches are modified in place.

These dates are later consumed by get_all_tip_TMRCAs() and count_lineages_at_time().

Parameters

mostRecentSamplingDatefloat

Absolute date corresponding to the most recent sampled tip, usually a decimal year from baltic.bt_utils.calendar_to_decimal_date().

justLeavesbool, default=False

If True, only assign absolute times to leaves, leaving internal nodes with whatever absoluteTime they already had.

Returns

None

Branches are modified in place.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:2.0):1.0,C:3.0);", treeType="time")
>>> _ = ll.traverse_tree()
>>> ll.set_absolute_time(2020.0)
>>> round(ll.get_leaf("C").absoluteTime, 1)
2020.0
>>> round(ll.root.absoluteTime, 1), ll.mostRecent
(2017.0, 2020.0)
sort_branches(descending=True, sortFxn=None, operationFxn=None)

Reorder child lists for internal nodes and refresh plotting coordinates.

Coordinate updates are handled by _assign_tree_coordinates().

Parameters

descendingbool, default=True

Controls the direction of the default sort. Ignored when sortFxn or operationFxn is given.

sortFxncallable, optional

Key function used to sort each node’s children. By default children are ordered by type, then descendant count, then branch length.

operationFxncallable, optional

Callable that receives and returns each node’s child list directly, for reorderings a key function cannot express (such as reversing).

Returns

None

Child lists and plotting coordinates are updated in place.

Raises

Exception

If both sortFxn and operationFxn are given.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> ll.sort_branches(descending=False, operationFxn=lambda kids: list(reversed(kids)))
>>> ll.root.children[0].is_leaf()
True
subtree(startingNode=None, traverseCondition=None, stem=True)

Generate a new subtree starting from a given root node according to a certain condition.

Parameters

startingNodeBranchLike, optional

The node from which the new subtree will descend.

By default, the root of the tree is used.

traverseConditioncallable, optional

Function defining the conditional inclusion descendant nodes.

By convention, the function should take a single BranchLike object as input and return a boolean value indicating whether to traverse that branch. For example, to include all branches with length greater than 0.5: traverseCondition = lambda k: k.length > 0.5.

By default, all branches are included. Supplying one also prunes children that were not traversed and runs fix_hanging_nodes() on the result.

stembool, default=True

Include the stem branch leading into startingNode. When False, the new root’s length is set to 0.0. Has no effect beyond that when startingNode is already the root.

Returns

Tree or None

A new tree whose branches are deep copies, so editing it never affects the original. None is returned, with an error logged, when the traversal collects no leaves – which is what an over-restrictive traverseCondition produces.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> node = ll.find_MRCA("A", "B", "C")
>>> sub = ll.subtree(startingNode=node, stem=False)
>>> sorted(tip.name for tip in sub.get_external())
['A', 'B', 'C']

The copy is independent of the tree it came from:

>>> sub.get_leaf("A").name = "renamed"
>>> sorted(tip.name for tip in ll.get_external())
['A', 'B', 'C', 'D']
to_auspice_json(traits=None, mostRecentDate=None)

Convert the tree to an Auspice v2 JSON structure.

Branch dictionaries are constructed with baltic.bt_utils.branch_to_json().

Only traits Auspice can colour by are carried into the metadata: those ending _95%_HPD (continuous), .set.prob (categorical) or _median, plus posterior. Other traits are dropped from the colourings.

Note

This modifies the tree. Every internal node gains a node_idx trait holding its Auspice node name, and traverse_tree() is run to establish the ordering.

Parameters

traitsiterable, optional

Trait names to export. If omitted, every trait found on the tree is considered. When passing an explicit list, include the _95%_HPD and .set.prob companions of any trait whose uncertainty should survive.

mostRecentDatefloat, optional

Most recent sampling date for time trees. Must be a float – an int such as 2024 is rejected. For a time tree it defaults to the tree’s mostRecent; supplying it for a divergence tree logs a warning.

Returns

dict

Auspice JSON payload with version, meta and tree keys.

Raises

AssertionError

If mostRecentDate is not a float, or if it was omitted for a time tree whose mostRecent has not been set by set_absolute_time().

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="time")
>>> ll.sort_branches()
>>> ll.set_absolute_time(2024.0)
>>> data = ll.to_auspice_json(mostRecentDate=2024.0)
>>> sorted(data.keys())
['meta', 'tree', 'version']
to_string(curNode=None, traits=None, nexus=False, stringFragment=None, traverseCondition=None, rename=None, quoteCharacter="'", json=False)

Serialize the tree to Newick- or NEXUS-like text.

Annotations are written as BEAST-style [&key=value] comments. Tip names are always quoted, and branch lengths are written with fifteen decimal places.

Parameters

curNodeBranchLike, optional

Current node during recursive serialization. Callers normally leave this unset; see the note on the return value below.

traitsiterable, optional

Trait names to include in branch comments. By default every trait found anywhere on the tree is exported; pass an empty list for a plain Newick string.

nexusbool, default=False

If True, wrap the result in a simple NEXUS tree block.

stringFragmentlist, optional

Internal accumulator used during recursion.

traverseConditioncallable, optional

Predicate selecting which descendants to serialize. By default every descendant is written.

renamedict, optional

Optional mapping from existing tip names to exported names. Must contain every tip in the tree.

quoteCharacterstr, default=”’”

Quote character used around tip names.

jsonbool, default=False

Compatibility flag. Its only effect is to forbid combining it with nexus.

Returns

str or None

Serialized tree string. The string is assembled and returned only when recursion reaches the tree’s root, so calling this with an explicit curNode other than the root returns None. Use subtree() first to serialize part of a tree.

Raises

AssertionError

If rename is not a dict or omits a tip name; if traverseCondition leaves a node with no traversable children; or if nexus and json are both true.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> ll.to_string().endswith(";")
True
>>> ll.to_string(traits=[])
"(('A':1.000000000000000,'B':1.000000000000000):1.000000000000000,'C':1.000000000000000):0.000000000000000;"
>>> ll.to_string(nexus=True).startswith("#NEXUS")
True
traverse_tree(curNode=None, includeCondition=None, traverseCondition=None, collect=None)

Traverse the tree recursively while updating heights and descendant sets.

This is the method that recomputes tree state, so call it after any structural edit. As it walks it sets each branch’s height, fills each node’s leaves set with the names of its descendant tips, sets each node’s childHeight, and updates the tree’s treeHeight. When started from the root with neither condition given, the existing height, leaves and childHeight values are cleared first; supplying either condition skips that reset, so a filtered traversal refines existing state rather than rebuilding it.

Parameters

curNodeBranchLike, optional

Node at which to start traversal. By default the root is used.

includeConditioncallable, optional

Predicate deciding whether a visited branch should be collected. By default only leaf-like branches are collected, so a bare traverse_tree() returns the tips rather than every branch.

traverseConditioncallable, optional

Predicate deciding whether a child branch should be traversed. By default every child is traversed.

collectlist, optional

Existing collection list to append to. Used by the recursive calls; callers normally leave this unset.

Returns

list[BranchLike]

Branches satisfying includeCondition, in traversal order.

Raises

AttributeError

If a node without children is encountered. Repair such a tree with fix_hanging_nodes().

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence")
>>> leaves = ll.traverse_tree(includeCondition=lambda k: k.is_leaf())
>>> [tip.name for tip in leaves]
['A', 'B', 'C']

Heights and descendant sets are a side effect of the walk:

>>> ll.root.childHeight, ll.treeHeight
(2.0, 2.0)
>>> sorted(ll.root.leaves)
['A', 'B', 'C']
treeStats()

Print a short textual summary of tree statistics.

This is a print-oriented wrapper around _calculate_tree_stats(); use treeStatsDict() to get the same numbers as data. Because the statistics are recomputed, this calls traverse_tree() and therefore refreshes branch heights as a side effect.

Returns

None

The summary is written to standard output.

Examples

>>> import baltic as bt
>>> import io
>>> from contextlib import redirect_stdout
>>> ll = bt.make_tree("(A:1.0,B:2.0);", treeType="divergence")
>>> buf = io.StringIO()
>>> with redirect_stdout(buf):
...     ll.treeStats()
>>> "Tree height" in buf.getvalue()
True
treeStatsDict()

Return summary statistics describing the current tree.

This exposes the dictionary returned by _calculate_tree_stats(), the same numbers treeStats() prints. Because the statistics are recomputed, this calls traverse_tree() and therefore refreshes branch heights as a side effect.

Returns

dict

Dictionary of tree height and length, the strictlyBifurcating, multitypeTree, singletonTree and hasTraits topology flags, and counts of objects, nodes and leaves.

Examples

>>> import baltic as bt
>>> ll = bt.make_tree("(A:1.0,B:2.0);", treeType="divergence")
>>> sorted(ll.treeStatsDict())
['hasTraits', 'multitypeTree', 'numLeaves', 'numNodes', 'numObjects', 'singletonTree', 'strictlyBifurcating', 'treeHeight', 'treeLength']