Preserve root branch length in Phylo.TreeMixin.prune

This commit is contained in:
Michael M
2024-01-22 20:12:06 -06:00
committed by Peter Cock
parent 1605da01a7
commit d5459bb627
5 changed files with 39 additions and 17 deletions

View File

@ -660,8 +660,8 @@ class TreeMixin:
def prune(self, target=None, **kwargs): def prune(self, target=None, **kwargs):
"""Prunes a terminal clade from the tree. """Prunes a terminal clade from the tree.
If taxon is from a bifurcation, the connecting node will be collapsed If the taxon is from a bifurcation, the connecting node will be collapsed
and its branch length added to remaining terminal node. This might be no and its branch length added to remaining terminal node. This might no
longer be a meaningful value. longer be a meaningful value.
:returns: parent clade of the pruned target :returns: parent clade of the pruned target
@ -679,17 +679,14 @@ class TreeMixin:
parent.clades.remove(path[-1]) parent.clades.remove(path[-1])
if len(parent) == 1: if len(parent) == 1:
# We deleted a branch from a bifurcation # We deleted a branch from a bifurcation
if parent == self.root: child = parent.clades[0]
if child.branch_length is not None:
child.branch_length += parent.branch_length or 0.0
if len(path) == 1:
# If we're at the root, move the root upwards # If we're at the root, move the root upwards
# NB: This loses the length of the original branch parent = self.root = child
newroot = parent.clades[0]
newroot.branch_length = None
parent = self.root = newroot
else: else:
# If we're not at the root, collapse this parent # If we're not at the root, collapse this parent
child = parent.clades[0]
if child.branch_length is not None:
child.branch_length += parent.branch_length or 0.0
if len(path) < 3: if len(path) < 3:
grandparent = self.root grandparent = self.root
else: else:

View File

@ -352,7 +352,7 @@ function:
>>> Phylo.write(tree1, "tree1.nwk", "newick") >>> Phylo.write(tree1, "tree1.nwk", "newick")
1 1
>>> Phylo.write(trees, "other_trees.xml", "phyloxml") # write the remaining trees >>> Phylo.write(trees, "other_trees.xml", "phyloxml") # write the remaining trees
12 13
Convert files between any of the supported formats with the ``convert`` Convert files between any of the supported formats with the ``convert``
function: function:
@ -364,7 +364,7 @@ function:
>>> Phylo.convert("tree1.nwk", "newick", "tree1.xml", "nexml") >>> Phylo.convert("tree1.nwk", "newick", "tree1.xml", "nexml")
1 1
>>> Phylo.convert("other_trees.xml", "phyloxml", "other_trees.nex", "nexus") >>> Phylo.convert("other_trees.xml", "phyloxml", "other_trees.nex", "nexus")
12 13
To use strings as input or output instead of actual files, use To use strings as input or output instead of actual files, use
``StringIO`` as you would with SeqIO and AlignIO: ``StringIO`` as you would with SeqIO and AlignIO:
@ -681,7 +681,7 @@ tree intact, make a complete copy of the tree first, using Pythons
sort clades deepest-to-shallowest. sort clades deepest-to-shallowest.
``prune`` ``prune``
Prunes a terminal clade from the tree. If taxon is from a Prunes a terminal clade from the tree. If the taxon is from a
bifurcation, the connecting node will be collapsed and its branch bifurcation, the connecting node will be collapsed and its branch
length added to remaining terminal node. This might no longer be a length added to remaining terminal node. This might no longer be a
meaningful value. meaningful value.

View File

@ -408,6 +408,26 @@
</clade> </clade>
</clade> </clade>
</phylogeny> </phylogeny>
<phylogeny rooted="true">
<clade>
<branch_length>0.04</branch_length>
<clade>
<branch_length>0.06</branch_length>
<clade>
<name>A</name>
<branch_length>0.102</branch_length>
</clade>
<clade>
<name>B</name>
<branch_length>0.23</branch_length>
</clade>
</clade>
<clade>
<name>C</name>
<branch_length>0.4</branch_length>
</clade>
</clade>
</phylogeny>
<align:alignment xmlns:align="http://example.org/align"> <align:alignment xmlns:align="http://example.org/align">
<seq name="A">acgtcgcggcccgtggaagtcctctcct</seq> <seq name="A">acgtcgcggcccgtggaagtcctctcct</seq>
<seq name="B">aggtcgcggcctgtggaagtcctctcct</seq> <seq name="B">aggtcgcggcctgtggaagtcctctcct</seq>

