Module asymmetree.tools.PhyloTreeTools

Auxiliary functions for phylogentic trees.

Phylogenetic trees are usually defined as trees where every inner node has at least two children. However, the class Tree does not force this property. Simulated trees are often 'planted' i.e. the root has only a single child, and this edge represents the ancestral lineage.

Expand source code
# -*- coding: utf-8 -*-

"""
Auxiliary functions for phylogentic trees.

Phylogenetic trees are usually defined as trees where every inner node has
at least two children. However, the class `Tree` does not force this property.
Simulated trees are often 'planted' i.e. the root has only a single child,
and this edge represents the ancestral lineage.
"""

import collections, itertools, random, re

import numpy as np

from tralda.datastructures.Tree import Tree, TreeNode


__author__ = 'David Schaller'


default_attributes = {'label': '',
                      'event': None,
                      'reconc': None,
                      'dist': 1.0,
                      'tstamp': None,
                      'transferred': 0}


def node_to_str(node):
    """String representation of a node in a phylogenetic tree.
    
    Returns
    -------
    str
        String representation of the node including reconc (if defined)
        and dist.
    """
    
    if isinstance(node.reconc, (tuple, list)):
        return '{}<{}-{}>:{}'.format(node.label, *node.reconc, node.dist)
    elif node.reconc:
        return '{}<{}>:{}'.format(node.label, node.reconc, node.dist)
    else:
        return '{}:{}'.format(node.label, node.dist)


def sorted_nodes(tree, oldest_to_youngest=True):
    """List of nodes sorted by timestamp.
    
    Return a list of all nodes in the tree sorted by time stamp beginning
    with the oldest (highest time stamp). Optionally the order can be
    reversed.
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree with nodes that have a `tstamp` attribute.
    oldest_to_youngest : bool, optional
        If True, the nodes are sorted from oldest to youngest, otherwise
        from youngest to oldest (the default is True).
        
    Returns
    -------
    list of TreeNode instances
        Nodes sorted by their timestamp.
    """
    
    return sorted(tree.preorder(),
                  key=lambda node: node.tstamp,
                  reverse=oldest_to_youngest)


def sorted_edges(tree):
    """List of edges (u,v) sorted by timestamp of u.
    
    Return a list of all edges (u,v) sorted by timestamp of u beginning
    with the oldest (highest time stamp).
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree with nodes that have a `tstamp` attribute.
        
    Returns
    -------
    list of pairs of TreeNode instances
        Edges sorted by their timestamp.
    """

    return sorted(tree.edges(),
                  key=lambda e: (e[0].tstamp, e[1].tstamp),
                  reverse=True)

def distance_from_timing(tree):
    """Adjusts all distances according to the time stamp difference.
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree with nodes that have a `tstamp` attribute.
    """
    
    if tree.root:
        tree.root.dist = 0.0
    
    for u, v in tree.edges():
        v.dist = abs(u.tstamp - v.tstamp)


def delete_and_reconnect(tree, node,
                         add_distances=True,
                         keep_transferred=True):
    """Delete a node from the tree and reconnect its parent and children.
    
    Parameters
    ----------
    node : TreeNode
        The node in the tree to be deleted.
    add_distances : bool, optional
        When the node v is deleted and its children are reconnected to its
        parent, add to the `dist` parameter of the children `v.dist`, i.e.
        the distance of v from its parent (the default is True).
    keep_transferred : bool, optional
        When the edge of the deleted node v from its parent u was a
        transfer edge, make all edges from u to the children of v transfer
        edges (the default is True).
        
    Returns
    -------
    TreeNode or bool
        The parent of the node, if it could be deleted, or False, if the
        node could not be deleted, i.e., it has no parent.
    """
    
    if not node.parent:
        return False
    
    for child in node.children:
        if add_distances and hasattr(node, 'dist') and hasattr(child, 'dist'):
            child.dist += node.dist
        if (keep_transferred and 
            hasattr(node, 'transferred') and 
            node.transferred):
            child.transferred = 1
    
    return tree.delete_and_reconnect(node)


def add_planted_root(tree):
    """Add a new root that has the original root as its single child.
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree.
    
    Returns
    -------
    TreeNode
        The newly added root.
    """
    
    old_root = tree.root
    tree.root = TreeNode()
    for k, v in default_attributes.items():
        setattr(tree.root, k, v)
    
    if old_root:
        tree.root.add_child(old_root)
    
    return tree.root


def reconc_sorted_leaves(tree, return_list=False):
    """Sort leaves by their reconciliation attribute.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `reconc` attribute.
    return_list : bool, optional
        If True, also return a list of leaves such that leaves of the same
        reconciliation appear as a coherent sequence (the default is False).
    
    Returns
    -------
    dict
        A dictionary with reconciliations as keys and a list of corresponding
        nodes as values.
    list, optional
        List of leaves such that leaves of the same reconciliation appear as a
        coherent sequence.
    """
    
    reconc_dict = {}
    
    for leaf in tree.leaves():
        if leaf.reconc not in reconc_dict:
            reconc_dict[leaf.reconc] = []
        reconc_dict[leaf.reconc].append(leaf)
    
    if not return_list:
        return reconc_dict
    else:
        leaves = []
        for reconc, leaf_list in reconc_dict.items():
            for leaf in leaf_list:
                leaves.append(leaf)
        
        return reconc_dict, leaves
    
    
def distance_matrix(tree, leaf_order=None):
    """Distance matrix on the leaf set of the phylogenetic tree.
    
    Computes a distance matrix on the set of leaves of the tree where each
    distances is the sum of the distances (`dist`) on the path connecting
    the pair of leaves.
    
    Additionally a list of leaves corresponding to the indices in the
    matrix is returned.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `dist` attribute.
    leaf_order : list, optional
        A list of all leaves in the tree defining the indices for the
        matrix (the default is None, in which case leaves are indexed in
        sibling order).
    
    Returns
    -------
    list of TreeNode objects
        Represents the order for the lines/columns in the distance matrix.
    numpy.ndarray (dtype=numpy.float)
        The distance matrix.
    """
    
    distance_dict = distances_from_root(tree)
    leaves = tree.leaf_dict()
    
    if leaf_order:
        if set(leaf_order) != set(leaves[tree.root]):
            raise ValueError('ordered leaf list does not match with the '\
                             'leaves in the tree')
        L = leaf_order
    else:
        # leaves in sibling order
        L = leaves[tree.root]     
    
    leaf_index = {l: i for i, l in enumerate(L)}
    
    D = np.zeros((len(L),len(L)), dtype=np.float)
    
    for v in tree.preorder():
        if v.children:
            for c1, c2 in itertools.combinations(v.children, 2):
                for x in leaves[c1]:
                    x_index = leaf_index[x]
                    x_dist = distance_dict[x] - distance_dict[v]
                    for y in leaves[c2]:
                        y_index = leaf_index[y]
                        y_dist = distance_dict[y] - distance_dict[v]
                        D[x_index, y_index] = x_dist + y_dist
                        D[y_index, x_index] = x_dist + y_dist
    
    return L, D


