Skip to content

Commit

Permalink
Added options --root and --tips to collapse brlength, support, and de…
Browse files Browse the repository at this point in the history
…pth commands #12
  • Loading branch information
fredericlemoine committed Jan 14, 2021
1 parent b7b35cd commit d3b615a
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 31 deletions.
14 changes: 12 additions & 2 deletions cmd/collapsebrlen.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@ import (
)

var shortbranchesThreshold float64
var shortbranchesRemoveRoot bool
var shortbranchesRemoveTips bool

// collapseCmd represents the collapse command
var collapsebrlenCmd = &cobra.Command{
Use: "length",
Short: "Collapse short branches of the input tree",
Long: `Collapse short branches of the input tree.
Short branches are defined by a threshold (-l). All branches
Short branches are defined by a threshold (-l). All internal branches
with length <= threshold are removed.
If --root is given, then it applies also to internal branches connected to the root in the case
of rooted trees. This may unroot the tree. In that case, so far the two branches connected to the root are
considered independently whereas it may be more useful to consider them as a single bipartition if the
tree is going to be unrooted.
If --tips is given, then it applies also to external branches, just by setting their length to 0.0
`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
var f *os.File
Expand All @@ -43,7 +51,7 @@ with length <= threshold are removed.
io.LogError(t.Err)
return t.Err
}
t.Tree.CollapseShortBranches(shortbranchesThreshold)
t.Tree.CollapseShortBranches(shortbranchesThreshold, shortbranchesRemoveRoot, shortbranchesRemoveTips)
f.WriteString(t.Tree.Newick() + "\n")
}
return
Expand All @@ -53,4 +61,6 @@ with length <= threshold are removed.
func init() {
collapseCmd.AddCommand(collapsebrlenCmd)
collapsebrlenCmd.Flags().Float64VarP(&shortbranchesThreshold, "length", "l", 0.0, "Length cutoff to collapse branches")
collapsebrlenCmd.Flags().BoolVar(&shortbranchesRemoveRoot, "root", false, "Applies also to branches connected to the root (may unroot the tree)")
collapsebrlenCmd.Flags().BoolVar(&shortbranchesRemoveTips, "tips", false, "Applies also to tips (keeps a 0.0 length tip)")
}
14 changes: 11 additions & 3 deletions cmd/collapsedepth.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,26 @@ import (

var maxdepthThreshold int
var mindepthThreshold int
var collapseDepthRoot bool
var collapseDepthTips bool

// collapsedepthCmd represents the collapsedepth command
var collapsedepthCmd = &cobra.Command{
Use: "depth",
Short: "Collapse branches having a given depth",
Long: `Collapse branches having a given depth.
Branches having depth (number of taxa on the lightest side of
the bipartition) d such that:
Removes internal branches (not connected to the root in case of rooted trees)
having depth (number of taxa on the lightest side of the bipartition) d such that:
min-depth<=d<=max-depth
will be collapsed.
If --root is given, then it applies also to internal branches connected to the root in the case
of rooted trees. This may unroot the tree.
If --tips is given, then it applies also to external branches (if min-depth<=1), just by setting their length to 0.0
`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
var f *os.File
Expand Down Expand Up @@ -53,7 +59,7 @@ will be collapsed.
return
}

t.Tree.CollapseTopoDepth(mindepthThreshold, maxdepthThreshold)
t.Tree.CollapseTopoDepth(mindepthThreshold, maxdepthThreshold, collapseDepthRoot, collapseDepthTips)
f.WriteString(t.Tree.Newick() + "\n")
}
return
Expand All @@ -65,4 +71,6 @@ func init() {

collapsedepthCmd.Flags().IntVarP(&mindepthThreshold, "min-depth", "m", 0, "Min depth cutoff to collapse branches")
collapsedepthCmd.Flags().IntVarP(&maxdepthThreshold, "max-depth", "M", 0, "Max Depth cutoff to collapse branches")
collapsedepthCmd.Flags().BoolVar(&collapseDepthRoot, "root", false, "Applies also to branches connected to the root (may unroot the tree)")
collapsedepthCmd.Flags().BoolVar(&collapseDepthTips, "tips", false, "Applies also to tips (keeps a 0.0 length tip)")
}
15 changes: 11 additions & 4 deletions cmd/collapsesupport.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ import (
)

var lowSupportThreshold float64
var supportRemoveRoot bool