View File

@ -150,7 +150,7 @@ class IOTests(unittest.TestCase):
trees = Phylo.parse("PhyloXML/phyloxml_examples.xml", "phyloxml") trees = Phylo.parse("PhyloXML/phyloxml_examples.xml", "phyloxml")
with tempfile.NamedTemporaryFile(mode="w") as out_handle: with tempfile.NamedTemporaryFile(mode="w") as out_handle:
count = Phylo.write(trees, out_handle, "phyloxml") count = Phylo.write(trees, out_handle, "phyloxml")
self.assertEqual(13, count) self.assertEqual(14, count)
def test_convert_phyloxml_filename(self): def test_convert_phyloxml_filename(self):
"""Write phyloxml to a given filename.""" """Write phyloxml to a given filename."""
@ -162,7 +162,7 @@ class IOTests(unittest.TestCase):
count = Phylo.write(trees, tmp_filename, "phyloxml") count = Phylo.write(trees, tmp_filename, "phyloxml")
finally: finally:
os.remove(tmp_filename) os.remove(tmp_filename)
self.assertEqual(13, count) self.assertEqual(14, count)
def test_int_labels(self): def test_int_labels(self):
"""Read newick formatted tree with numeric labels.""" """Read newick formatted tree with numeric labels."""
@ -471,6 +471,7 @@ class MixinTests(unittest.TestCase):
tree = self.phylogenies[1] tree = self.phylogenies[1]
parent = tree.prune(name="C") parent = tree.prune(name="C")
self.assertEqual(parent, tree.root) self.assertEqual(parent, tree.root)
self.assertEqual(tree.root.branch_length, 0.06)
self.assertEqual(len(parent.clades), 2) self.assertEqual(len(parent.clades), 2)
for clade, name, blen in zip(parent, "AB", (0.102, 0.23)): for clade, name, blen in zip(parent, "AB", (0.102, 0.23)):
self.assertTrue(clade.is_terminal()) self.assertTrue(clade.is_terminal())
@ -478,6 +479,10 @@ class MixinTests(unittest.TestCase):
self.assertAlmostEqual(clade.branch_length, blen) self.assertAlmostEqual(clade.branch_length, blen)
self.assertEqual(len(tree.get_terminals()), 2) self.assertEqual(len(tree.get_terminals()), 2)
self.assertEqual(len(tree.get_nonterminals()), 1) self.assertEqual(len(tree.get_nonterminals()), 1)
# Taxon just below root
tree = self.phylogenies[13]
parent = tree.prune(name="C")
self.assertEqual(tree.root.branch_length, 0.1)
def test_split(self): def test_split(self):
"""TreeMixin: split() method.""" """TreeMixin: split() method."""

View File

@ -96,13 +96,13 @@ class ParseTests(unittest.TestCase):
test_read_apaf = _test_read_factory(EX_APAF, (1, 0)) test_read_apaf = _test_read_factory(EX_APAF, (1, 0))
test_read_bcl2 = _test_read_factory(EX_BCL2, (1, 0)) test_read_bcl2 = _test_read_factory(EX_BCL2, (1, 0))
test_read_made = _test_read_factory(EX_MADE, (6, 0)) test_read_made = _test_read_factory(EX_MADE, (6, 0))
test_read_phylo = _test_read_factory(EX_PHYLO, (13, 1)) test_read_phylo = _test_read_factory(EX_PHYLO, (14, 1))
test_read_dollo = _test_read_factory(EX_DOLLO, (1, 0)) test_read_dollo = _test_read_factory(EX_DOLLO, (1, 0))
test_parse_apaf = _test_parse_factory(EX_APAF, 1) test_parse_apaf = _test_parse_factory(EX_APAF, 1)
test_parse_bcl2 = _test_parse_factory(EX_BCL2, 1) test_parse_bcl2 = _test_parse_factory(EX_BCL2, 1)
test_parse_made = _test_parse_factory(EX_MADE, 6) test_parse_made = _test_parse_factory(EX_MADE, 6)
test_parse_phylo = _test_parse_factory(EX_PHYLO, 13) test_parse_phylo = _test_parse_factory(EX_PHYLO, 14)
test_parse_dollo = _test_parse_factory(EX_DOLLO, 1) test_parse_dollo = _test_parse_factory(EX_DOLLO, 1)
# lvl-2 clades, sub-clade counts, lvl-3 clades # lvl-2 clades, sub-clade counts, lvl-3 clades