def distances_from_root(tree):
    """The distances of each node to the root of the tree.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `dist` attribute.
    
    Returns
    -------
    dict
        The keys are TreeNode objects and the values their distances
        (sum of `dist`) to the root.
    """
    
    distance_dict = {}
    
    for v in tree.preorder():
        if not v.parent:
            distance_dict[v] = 0.0
        else:
            depth = distance_dict[v.parent] + v.dist
            distance_dict[v] = depth
            
    return distance_dict


def topology_only(tree, inplace=True):
    """Reset reconciliations, distances, time stamps, transfer status, and
    inner labels.
    
    Parameters
    ----------
    tree : Tree
        A tree.
    inplace : bool
        If True, reset the attributes of this tree instance, otherwise
        make a copy first and modify the copy.
    
    Returns
    -------
    Tree
        The original or a copy of the tree instance with reset attributes.
    """
    
    if not inplace:
        T = tree.copy()
    else:
        T = tree
    
    for v in T.preorder():
        if v.children:
            v.label = ''
            v.reconc = None
        v.dist = 1.0
        v.tstamp = None
        v.transferred = 0
    
    return T


def count_node_types(tree):
    """Count speciations, duplication, losses, HGTs and surviving genes.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `label` attribute.
    
    Returns
    -------
    dict
        With the event counts as values. Key are 'S' (speciations), 'D'
        (duplication), 'L' (losses), 'H' (HGTs) and 'extant' (surviving
        genes).
    """
    
    counts = {'S': 0, 'D': 0, 'L': 0, 'H': 0, 'extant': 0}
    
    for v in tree.preorder():
        
        if not v.children:
            if v.event == 'L':
                counts['L'] += 1
            else:
                counts['extant'] += 1
        
        elif v.event == 'S':
            counts['S'] += 1
        elif v.event == 'D':
            counts['D'] += 1
        elif v.event == 'H':
            counts['H'] += 1
    
    return counts


def random_colored_tree(n, colors, binary=False, force_all_colors=False):
     """Create a random colored tree.
     
     The number of leaves and the reconciliation labels are specified in the
     parameters n and colors, respectively. Each non-leaf node in the 
     resulting tree will have at least children (property of phylogenetic
     trees).
     
     Parameters
     ----------
     n : int
         The desired number of leaves.
     colors : int or list
         The list of recociliations, or the desired number in which case the
         reconciliations {1, ..., colors} are used.
     binary : bool, optional
         If True, forces the tree to be binary (the default is False).
     force_all_colors : bool
         If True, the resulting tree is guaranteed to have at least one leaf
         of each reconciliation (the default is False).
     
     Returns
     -------
     Tree
         A random tree with n leaves to which the `reconc` attribute is
         assigned at random.
     
     Raises
     ------
     TypeError
         If n is not an integer > 0.
     ValueError
         If the number of colors is greater than n and `force_all_colors`
         is true.
     """
     
     tree = Tree.random_tree(n, binary=binary)
         
     if isinstance(colors, int):
         colors = [i+1 for i in range(colors)]
     elif not isinstance(colors, collections.abc.Iterable):
         raise TypeError("'colors' must be of type 'int' or iterable")
         
     if len(colors) > n and force_all_colors:
         raise ValueError('cannot force all colors since #colors > n')
     
     leaves = [l for l in tree.leaves()]
     
     if force_all_colors:
         # use every color at least once
         permutation = np.random.permutation(len(leaves))
         for i in range(len(leaves)):
             if i < len(colors):
                 leaves[permutation[i]].reconc = colors[i]
             else:
                 # color the remaining leaves randomly
                 leaves[permutation[i]].reconc = random.choice(colors)
     else:
         # assign colors completely randomly
         for leaf in leaves:
             leaf.reconc = random.choice(colors)
             
     return tree
 
    
def phylo_tree_attributes(tree, inplace=True):
    """Add the attributes for a phylogentic tree if not already set.
    
    Parameters
    ----------
    tree : Tree
    inplace : bool
        If True, the input tree is modified, otherwise a copy is returned.
        The default is True.
    
    Returns
    -------
    Tree
    """
    
    if not inplace:
        tree = tree.copy()
    
    for v in tree.preorder():
        for key, value in default_attributes.items():
            if not hasattr(v, key):
                setattr(tree.root, key, value)
    
    return tree


# --------------------------------------------------------------------------
#                    RECONSTRUCTION OF INFORMATION
# --------------------------------------------------------------------------

def assign_missing_labels(tree):
    """Assign integer label to nodes with no or missing label.
    
    Also assign the `number_of_species` attribute, i.e., the number of leaves,
    to the tree.
    
    Parameters
    ----------
    tree : Tree
    """
    
    tree.number_of_species = 0
    labels = set()
    
    for v in tree.preorder():
        if not v.children:
            tree.number_of_species += 1
            
        if hasattr(v, 'label') and v.label:
            labels.add(v.label)
    
    # assign new labels to remaining nodes
    current_label = 0
    for v in tree.preorder():
        if not hasattr(v, 'label') or not v.label:
            while current_label in labels:
                current_label += 1
            v.label = current_label
            labels.add(current_label)


def reconstruct_reconc_from_graph(tree, G):
    """Reconstruct the reconciliations from a NetworkX Graph.
    
    Parameters
    ----------
    tree : Tree
    G : networkx.Graph
        The graph from which labels and reconciliations shall be reconstructed.
    """
    
    for v in tree.preorder():
        if hasattr(v, 'label') and v.label in G:
            if 'reconc' in G.nodes[v.label]:
                if isinstance(G.nodes[v.label]['reconc'], list):
                    v.reconc = tuple(G.nodes[v.label]['reconc'])
                else:
                    v.reconc = G.nodes[v.label]['reconc']
            elif 'color' in G.nodes[v.label]:
                if isinstance(G.nodes[v.label]['color'], list):
                    v.reconc = tuple(G.nodes[v.label]['color'])
                else:
                    v.reconc = G.nodes[v.label]['color']
            
            