// collapsesupportCmd represents the collapsesupport command
var collapsesupportCmd = &cobra.Command{
Use: "support",
Short: "Collapse lowly supported branches of the input tree",
Long: `Collapse lowly supported branches of the input tree.
Lowly supported branches are defined by a threshold (-s). All branches
with support < threshold are removed.
`,
Lowly supported branches are defined by a threshold (-s). All internal branches
with support < threshold and that are not connected to the root in case of rooted tree
are removed.
If --root is given, then it applies also to internal branches connected to the root in the case
of rooted trees. This may unroot the tree.
`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
var f *os.File
var treefile goio.Closer
Expand All @@ -38,7 +43,7 @@ with support < threshold are removed.
io.LogError(t.Err)
return t.Err
}
t.Tree.CollapseLowSupport(lowSupportThreshold)
t.Tree.CollapseLowSupport(lowSupportThreshold, supportRemoveRoot)
f.WriteString(t.Tree.Newick() + "\n")
}
return
Expand All @@ -48,4 +53,6 @@ with support < threshold are removed.
func init() {
collapseCmd.AddCommand(collapsesupportCmd)
collapsesupportCmd.Flags().Float64VarP(&lowSupportThreshold, "support", "s", 0.0, "Support cutoff to collapse branches")
collapsesupportCmd.Flags().BoolVar(&supportRemoveRoot, "root", false, "Applies also to branches connected to the root (may unroot the tree)")

}
5 changes: 4 additions & 1 deletion docs/commands/collapse.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Commands

### collapse
This command removes branches from a set of input trees. Three subcommands :
This command removes branches from a set of input trees. They apply only to internal branches, and not to branches connected to the root (for rooted trees). Three subcommands :
* `gotree collapse length` will remove branches whose length is less than or equal to the specified length;
* `gotree collapse support` will remove branches whose support is less than the specified support;
* `gotree collapse depth` will remove branches whose depth is between (or equal to) given min and max depths. Here, depth is defined as the number of taxa on the lightest side of the branch.
Expand All @@ -17,6 +17,9 @@ This command removes branches from a set of input trees. Three subcommands :
t2 t2
```

- If option `--tips` is given, length of tips matching the threshold will be set to 0.0 (for `depth` and `length` commands).
- If option `--root` is given, branches connected to the root (in the case of rooted trees) and matching the threshold will be removed (for `depth`, `length`, and `support` commands). Resulting tree may be unrooted. Also, so far the two branches connected to the root are considered independently whereas it may be more useful to consider them as a single bipartition if the tree is going to be unrooted.

#### Usage

General command
Expand Down
26 changes: 26 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,32 @@ ${GOTREE} generate yuletree --seed 10 | ${GOTREE} collapse length -l 0.05 | ${GO
diff -q -b result expected
rm -f expected result

echo "->gotree collapse support root + tips"
cat > expected <<EOF
(A:1,B:1,C:1,D:1);
EOF
echo "((A:1,B:1)0.2:1,(C:1,D:1)0.1:1);" | ${GOTREE} collapse support -s 1 --root > result
diff -q -b result expected
rm -f expected result


echo "->gotree collapse length root + tips"
cat > expected <<EOF
(A:0,B:0,C:0,D:0);
EOF
echo "((A:1,B:1)0.2:1,(C:1,D:1)0.1:1);" | ${GOTREE} collapse length -l 2 --root --tips > result
diff -q -b result expected
rm -f expected result


echo "->gotree collapse depth root + tips"
cat > expected <<EOF
(A:0,B:0,C:0,D:0);
EOF
echo "((A:1,B:1)0.2:1,(C:1,D:1)0.1:1);" | ${GOTREE} collapse depth -m 0 -M 10 --root --tips > result
diff -q -b result expected
rm -f expected result


# gotree collapse support
echo "->gotree collapse support"
Expand Down
99 changes: 97 additions & 2 deletions tests/collapse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package tests

import (
"fmt"
"github.com/evolbioinfo/gotree/io/newick"
"strings"
"testing"

"github.com/evolbioinfo/gotree/io/newick"
)

var treestring string = "(Tip2:1.00000,Node0:1.0000,((Tip7:1.00000,((Tip9:1.00000,Tip6:1.0000):1.0000,(Tip5:1.00000,Tip3:1.0000):1.0000):1.00):1.00,(Tip4:1.00000,(Tip8:1.00000,Tip1:1.000):0.126):0.127):0.125);"
Expand All @@ -25,7 +26,7 @@ func TestCollapse(t *testing.T) {
t.Error(fmt.Sprintf("The sum of branch lengths before collapse is not (%f)", sumlen))
}

tree.CollapseShortBranches(0.126)
tree.CollapseShortBranches(0.126, false, false)
edges = tree.Edges()
sumlen = tree.SumBranchLengths()

Expand All @@ -38,6 +39,100 @@ func TestCollapse(t *testing.T) {
}
}

var treestring3 string = "((A:1,B:1)0.2:1,(C:1,D:1)0.1:1);"

func TestCollapse2(t *testing.T) {
tree, err2 := newick.NewParser(strings.NewReader(treestring3)).Parse()

if err2 != nil {
t.Error(err2)
}

edges := tree.Edges()
sumlen := tree.SumBranchLengths()
if len(edges) != 6 {
t.Error(fmt.Sprintf("The number of edges before collapse is not 6 (%d)", len(edges)))
}
if sumlen != 6 {
t.Error(fmt.Sprintf("The sum of branch lengths before collapse is not 6 (%f)", sumlen))
}

tree.CollapseShortBranches(2, true, true)
edges = tree.Edges()
sumlen = tree.SumBranchLengths()

if len(edges) != 4 {
t.Error(fmt.Sprintf("The number of edges after collapse is not 4 (%d)", len(edges)))
}

if sumlen != 0 {
t.Error(fmt.Sprintf("The sum of branch lengths after collapse is not 0 (%f)", sumlen))
}
}

func TestCollapse3(t *testing.T) {
tree, err2 := newick.NewParser(strings.NewReader(treestring3)).Parse()

if err2 != nil {
t.Error(err2)
}

edges := tree.Edges()
sumlen := tree.SumBranchLengths()
if len(edges) != 6 {
t.Error(fmt.Sprintf("The number of edges before collapse is not 6 (%d)", len(edges)))
}
if sumlen != 6 {
t.Error(fmt.Sprintf("The sum of branch lengths before collapse is not 6 (%f)", sumlen))
}

tree.CollapseLowSupport(0.5, true)
edges = tree.Edges()
sumlen = tree.SumBranchLengths()

if len(edges) != 4 {
t.Error(fmt.Sprintf("The number of edges after collapse is not 4 (%d)", len(edges)))
}

if sumlen != 4 {
t.Error(fmt.Sprintf("The sum of branch lengths after collapse is not 4 (%f)", sumlen))
}
}

func TestCollapse5(t *testing.T) {
tree, err2 := newick.NewParser(strings.NewReader(treestring3)).Parse()
if err2 != nil {
t.Error(err2)
}

if err2 = tree.ReinitIndexes(); err2 != nil {
t.Error(err2)
}

edges := tree.Edges()
sumlen := tree.SumBranchLengths()
if len(edges) != 6 {
t.Error(fmt.Sprintf("The number of edges before collapse is not 6 (%d)", len(edges)))
}
if sumlen != 6 {
t.Error(fmt.Sprintf("The sum of branch lengths before collapse is not 6 (%f)", sumlen))
}

if err2 = tree.CollapseTopoDepth(0, 10, true, true); err2 != nil {
t.Error(err2)
}
edges = tree.Edges()
sumlen = tree.SumBranchLengths()

if len(edges) != 4 {
t.Error(fmt.Sprintf("The number of edges after collapse is not 4 (%d)", len(edges)))
}

if sumlen != 0 {
t.Error(fmt.Sprintf("The sum of branch lengths after collapse is not 0 (%f)", sumlen))
}
}

var treestring2 string = "(A:1,(B:1):1,C:1);"

func TestCollapseSingle(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions tests/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestCollapseDepth(t *testing.T) {
t.Error(err)
}

if err = tr.CollapseTopoDepth(2, 3); err != nil {
if err = tr.CollapseTopoDepth(2, 3, false, false); err != nil {
t.Error(err)
}
if tr.Newick() != "(Tip4,Tip0,Tip3,Tip2,Tip1);" {
Expand All @@ -96,7 +96,7 @@ func TestCollapseLength(t *testing.T) {
if err != nil {
t.Error(err)
}
tr.CollapseShortBranches(0.01)
tr.CollapseShortBranches(0.01, false, false)
if tr.Newick() != "(Tip4:0.1,Tip0:0.1,(Tip3:0.1,Tip2:0.2,Tip1:0.2):0.4);" {
t.Error(fmt.Sprintf("Tree after collapse lengths is not valid: %s", tr.Newick()))
}
Expand All @@ -108,7 +108,7 @@ func TestCollapseSupport(t *testing.T) {
if err != nil {
t.Error(err)
}
tr.CollapseLowSupport(0.7)
tr.CollapseLowSupport(0.7, false)
if tr.Newick() != "(Tip4,Tip0,(Tip3,Tip2,Tip1)0.9);" {
t.Error(fmt.Sprintf("Tree after collapse support is not valid: %s", tr.Newick()))
}
Expand Down
Loading

0 comments on commit d3b615a

Please sign in to comment.