def reconstruct_timestamps(tree):
    """Reconstruct the timestamps.
    
    Make the time stamps matching with the distance attribute. The root
    obtains time stamp 1.0, and all other node smaller time stamps such
    that the difference to the parent's time stamp is exactly `dist`.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `dist` attribute.
    """
    
    tree.root.tstamp = 1.0
    for v in tree.preorder():
        if v.parent:
            v.tstamp = v.parent.tstamp - v.dist
    

# --------------------------------------------------------------------------
#                         TREE MANIPULATION
# -------------------------------------------------------------------------- 

def delete_losses_and_contract(tree, inplace=False):
    """Delete all branches leading to loss leaves only.
    
    Nodes that would have only a single child afterwards are suppressed (except
    possibly the planted root), i.e. their children are recursively reconnected
    to the parents. Distances are cumulated in this process and the transferred
    status is kept in the sense that an edge is a transfer edge if at least
    one edge on the contracted path to which it corresponds was a transfer edge.
    
    Parameters
    ----------
    tree : Tree
        The tree in which loss branches shall be removed.
    inplace : bool, optional
        If True, the tree is directly manipulated. The default is False in
        which case a copy of the tree is created which gets manipulated while
        the original tree remains untouched.
    
    Returns
    -------
    Tree
        The tree with all loss branches removed (original instance or a new one
        depending on the `inplace` parameter).
    """
    
    if not inplace:
        tree = tree.copy()
    
    loss_nodes = []
    for node in tree.postorder():
        if not node.children and node.event == 'L':
            loss_nodes.append(node)
    
    # traverse from loss node to root delete if degree <= 1
    for loss_node in loss_nodes:
        current = delete_and_reconnect(tree, loss_node,
                                       add_distances=True,
                                       keep_transferred=True)
        
        while len(current.children) < 2 and current.parent:
            current = delete_and_reconnect(tree, current,
                                           add_distances=True,
                                           keep_transferred=True)
    
    return tree


def remove_planted_root(tree, inplace=True):
    """Remove the planted root of the tree (if existent).
    
    Parameters
    ----------
    tree : Tree
        The tree in which the planted root shall be removed.
    inplace : bool, optional
        If True, the tree is directly manipulated, otherwise a copy is created
        and the original tree remains untouched (the default is True).
    
    Returns
    -------
    Tree
        The tree with the planted root removed (original instance or a new one
        depending on the `inplace` parameter).
    """
    
    if not inplace:
        tree = tree.copy()
        
    # delete the root if the tree is planted
    if len(tree.root.children) == 1:
        new_root = tree.root.children[0]
        new_root.detach()
        tree.root = new_root
        new_root.dist = 0.0
        
    if not tree.root.children and not tree.root.label:
        # no surviving genes --> return empty tree
        tree.root = None
    
    return tree


# --------------------------------------------------------------------------
#                          TREE  <--->  NEWICK
# --------------------------------------------------------------------------
        
def to_newick(tree, label=True, reconc=True, distance=True,
                    label_inner=True, reconc_inner=False):
    """Return a Newick representation of the tree.
    
    This function overrides the function of the parent class.
    
    Parameters
    ----------
    label : bool, optional
        If True, the Newick str contains the labels of the nodes (the
        default is True).
    reconc : bool, optional
        If True, the Newick str contains the reconciliations of the nodes in
        <[...]> brackets (the default is True).
    distance : bool, optional
        If True, the Newick str contains the distances of the nodes in
        standard :[...] notation (the default is True).
    label_inner : bool, optional
        If True, the Newick str also contains the labels of the inner 
        nodes (the default is True).
    reconc_inner : bool, optional
        If True, the Newick str contains the reconciliations of the inner nodes
        (the default is False).
    
    Returns
    -------
    str
        A Newick representation of the tree.
    """
    
    def _to_newick(node):
        
        if not node.children:
            token = ''
            if label and hasattr(node, 'label'):
                token += str(node.label)
            if reconc and hasattr(node, 'reconc') and node.reconc:
                token += '<{}-{}>'.format(*node.reconc) \
                    if isinstance(node.reconc, (tuple, list)) \
                    else '<{}>'.format(node.reconc)
            if distance and hasattr(node, 'dist'):
                token += ":{}".format(node.dist)
            return token
        else:
            s = ''
            for child in node.children:
                s += _to_newick(child) + ','
            token = ''
            if label and hasattr(node, 'label') and label_inner:
                token += str(node.label)
            if reconc_inner and hasattr(node, 'reconc') and node.reconc:
                token += '<{}-{}>'.format(*node.reconc) \
                    if isinstance(node.reconc, (tuple, list)) \
                    else '<{}>'.format(node.reconc)
            if distance and hasattr(node, 'dist'):
                token += ':{}'.format(node.dist)
            return '({}){}'.format(s[:-1], token)
    
    
    if tree.root:
        return _to_newick(tree.root) + ';'
    else:
        return ';'


def parse_newick(newick):
    """Parses trees in Newick format into object of type 'Tree'.
    
    Parameters
    ----------
    newick : str
        A tree in Newick format.
    
    Returns
    -------
    Tree
        The parsed tree.
    
    Raises
    ------
    TypeError
        If the input is not a string.
    ValueError
        If the input is not a valid Newick string.
    
    Notes
    -----
    Do not use this function for serialization and reloading Tree
    objects. Use the `serialize()` function instead.
    Labels and reconciliations that are integer numbers are converted to int.
    The reconciliations (if present in <...> in the string) are parsed as
    strings and need to be converted to integers afterwards if necessary.
    """
    
    # label<reconc>:distance
    label_col_dist_regex = re.compile(r"'?([a-zA-Z0-9_]*)'?<(.*)>:(-?[0-9]*\.?[0-9]*[Ee]?-?[0-9]+)")
    # label<reconc>
    label_col_regex = re.compile(r"'?([a-zA-Z0-9_]*)'?<(.*)>")
    # label:distance
    label_dist_regex = re.compile(r"'?([a-zA-Z0-9_]*)'?:(-?[0-9]*\.?[0-9]*[Ee]?-?[0-9]+)")
    
    def to_int(item):
        """Trys to convert the string into int."""
        
        return int(item) if item.isdigit() else item
    
    def parse_subtree(subroot, subtree_string):
        """Recursive function to parse the subtrees."""
        
        children = split_children(subtree_string)
        for child in children:
            node = TreeNode(event='')
            subroot.add_child(node)
            end = -1
            if child[0] == '(':                                 # the child has subtrees
                end = child.rfind(')')
                if end == -1:
                    raise ValueError('invalid Newick string')
                parse_subtree(node, child[1:end])               # recursive call 'parse_subtree'
            child = child[end+1:].strip()
            label_col_dist = label_col_dist_regex.match(child)
            if label_col_dist:                                  # CASE 1: label<reconc>:distance
                node.label = to_int(label_col_dist.group(1))
                node.reconc = to_int(label_col_dist.group(2))
                node.dist = float(label_col_dist.group(3))
            else:
                label_col = label_col_regex.match(child)
                label_dist = label_dist_regex.match(child)
                if label_col:                                   # CASE 2: label<reconc>
                    node.label = to_int(label_col.group(1))
                    node.reconc = to_int(label_col.group(2))
                    node.dist = 1.0
                elif label_dist:                                # CASE 3: label:distance
                    node.label = to_int(label_dist.group(1))
                    node.reconc = None
                    node.dist = float(label_dist.group(2))
                else:                                           # CASE 4: label
                    node.label = to_int(child)
                    node.reconc = None
                    node.dist = 1.0
            # reconc is a tuple
            if node.reconc and isinstance(node.reconc, str) and node.reconc.find('-') != -1:
                split_reconc = node.reconc.split('-')
                node.reconc = (to_int(split_reconc[0]),
                              to_int(split_reconc[1]))
                    
    def split_children(child_string):
        """Splits a given string by all ',' that are not enclosed by parentheses."""
        
        stack = 0
        children = []
        current = ""
        for c in child_string:
            if (stack == 0) and c == ',':
                children.append(current)
                current = ""
            elif c == '(':
                stack += 1
                current += c
            elif c == ')':
                if stack <= 0:
                    raise ValueError('invalid Newick string')
                stack -= 1
                current += c
            else:
                current += c
        children.append(current.strip())
        return children
    
    if not isinstance(newick, str):
        raise TypeError("Newick parser needs a 'str' as input")
    end = newick.find(";")
    if end != -1:
        newick = newick[:end]
    temp_root = TreeNode(event='')
    parse_subtree(temp_root, newick)
    if temp_root.children:
        root = temp_root.children[0]
        root.dist = 0.0                 # set distance of the root to 0
        root.detach()                   # remove the parent temp_root
                                        # (important for non-recursive to_newick2)
        return Tree(root)
    else:
        raise ValueError('invalid Newick string')

Functions

def add_planted_root(tree)

Add a new root that has the original root as its single child.

Parameters

tree : Tree
A phylogenetic tree.

Returns

TreeNode
The newly added root.
Expand source code
def add_planted_root(tree):
    """Add a new root that has the original root as its single child.
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree.
    
    Returns
    -------
    TreeNode
        The newly added root.
    """
    
    old_root = tree.root
    tree.root = TreeNode()
    for k, v in default_attributes.items():
        setattr(tree.root, k, v)
    
    if old_root:
        tree.root.add_child(old_root)
    
    return tree.root
def assign_missing_labels(tree)

Assign integer label to nodes with no or missing label.

Also assign the number_of_species attribute, i.e., the number of leaves, to the tree.

Parameters

tree : Tree
 
Expand source code
def assign_missing_labels(tree):
    """Assign integer label to nodes with no or missing label.
    
    Also assign the `number_of_species` attribute, i.e., the number of leaves,
    to the tree.
    
    Parameters
    ----------
    tree : Tree
    """
    
    tree.number_of_species = 0
    labels = set()
    
    for v in tree.preorder():
        if not v.children:
            tree.number_of_species += 1
            
        if hasattr(v, 'label') and v.label:
            labels.add(v.label)
    
    # assign new labels to remaining nodes
    current_label = 0
    for v in tree.preorder():
        if not hasattr(v, 'label') or not v.label:
            while current_label in labels:
                current_label += 1
            v.label = current_label
            labels.add(current_label)
def count_node_types(tree)

Count speciations, duplication, losses, HGTs and surviving genes.

Parameters

tree : Tree
A tree with nodes that have a label attribute.

Returns

dict
With the event counts as values. Key are 'S' (speciations), 'D' (duplication), 'L' (losses), 'H' (HGTs) and 'extant' (surviving genes).
Expand source code
def count_node_types(tree):
    """Count speciations, duplication, losses, HGTs and surviving genes.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `label` attribute.
    
    Returns
    -------
    dict
        With the event counts as values. Key are 'S' (speciations), 'D'
        (duplication), 'L' (losses), 'H' (HGTs) and 'extant' (surviving
        genes).
    """
    
    counts = {'S': 0, 'D': 0, 'L': 0, 'H': 0, 'extant': 0}
    
    for v in tree.preorder():
        
        if not v.children:
            if v.event == 'L':
                counts['L'] += 1
            else:
                counts['extant'] += 1
        
        elif v.event == 'S':
            counts['S'] += 1
        elif v.event == 'D':
            counts['D'] += 1
        elif v.event == 'H':
            counts['H'] += 1
    
    return counts
def delete_and_reconnect(tree, node, add_distances=True, keep_transferred=True)

Delete a node from the tree and reconnect its parent and children.

Parameters

node : TreeNode
The node in the tree to be deleted.
add_distances : bool, optional
When the node v is deleted and its children are reconnected to its parent, add to the dist parameter of the children v.dist, i.e. the distance of v from its parent (the default is True).
keep_transferred : bool, optional
When the edge of the deleted node v from its parent u was a transfer edge, make all edges from u to the children of v transfer edges (the default is True).

Returns

TreeNode or bool
The parent of the node, if it could be deleted, or False, if the node could not be deleted, i.e., it has no parent.
Expand source code
def delete_and_reconnect(tree, node,
                         add_distances=True,
                         keep_transferred=True):
    """Delete a node from the tree and reconnect its parent and children.
    
    Parameters
    ----------
    node : TreeNode
        The node in the tree to be deleted.
    add_distances : bool, optional
        When the node v is deleted and its children are reconnected to its
        parent, add to the `dist` parameter of the children `v.dist`, i.e.
        the distance of v from its parent (the default is True).
    keep_transferred : bool, optional
        When the edge of the deleted node v from its parent u was a
        transfer edge, make all edges from u to the children of v transfer
        edges (the default is True).
        
    Returns
    -------
    TreeNode or bool
        The parent of the node, if it could be deleted, or False, if the
        node could not be deleted, i.e., it has no parent.
    """
    
    if not node.parent:
        return False
    
    for child in node.children:
        if add_distances and hasattr(node, 'dist') and hasattr(child, 'dist'):
            child.dist += node.dist
        if (keep_transferred and 
            hasattr(node, 'transferred') and 
            node.transferred):
            child.transferred = 1
    
    return tree.delete_and_reconnect(node)
def delete_losses_and_contract(tree, inplace=False)

Delete all branches leading to loss leaves only.

Nodes that would have only a single child afterwards are suppressed (except possibly the planted root), i.e. their children are recursively reconnected to the parents. Distances are cumulated in this process and the transferred status is kept in the sense that an edge is a transfer edge if at least one edge on the contracted path to which it corresponds was a transfer edge.

Parameters

tree : Tree
The tree in which loss branches shall be removed.
inplace : bool, optional
If True, the tree is directly manipulated. The default is False in which case a copy of the tree is created which gets manipulated while the original tree remains untouched.

Returns

Tree
The tree with all loss branches removed (original instance or a new one depending on the inplace parameter).
Expand source code
def delete_losses_and_contract(tree, inplace=False):
    """Delete all branches leading to loss leaves only.
    
    Nodes that would have only a single child afterwards are suppressed (except
    possibly the planted root), i.e. their children are recursively reconnected
    to the parents. Distances are cumulated in this process and the transferred
    status is kept in the sense that an edge is a transfer edge if at least
    one edge on the contracted path to which it corresponds was a transfer edge.
    
    Parameters
    ----------
    tree : Tree
        The tree in which loss branches shall be removed.
    inplace : bool, optional
        If True, the tree is directly manipulated. The default is False in
        which case a copy of the tree is created which gets manipulated while
        the original tree remains untouched.
    
    Returns
    -------
    Tree
        The tree with all loss branches removed (original instance or a new one
        depending on the `inplace` parameter).
    """
    
    if not inplace:
        tree = tree.copy()
    
    loss_nodes = []
    for node in tree.postorder():
        if not node.children and node.event == 'L':
            loss_nodes.append(node)
    
    # traverse from loss node to root delete if degree <= 1
    for loss_node in loss_nodes:
        current = delete_and_reconnect(tree, loss_node,
                                       add_distances=True,
                                       keep_transferred=True)
        
        while len(current.children) < 2 and current.parent:
            current = delete_and_reconnect(tree, current,
                                           add_distances=True,
                                           keep_transferred=True)
    
    return tree
def distance_from_timing(tree)

Adjusts all distances according to the time stamp difference.

Parameters

tree : Tree
A phylogenetic tree with nodes that have a tstamp attribute.
Expand source code
def distance_from_timing(tree):
    """Adjusts all distances according to the time stamp difference.
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree with nodes that have a `tstamp` attribute.
    """
    
    if tree.root:
        tree.root.dist = 0.0
    
    for u, v in tree.edges():
        v.dist = abs(u.tstamp - v.tstamp)
def distance_matrix(tree, leaf_order=None)

Distance matrix on the leaf set of the phylogenetic tree.

Computes a distance matrix on the set of leaves of the tree where each distances is the sum of the distances (dist) on the path connecting the pair of leaves.

Additionally a list of leaves corresponding to the indices in the matrix is returned.

Parameters

tree : Tree
A tree with nodes that have a dist attribute.
leaf_order : list, optional
A list of all leaves in the tree defining the indices for the matrix (the default is None, in which case leaves are indexed in sibling order).

Returns

list of TreeNode objects
Represents the order for the lines/columns in the distance matrix.
numpy.ndarray (dtype=numpy.float)
The distance matrix.
Expand source code
def distance_matrix(tree, leaf_order=None):
    """Distance matrix on the leaf set of the phylogenetic tree.
    
    Computes a distance matrix on the set of leaves of the tree where each
    distances is the sum of the distances (`dist`) on the path connecting
    the pair of leaves.
    
    Additionally a list of leaves corresponding to the indices in the
    matrix is returned.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `dist` attribute.
    leaf_order : list, optional
        A list of all leaves in the tree defining the indices for the
        matrix (the default is None, in which case leaves are indexed in
        sibling order).
    
    Returns
    -------
    list of TreeNode objects
        Represents the order for the lines/columns in the distance matrix.
    numpy.ndarray (dtype=numpy.float)
        The distance matrix.
    """
    
    distance_dict = distances_from_root(tree)
    leaves = tree.leaf_dict()
    
    if leaf_order:
        if set(leaf_order) != set(leaves[tree.root]):
            raise ValueError('ordered leaf list does not match with the '\
                             'leaves in the tree')
        L = leaf_order
    else:
        # leaves in sibling order
        L = leaves[tree.root]     
    
    leaf_index = {l: i for i, l in enumerate(L)}
    
    D = np.zeros((len(L),len(L)), dtype=np.float)
    
    for v in tree.preorder():
        if v.children:
            for c1, c2 in itertools.combinations(v.children, 2):
                for x in leaves[c1]:
                    x_index = leaf_index[x]
                    x_dist = distance_dict[x] - distance_dict[v]
                    for y in leaves[c2]:
                        y_index = leaf_index[y]
                        y_dist = distance_dict[y] - distance_dict[v]
                        D[x_index, y_index] = x_dist + y_dist
                        D[y_index, x_index] = x_dist + y_dist
    
    return L, D
def distances_from_root(tree)

The distances of each node to the root of the tree.

Parameters

tree : Tree
A tree with nodes that have a dist attribute.

Returns

dict
The keys are TreeNode objects and the values their distances (sum of dist) to the root.
Expand source code
def distances_from_root(tree):
    """The distances of each node to the root of the tree.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `dist` attribute.
    
    Returns
    -------
    dict
        The keys are TreeNode objects and the values their distances
        (sum of `dist`) to the root.
    """
    
    distance_dict = {}
    
    for v in tree.preorder():
        if not v.parent:
            distance_dict[v] = 0.0
        else:
            depth = distance_dict[v.parent] + v.dist
            distance_dict[v] = depth
            
    return distance_dict
def node_to_str(node)

String representation of a node in a phylogenetic tree.

Returns

str
String representation of the node including reconc (if defined) and dist.
Expand source code
def node_to_str(node):
    """String representation of a node in a phylogenetic tree.
    
    Returns
    -------
    str
        String representation of the node including reconc (if defined)
        and dist.
    """
    
    if isinstance(node.reconc, (tuple, list)):
        return '{}<{}-{}>:{}'.format(node.label, *node.reconc, node.dist)
    elif node.reconc:
        return '{}<{}>:{}'.format(node.label, node.reconc, node.dist)
    else:
        return '{}:{}'.format(node.label, node.dist)
def parse_newick(newick)

Parses trees in Newick format into object of type 'Tree'.

Parameters

newick : str
A tree in Newick format.

Returns

Tree
The parsed tree.

Raises

TypeError
If the input is not a string.
ValueError
If the input is not a valid Newick string.

Notes

Do not use this function for serialization and reloading Tree objects. Use the serialize() function instead. Labels and reconciliations that are integer numbers are converted to int. The reconciliations (if present in <…> in the string) are parsed as strings and need to be converted to integers afterwards if necessary.

Expand source code
def parse_newick(newick):
    """Parses trees in Newick format into object of type 'Tree'.
    
    Parameters
    ----------
    newick : str
        A tree in Newick format.
    
    Returns
    -------
    Tree
        The parsed tree.
    
    Raises
    ------
    TypeError
        If the input is not a string.
    ValueError
        If the input is not a valid Newick string.
    
    Notes
    -----
    Do not use this function for serialization and reloading Tree
    objects. Use the `serialize()` function instead.
    Labels and reconciliations that are integer numbers are converted to int.
    The reconciliations (if present in <...> in the string) are parsed as
    strings and need to be converted to integers afterwards if necessary.
    """
    
    # label<reconc>:distance
    label_col_dist_regex = re.compile(r"'?([a-zA-Z0-9_]*)'?<(.*)>:(-?[0-9]*\.?[0-9]*[Ee]?-?[0-9]+)")
    # label<reconc>
    label_col_regex = re.compile(r"'?([a-zA-Z0-9_]*)'?<(.*)>")
    # label:distance
    label_dist_regex = re.compile(r"'?([a-zA-Z0-9_]*)'?:(-?[0-9]*\.?[0-9]*[Ee]?-?[0-9]+)")
    
    def to_int(item):
        """Trys to convert the string into int."""
        
        return int(item) if item.isdigit() else item
    
    def parse_subtree(subroot, subtree_string):
        """Recursive function to parse the subtrees."""
        
        children = split_children(subtree_string)
        for child in children:
            node = TreeNode(event='')
            subroot.add_child(node)
            end = -1
            if child[0] == '(':                                 # the child has subtrees
                end = child.rfind(')')
                if end == -1:
                    raise ValueError('invalid Newick string')
                parse_subtree(node, child[1:end])               # recursive call 'parse_subtree'
            child = child[end+1:].strip()
            label_col_dist = label_col_dist_regex.match(child)
            if label_col_dist:                                  # CASE 1: label<reconc>:distance
                node.label = to_int(label_col_dist.group(1))
                node.reconc = to_int(label_col_dist.group(2))
                node.dist = float(label_col_dist.group(3))
            else:
                label_col = label_col_regex.match(child)
                label_dist = label_dist_regex.match(child)
                if label_col:                                   # CASE 2: label<reconc>
                    node.label = to_int(label_col.group(1))
                    node.reconc = to_int(label_col.group(2))
                    node.dist = 1.0
                elif label_dist:                                # CASE 3: label:distance
                    node.label = to_int(label_dist.group(1))
                    node.reconc = None
                    node.dist = float(label_dist.group(2))
                else:                                           # CASE 4: label
                    node.label = to_int(child)
                    node.reconc = None
                    node.dist = 1.0
            # reconc is a tuple
            if node.reconc and isinstance(node.reconc, str) and node.reconc.find('-') != -1:
                split_reconc = node.reconc.split('-')
                node.reconc = (to_int(split_reconc[0]),
                              to_int(split_reconc[1]))
                    
    def split_children(child_string):
        """Splits a given string by all ',' that are not enclosed by parentheses."""
        
        stack = 0
        children = []
        current = ""
        for c in child_string:
            if (stack == 0) and c == ',':
                children.append(current)
                current = ""
            elif c == '(':
                stack += 1
                current += c
            elif c == ')':
                if stack <= 0:
                    raise ValueError('invalid Newick string')
                stack -= 1
                current += c
            else:
                current += c
        children.append(current.strip())
        return children
    
    if not isinstance(newick, str):
        raise TypeError("Newick parser needs a 'str' as input")
    end = newick.find(";")
    if end != -1:
        newick = newick[:end]
    temp_root = TreeNode(event='')
    parse_subtree(temp_root, newick)
    if temp_root.children:
        root = temp_root.children[0]
        root.dist = 0.0                 # set distance of the root to 0
        root.detach()                   # remove the parent temp_root
                                        # (important for non-recursive to_newick2)
        return Tree(root)
    else:
        raise ValueError('invalid Newick string')
def phylo_tree_attributes(tree, inplace=True)

Add the attributes for a phylogentic tree if not already set.

Parameters

tree : Tree
 
inplace : bool
If True, the input tree is modified, otherwise a copy is returned. The default is True.

Returns

Tree
 
Expand source code
def phylo_tree_attributes(tree, inplace=True):
    """Add the attributes for a phylogentic tree if not already set.
    
    Parameters
    ----------
    tree : Tree
    inplace : bool
        If True, the input tree is modified, otherwise a copy is returned.
        The default is True.
    
    Returns
    -------
    Tree
    """
    
    if not inplace:
        tree = tree.copy()
    
    for v in tree.preorder():
        for key, value in default_attributes.items():
            if not hasattr(v, key):
                setattr(tree.root, key, value)
    
    return tree
def random_colored_tree(n, colors, binary=False, force_all_colors=False)

Create a random colored tree.

The number of leaves and the reconciliation labels are specified in the parameters n and colors, respectively. Each non-leaf node in the resulting tree will have at least children (property of phylogenetic trees).

Parameters

n : int
The desired number of leaves.
colors : int or list
The list of recociliations, or the desired number in which case the reconciliations {1, …, colors} are used.
binary : bool, optional
If True, forces the tree to be binary (the default is False).
force_all_colors : bool
If True, the resulting tree is guaranteed to have at least one leaf of each reconciliation (the default is False).

Returns

Tree
A random tree with n leaves to which the reconc attribute is assigned at random.

Raises

TypeError
If n is not an integer > 0.
ValueError
If the number of colors is greater than n and force_all_colors is true.
Expand source code
def random_colored_tree(n, colors, binary=False, force_all_colors=False):
     """Create a random colored tree.
     
     The number of leaves and the reconciliation labels are specified in the
     parameters n and colors, respectively. Each non-leaf node in the 
     resulting tree will have at least children (property of phylogenetic
     trees).
     
     Parameters
     ----------
     n : int
         The desired number of leaves.
     colors : int or list
         The list of recociliations, or the desired number in which case the
         reconciliations {1, ..., colors} are used.
     binary : bool, optional
         If True, forces the tree to be binary (the default is False).
     force_all_colors : bool
         If True, the resulting tree is guaranteed to have at least one leaf
         of each reconciliation (the default is False).
     
     Returns
     -------
     Tree
         A random tree with n leaves to which the `reconc` attribute is
         assigned at random.
     
     Raises
     ------
     TypeError
         If n is not an integer > 0.
     ValueError
         If the number of colors is greater than n and `force_all_colors`
         is true.
     """
     
     tree = Tree.random_tree(n, binary=binary)
         
     if isinstance(colors, int):
         colors = [i+1 for i in range(colors)]
     elif not isinstance(colors, collections.abc.Iterable):
         raise TypeError("'colors' must be of type 'int' or iterable")
         
     if len(colors) > n and force_all_colors:
         raise ValueError('cannot force all colors since #colors > n')
     
     leaves = [l for l in tree.leaves()]
     
     if force_all_colors:
         # use every color at least once
         permutation = np.random.permutation(len(leaves))
         for i in range(len(leaves)):
             if i < len(colors):
                 leaves[permutation[i]].reconc = colors[i]
             else:
                 # color the remaining leaves randomly
                 leaves[permutation[i]].reconc = random.choice(colors)
     else:
         # assign colors completely randomly
         for leaf in leaves:
             leaf.reconc = random.choice(colors)
             
     return tree
def reconc_sorted_leaves(tree, return_list=False)

Sort leaves by their reconciliation attribute.

Parameters

tree : Tree
A tree with nodes that have a reconc attribute.
return_list : bool, optional
If True, also return a list of leaves such that leaves of the same reconciliation appear as a coherent sequence (the default is False).

Returns

dict
A dictionary with reconciliations as keys and a list of corresponding nodes as values.
list, optional
List of leaves such that leaves of the same reconciliation appear as a coherent sequence.
Expand source code
def reconc_sorted_leaves(tree, return_list=False):
    """Sort leaves by their reconciliation attribute.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `reconc` attribute.
    return_list : bool, optional
        If True, also return a list of leaves such that leaves of the same
        reconciliation appear as a coherent sequence (the default is False).
    
    Returns
    -------
    dict
        A dictionary with reconciliations as keys and a list of corresponding
        nodes as values.
    list, optional
        List of leaves such that leaves of the same reconciliation appear as a
        coherent sequence.
    """
    
    reconc_dict = {}
    
    for leaf in tree.leaves():
        if leaf.reconc not in reconc_dict:
            reconc_dict[leaf.reconc] = []
        reconc_dict[leaf.reconc].append(leaf)
    
    if not return_list:
        return reconc_dict
    else:
        leaves = []
        for reconc, leaf_list in reconc_dict.items():
            for leaf in leaf_list:
                leaves.append(leaf)
        
        return reconc_dict, leaves
def reconstruct_reconc_from_graph(tree, G)

Reconstruct the reconciliations from a NetworkX Graph.

Parameters

tree : Tree
 
G : networkx.Graph
The graph from which labels and reconciliations shall be reconstructed.
Expand source code
def reconstruct_reconc_from_graph(tree, G):
    """Reconstruct the reconciliations from a NetworkX Graph.
    
    Parameters
    ----------
    tree : Tree
    G : networkx.Graph
        The graph from which labels and reconciliations shall be reconstructed.
    """
    
    for v in tree.preorder():
        if hasattr(v, 'label') and v.label in G:
            if 'reconc' in G.nodes[v.label]:
                if isinstance(G.nodes[v.label]['reconc'], list):
                    v.reconc = tuple(G.nodes[v.label]['reconc'])
                else:
                    v.reconc = G.nodes[v.label]['reconc']
            elif 'color' in G.nodes[v.label]:
                if isinstance(G.nodes[v.label]['color'], list):
                    v.reconc = tuple(G.nodes[v.label]['color'])
                else:
                    v.reconc = G.nodes[v.label]['color']
def reconstruct_timestamps(tree)

Reconstruct the timestamps.

Make the time stamps matching with the distance attribute. The root obtains time stamp 1.0, and all other node smaller time stamps such that the difference to the parent's time stamp is exactly dist.

Parameters

tree : Tree
A tree with nodes that have a dist attribute.
Expand source code
def reconstruct_timestamps(tree):
    """Reconstruct the timestamps.
    
    Make the time stamps matching with the distance attribute. The root
    obtains time stamp 1.0, and all other node smaller time stamps such
    that the difference to the parent's time stamp is exactly `dist`.
    
    Parameters
    ----------
    tree : Tree
        A tree with nodes that have a `dist` attribute.
    """
    
    tree.root.tstamp = 1.0
    for v in tree.preorder():
        if v.parent:
            v.tstamp = v.parent.tstamp - v.dist
def remove_planted_root(tree, inplace=True)

Remove the planted root of the tree (if existent).

Parameters

tree : Tree
The tree in which the planted root shall be removed.
inplace : bool, optional
If True, the tree is directly manipulated, otherwise a copy is created and the original tree remains untouched (the default is True).

Returns

Tree
The tree with the planted root removed (original instance or a new one depending on the inplace parameter).
Expand source code
def remove_planted_root(tree, inplace=True):
    """Remove the planted root of the tree (if existent).
    
    Parameters
    ----------
    tree : Tree
        The tree in which the planted root shall be removed.
    inplace : bool, optional
        If True, the tree is directly manipulated, otherwise a copy is created
        and the original tree remains untouched (the default is True).
    
    Returns
    -------
    Tree
        The tree with the planted root removed (original instance or a new one
        depending on the `inplace` parameter).
    """
    
    if not inplace:
        tree = tree.copy()
        
    # delete the root if the tree is planted
    if len(tree.root.children) == 1:
        new_root = tree.root.children[0]
        new_root.detach()
        tree.root = new_root
        new_root.dist = 0.0
        
    if not tree.root.children and not tree.root.label:
        # no surviving genes --> return empty tree
        tree.root = None
    
    return tree
def sorted_edges(tree)

List of edges (u,v) sorted by timestamp of u.

Return a list of all edges (u,v) sorted by timestamp of u beginning with the oldest (highest time stamp).

Parameters

tree : Tree
A phylogenetic tree with nodes that have a tstamp attribute.

Returns

list of pairs of TreeNode instances
Edges sorted by their timestamp.
Expand source code
def sorted_edges(tree):
    """List of edges (u,v) sorted by timestamp of u.
    
    Return a list of all edges (u,v) sorted by timestamp of u beginning
    with the oldest (highest time stamp).
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree with nodes that have a `tstamp` attribute.
        
    Returns
    -------
    list of pairs of TreeNode instances
        Edges sorted by their timestamp.
    """

    return sorted(tree.edges(),
                  key=lambda e: (e[0].tstamp, e[1].tstamp),
                  reverse=True)
def sorted_nodes(tree, oldest_to_youngest=True)

List of nodes sorted by timestamp.

Return a list of all nodes in the tree sorted by time stamp beginning with the oldest (highest time stamp). Optionally the order can be reversed.

Parameters

tree : Tree
A phylogenetic tree with nodes that have a tstamp attribute.
oldest_to_youngest : bool, optional
If True, the nodes are sorted from oldest to youngest, otherwise from youngest to oldest (the default is True).

Returns

list of TreeNode instances
Nodes sorted by their timestamp.
Expand source code
def sorted_nodes(tree, oldest_to_youngest=True):
    """List of nodes sorted by timestamp.
    
    Return a list of all nodes in the tree sorted by time stamp beginning
    with the oldest (highest time stamp). Optionally the order can be
    reversed.
    
    Parameters
    ----------
    tree : Tree
        A phylogenetic tree with nodes that have a `tstamp` attribute.
    oldest_to_youngest : bool, optional
        If True, the nodes are sorted from oldest to youngest, otherwise
        from youngest to oldest (the default is True).
        
    Returns
    -------
    list of TreeNode instances
        Nodes sorted by their timestamp.
    """
    
    return sorted(tree.preorder(),
                  key=lambda node: node.tstamp,
                  reverse=oldest_to_youngest)
def to_newick(tree, label=True, reconc=True, distance=True, label_inner=True, reconc_inner=False)

Return a Newick representation of the tree.

This function overrides the function of the parent class.

Parameters

label : bool, optional
If True, the Newick str contains the labels of the nodes (the default is True).
reconc : bool, optional
If True, the Newick str contains the reconciliations of the nodes in <[…]> brackets (the default is True).
distance : bool, optional
If True, the Newick str contains the distances of the nodes in standard :[…] notation (the default is True).
label_inner : bool, optional
If True, the Newick str also contains the labels of the inner nodes (the default is True).
reconc_inner : bool, optional
If True, the Newick str contains the reconciliations of the inner nodes (the default is False).

Returns

str
A Newick representation of the tree.
Expand source code
def to_newick(tree, label=True, reconc=True, distance=True,
                    label_inner=True, reconc_inner=False):
    """Return a Newick representation of the tree.
    
    This function overrides the function of the parent class.
    
    Parameters
    ----------
    label : bool, optional
        If True, the Newick str contains the labels of the nodes (the
        default is True).
    reconc : bool, optional
        If True, the Newick str contains the reconciliations of the nodes in
        <[...]> brackets (the default is True).
    distance : bool, optional
        If True, the Newick str contains the distances of the nodes in
        standard :[...] notation (the default is True).
    label_inner : bool, optional
        If True, the Newick str also contains the labels of the inner 
        nodes (the default is True).
    reconc_inner : bool, optional
        If True, the Newick str contains the reconciliations of the inner nodes
        (the default is False).
    
    Returns
    -------
    str
        A Newick representation of the tree.
    """
    
    def _to_newick(node):
        
        if not node.children:
            token = ''
            if label and hasattr(node, 'label'):
                token += str(node.label)
            if reconc and hasattr(node, 'reconc') and node.reconc:
                token += '<{}-{}>'.format(*node.reconc) \
                    if isinstance(node.reconc, (tuple, list)) \
                    else '<{}>'.format(node.reconc)
            if distance and hasattr(node, 'dist'):
                token += ":{}".format(node.dist)
            return token
        else:
            s = ''
            for child in node.children:
                s += _to_newick(child) + ','
            token = ''
            if label and hasattr(node, 'label') and label_inner:
                token += str(node.label)
            if reconc_inner and hasattr(node, 'reconc') and node.reconc:
                token += '<{}-{}>'.format(*node.reconc) \
                    if isinstance(node.reconc, (tuple, list)) \
                    else '<{}>'.format(node.reconc)
            if distance and hasattr(node, 'dist'):
                token += ':{}'.format(node.dist)
            return '({}){}'.format(s[:-1], token)
    
    
    if tree.root:
        return _to_newick(tree.root) + ';'
    else:
        return ';'
def topology_only(tree, inplace=True)

Reset reconciliations, distances, time stamps, transfer status, and inner labels.

Parameters

tree : Tree
A tree.
inplace : bool
If True, reset the attributes of this tree instance, otherwise make a copy first and modify the copy.

Returns

Tree
The original or a copy of the tree instance with reset attributes.
Expand source code
def topology_only(tree, inplace=True):
    """Reset reconciliations, distances, time stamps, transfer status, and
    inner labels.
    
    Parameters
    ----------
    tree : Tree
        A tree.
    inplace : bool
        If True, reset the attributes of this tree instance, otherwise
        make a copy first and modify the copy.
    
    Returns
    -------
    Tree
        The original or a copy of the tree instance with reset attributes.
    """
    
    if not inplace:
        T = tree.copy()
    else:
        T = tree
    
    for v in T.preorder():
        if v.children:
            v.label = ''
            v.reconc = None
        v.dist = 1.0
        v.tstamp = None
        v.transferred = 0
    
    return T