Skip to content

Commit

Permalink
Merge pull request #88 from EcoJulia/rr/output
Browse files Browse the repository at this point in the history
Allow export of trees
  • Loading branch information
richardreeve authored Dec 19, 2023
2 parents 27c0d92 + b778b54 commit 09d1431
Show file tree
Hide file tree
Showing 20 changed files with 953 additions and 469 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# NEWS

- v0.5.1
- Allow export of trees to nexus and newick format
- v0.5.0
- Add recursive tree, node and branch types
- Improve testing
Expand Down
6 changes: 4 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name = "Phylo"
uuid = "aea672f4-3940-5932-aa44-993d1c3ff149"
author = ["Richard Reeve <[email protected]>"]
version = "0.5.0"
author = ["Richard Reeve <[email protected]>",
"Michael Krabbe Borregaard <[email protected]>",
"Claire Harris <[email protected]>"]
version = "0.5.1"

[deps]
AxisArrays = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
Expand Down
63 changes: 51 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,21 @@
| **Documentation** | **Build Status** | **DOI** |
|:-----------------:|:--------------------------:|:--------------------------:|
| [![stable docs][docs-stable-img]][docs-stable-url] | [![build tests][actions-img]][actions-url] [![JuliaNightly][nightly-img]][nightly-url] | [![Zenodo][zenodo-badge]][zenodo-url] |
| [![dev docs][docs-dev-img]][docs-dev-url] | [![codecov][codecov-img]][codecov-url] [![coveralls][coveralls-img]][coveralls-url] | |
| [![dev docs][docs-dev-img]][docs-dev-url] | [![codecov][codecov-img]][codecov-url] | |

## Installation

The package is registered in the `General` registry so can be
installed with `add`. For example:

```julia
(@v1.6) pkg> add Phylo
Updating registry at `~/.julia/registries/General`
Updating git-repo `https://github.com/JuliaRegistries/General.git`
(@v1.9) pkg> add Phylo
Resolving package versions...
Updating `~/.julia/environments/v1.6/Project.toml`
[aea672f4] + Phylo v0.4.18
Updating `~/.julia/environments/v1.6/Manifest.toml`
Updating `~/.julia/environments/v1.9/Project.toml`
[aea672f4] + Phylo v0.5.0
Updating `~/.julia/environments/v1.9/Manifest.toml`

(@v1.6) pkg>
(@v1.9) pkg>
```

## Project Status
Expand Down Expand Up @@ -138,7 +136,10 @@ required (though they are currently working together reasonably successfully).

### Reading from a file

It can also read newick trees either from strings or files:
It can also read newick and nexus trees either from strings or files.
`parsenewick()` will read to the default tree type – currently the rooted,
polytomous, `RootedTree`, and the multiple tree version of it (`RootedTree`s
nested inside a `TreeSet`):

```julia
julia> using Phylo
Expand Down Expand Up @@ -195,6 +196,47 @@ Dict{String, Any} with 1 entry:
"lnP" => 1.0
```
Extensions to `Base.parse()` will allow you to be more precise in the tree type:
```julia
julia> tree = open(parse(RootedTree), Phylo.path("H1N1.newick"))
RootedTree with 507 tips and 1 root. Leaf names are 227, 294, 295, 110, 390, ... [501 omitted] ... and 418

1013 nodes: [RecursiveNode{OneRoot} 'Node 1013', a root node with 2 outbound connections (branches [1011, 1012]), RecursiveNode{OneRoot} 'Node 1011', an internal node with 1 inbound and 2 outbound connections (branches 1011 and [1009, 1010]), RecursiveNode{OneRoot} 'Node 1009', an internal node with 1 inbound and 2 outbound connections (branches 1009 and [1007, 1008]), RecursiveNode{OneRoot} 'Node 1008', an internal node with 1 inbound and 2 outbound connections (branches 1007 and [1005, 1006]), RecursiveNode{OneRoot} 'Node 1004', an internal node with 1 inbound and 2 outbound connections (branches 1005 and [1001, 1002]) ... 1007 missing ... RecursiveNode{OneRoot} '418', a leaf with an incoming connection (branch 1012)]

1012 branches: [RecursiveBranch{OneRoot} 1, from node 'Node 5' to node '294' (length 0.2559376385188), RecursiveBranch{OneRoot} 2, from node 'Node 5' to node '295' (length 1.255937638519), RecursiveBranch{OneRoot} 3, from node 'Node 7' to node '227' (length 3.093983613629), RecursiveBranch{OneRoot} 4, from node 'Node 7' to node 'Node 5' (length 4.83804597511), RecursiveBranch{OneRoot} 5, from node 'Node 11' to node '104' (length 0.4902870119746) ... 1006 missing ... RecursiveBranch{OneRoot} 1012, from node 'Node 1013' to node '418' (length 13.87884773144)]

Node records: "Node 1013" => Dict{String, Any}("length" => 0.0, "height" => 84.94613266277547, "height/95%/HPD" => [75.00016004016078, 100.9885305644122], "height/median" => 82.87499084796832, "posterior" => 1.0, "height/range" => [75.00016004016078, 151.06404614035887]) ... "418" => Dict{String, Any}("length/range" => [0.000160040160921, 76.06404614036], "height/median" => 75.00000000000021, "rate" => 0.00287656620693594, "rate/95%/HPD" => [0.00032906282418212297, 0.00668807772865533], "rate/median" => 0.002350371083836891, "length/median" => 7.87499084797, "length" => 9.946132662775383, "height" => 74.99999999999999, "height/95%/HPD" => [74.99999999999359, 75.0000000000068], "length/95%/HPD" => [0.000160040160921, 25.98853056441])

julia> open(parse(treesettype(RootedTree)), Phylo.path("H1N1.trees"))
[ Info: Created a tree called 'TREE1'
[ Info: Created a tree called 'TREE2'
TreeSet{String, OneRoot, String, RecursiveNode{OneRoot, String, Dict{String, Any}, Dict{String, Any}, PolytomousBranching, Float64}, RecursiveBranch{OneRoot, String, Dict{String, Any}, Dict{String, Any}, PolytomousBranching, Float64}, RootedTree} with 2 tree(s), each with 507 tips.
Tree names are TREE2 and TREE1. Dict("TREE2" => 1013, "TREE1" => 1013) nodes and Dict("TREE2" => 1012, "TREE1" => 1012) branches.

TREE2: RootedTree with 507 tips and 1 root. Leaf names are H1N1_A_BRAZIL_11_1978, H1N1_A_TAHITI_8_1998, H1N1_A_TAIWAN_1_1986, H1N1_A_BAYERN_7_1995, H1N1_A_ENGLAND_45_1998, ... [501 omitted] ... and H1N1_A_PUERTORICO_8_1934
TREE1: RootedTree with 507 tips and 1 root. Leaf names are H1N1_A_BRAZIL_11_1978, H1N1_A_TAHITI_8_1998, H1N1_A_TAIWAN_1_1986, H1N1_A_BAYERN_7_1995, H1N1_A_ENGLAND_45_1998, ... [501 omitted] ... and H1N1_A_PUERTORICO_8_1934
```
### Writing to a file
Trees can be written out either individually (using newick or nexus format), or
multiply using nexus format, all using `Base.write()`. By default single trees
will be written as newick and treesets will be written using nexus format:
```julia
julia> write("test.newick", tree)

julia> open(parsenewick, "test.newick")
RootedTree with 507 tips and 1 root. Leaf names are 227, 294, 295, 110, 390, ... [501 omitted] ... and 418

1013 nodes: [RecursiveNode{OneRoot} 'Node 1013', a root node with 2 outbound connections (branches [1011, 1012]), RecursiveNode{OneRoot} 'Node 1011', an internal node with 1 inbound and 2 outbound connections (branches 1011 and [1009, 1010]), RecursiveNode{OneRoot} 'Node 1009', an internal node with 1 inbound and 2 outbound connections (branches 1009 and [1007, 1008]), RecursiveNode{OneRoot} 'Node 1008', an internal node with 1 inbound and 2 outbound connections (branches 1007 and [1005, 1006]), RecursiveNode{OneRoot} 'Node 1004', an internal node with 1 inbound and 2 outbound connections (branches 1005 and [1001, 1002]) ... 1007 missing ... RecursiveNode{OneRoot} '418', a leaf with an incoming connection (branch 1012)]

1012 branches: [RecursiveBranch{OneRoot} 1, from node 'Node 5' to node '294' (length 0.2559376385188), RecursiveBranch{OneRoot} 2, from node 'Node 5' to node '295' (length 1.255937638519), RecursiveBranch{OneRoot} 3, from node 'Node 7' to node '227' (length 3.093983613629), RecursiveBranch{OneRoot} 4, from node 'Node 7' to node 'Node 5' (length 4.83804597511), RecursiveBranch{OneRoot} 5, from node 'Node 18' to node '390' (length 0.2307062432264) ... 1006 missing ... RecursiveBranch{OneRoot} 1012, from node 'Node 1013' to node '418' (length 13.87884773144)]

Node records: "Node 1013" => Dict{String, Any}("length" => 0.0, "height" => 84.94613266277547, "height/95%/HPD" => [75.00016004016078, 100.9885305644122], "height/median" => 82.87499084796832, "posterior" => 1.0, "height/range" => [75.00016004016078, 151.06404614035887]) ... "418" => Dict{String, Any}("length/range" => [0.000160040160921, 76.06404614036], "height/median" => 75.00000000000021, "rate" => 0.00287656620693594, "rate/95%/HPD" => [0.00032906282418212297, 0.00668807772865533], "rate/median" => 0.002350371083836891, "length/median" => 7.87499084797, "length" => 9.946132662775383, "height" => 74.99999999999999, "height/95%/HPD" => [74.99999999999359, 75.0000000000068], "length/95%/HPD" => [0.000160040160921, 25.98853056441])
```
### Calculating metrics
We so far only support calculating a few metrics on trees, but will gradually be added. Open an issue with a request!
Expand Down Expand Up @@ -352,9 +394,6 @@ julia> d = DataFrame(nodename=getnodename.(tree, traversal(tree, preorder)), tra
[nightly-img]: https://github.com/EcoJulia/Phylo.jl/actions/workflows/nightly.yaml/badge.svg
[nightly-url]: https://github.com/EcoJulia/Phylo.jl/actions/workflows/nightly.yaml
[coveralls-img]: https://img.shields.io/coveralls/EcoJulia/Phylo.jl.svg
[coveralls-url]: https://coveralls.io/r/EcoJulia/Phylo.jl?branch=dev
[codecov-img]: https://codecov.io/gh/EcoJulia/Phylo.jl/branch/dev/graph/badge.svg
[codecov-url]: https://codecov.io/gh/EcoJulia/Phylo.jl
Expand Down
4 changes: 2 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ makedocs(modules = [Phylo, Phylo.API],
"Tutorial / Quick start" => "tutorial.md",
"Manual" => Any[
"Phylogeny data types" => "man/treetypes.md",
"Creating phylogenies" => "man/input.md",
"Creating and writing phylogenies" => "man/io.md",
"Manipulating and building phylogenies" => "man/manipulating.md",
"Traversal and iterators" => "man/traversal.md",
"Getting phylogeny attributes" => "man/attributes.md",
Expand All @@ -17,7 +17,7 @@ makedocs(modules = [Phylo, Phylo.API],
"List of functions" => "functionlist.md",
"API" => "api.md"];
format = Documenter.HTML(size_threshold_ignore = ["man/plotting.md",
"man/input.md"]))
"man/io.md"]))

deploydocs(repo = "github.com/EcoJulia/Phylo.jl.git",
devbranch = "dev",
Expand Down
1 change: 1 addition & 0 deletions docs/src/man/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ validate!
invalidate!
branchdims
treetype
treesettype
```

## Methods on Nodes
Expand Down
42 changes: 36 additions & 6 deletions docs/src/man/input.md → docs/src/man/io.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,68 @@

Phylo can read newick trees either from strings,

```@example reading
```@example io
using Phylo
simpletree = parsenewick("((,Tip:1.0)Internal,)Root;")
```

which will result in the following tree:

```@example reading
```@example io
getbranches(simpletree)
```

or from files

```@example reading
```@example io
tree = open(parsenewick, Phylo.path("H1N1.newick"))
```

It can read nexus trees from files too:

```@example reading
```@example io
ts = open(parsenexus, Phylo.path("H1N1.trees"))
```

Reading multiple trees from a nexus file returns a `TreeSet` - index to get
the individual trees

```@example reading
```@example io
gettreeinfo(ts)
```

```@example reading
```@example io
ts["TREE1"]
```

## Writing phylogenies to disk

Phylo can write newick trees either to strings,

```@example io
out = Phylo.outputtree(simpletree, Newick())
```

or to files

```@example io
Phylo.write("test.newick", simpletree)
```

It can write nexus trees to files too:

```@example io
Phylo.write("h1.trees", ts)
```

It will use newick as the default format for OneTree trees (e.g. a RecursiveTree),
and nexus for ManyTrees trees (e.g. a TreeSet). However, you can tell it to use nexus
for a OneTree:

```@example io
Phylo.write("test.trees", simpletree, format = Nexus())
```

## Creating random phylogenies

The package can be used to generate random trees using the framework from
Expand Down Expand Up @@ -119,4 +147,6 @@ parsenewick
parsenexus
Nonultrametric
Ultrametric
Newick
Nexus
```
5 changes: 4 additions & 1 deletion docs/src/man/traversal.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Traversing and iterating over trees

The code also provides iterators, and filtered iterators over the
branches, nodes, branchnames and nodenames of a tree (using the random tree from
[Creating phylogenies](input.md))
[Creating and writing phylogenies](io.md))

```@example random_trees
using Phylo
nu = Nonultrametric(5);
Expand All @@ -15,6 +17,7 @@ collect(nodenamefilter(isroot, tree))
```

TreeSets are iterators themselves

```@example random_trees
trees = rand(nu, ["Tree 1", "Tree 2"])
collect(trees)
Expand Down
53 changes: 13 additions & 40 deletions src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ Returns a tree - either itself if it is a single tree, or the single tree
in a set with label id. Must be implemented for any ManyTrees type.
"""
function _gettree end
_gettree(treepair::Pair{Label, <: AbstractTree{OneTree}}) where Label =
treepair[2]
@traitfn function _gettree(tree::T,
id::TN) where {T <: AbstractTree{OneTree}, TN;
MatchTreeNameType{T, TN}}
Expand Down Expand Up @@ -302,39 +300,27 @@ _nroots(tree::AbstractTree{OneTree, Unrooted}) = 0
"""
_getnode(::AbstractTree, id)
Returns the node or name associated with id (which could be a name,
a node or a pair) from a tree. Must be implemented for any PreferNodeObjects
Returns the node or name associated with id (which could be a name or
a node) from a tree. Must be implemented for any PreferNodeObjects
tree and node label type.
"""
function _getnode end
@traitfn _getnode(tree::T,
node::N) where {RT, NL, N,
T <: AbstractTree{OneTree, RT, NL, N};
PreferNodeObjects{T}} = node
@traitfn _getnode(tree::T,
pair::Pair{NL, N}) where {RT, NL, N,
T <: AbstractTree{OneTree,
RT, NL, N};
PreferNodeObjects{T}} = pair[2]
@traitfn _getnode(tree::T,
nodename::NL) where {RT, NL,
T <: AbstractTree{OneTree, RT, NL};
!PreferNodeObjects{T}} = nodename
@traitfn _getnode(tree::T,
pair::Pair{NL, N}) where {RT, NL, N,
T <: AbstractTree{OneTree,
RT, NL, N};
!PreferNodeObjects{T}} = pair[1]

"""
_getnodename(::AbstractTree, id)
Returns the name of a node associated with id (which could be a name, a node
or a pair) from a tree. Must be implemented for PreferNodeObjects tree types.
Returns the name of a node associated with id (which could be a name or a node)
from a tree. Must be implemented for PreferNodeObjects tree types.
"""
function _getnodename end
_getnodename(::AbstractTree{OneTree, RT, NL, N},
pair::Pair{NL, N}) where {RT, NL, N} = pair[1]
_getnodename(::AbstractTree{OneTree, RT, NL, N},
nodename::NL) where {RT, NL, N} = nodename

Expand Down Expand Up @@ -367,30 +353,19 @@ function _deletenode! end
"""
_getbranch(::AbstractTree, id)
Returns the branch or name associated with id (which could be a name,
a branch or a pair) from a tree. Must be implemented for any PreferBranchObjects
Returns the branch or name associated with id (which could be a name or
a branch) from a tree. Must be implemented for any PreferBranchObjects
tree and branch label type.
"""
function _getbranch end
@traitfn _getbranch(::T,
branch::B) where {RT, NL, N, B,
T <: AbstractTree{OneTree, RT, NL, N, B};
PreferBranchObjects{T}} = branch
@traitfn _getbranch(::T,
pair::Pair{Int, B}) where {RT, NL, N, B,
T <: AbstractTree{OneTree, RT,
NL, N, B};
PreferBranchObjects{T}} = pair[2]
@traitfn _getbranch(::T,
branchname::Int) where {T <: AbstractTree{OneTree};
!PreferBranchObjects{T}} =
branchname
@traitfn _getbranch(::T,
pair::Pair{Int, B}) where {RT, NL, N, B,
T <: AbstractTree{OneTree,
RT, NL, N, B};
!PreferBranchObjects{T}} =
pair[1]
@traitfn _getbranch(tree::T, src::N1, dst::N2) where
{T <: AbstractTree{OneTree}, N1, N2; MatchNodeTypes{T, N1, N2}} =
first(b for b in _getbranches(tree) if _src(tree, b) == src && _dst(tree, b) == dst)
Expand All @@ -402,12 +377,10 @@ function _getbranch end
"""
_getbranchname(::AbstractTree, id)
Returns the name of a branch associated with id (which could be a name, a branch
or a pair) from a tree. Must be implemented for PreferBranchObjects tree types.
Returns the name of a branch associated with id (which could be a name or a branch)
from a tree. Must be implemented for PreferBranchObjects tree types.
"""
function _getbranchname end
_getbranchname(::AbstractTree{OneTree, RT, NL, N},
pair::Pair{NL, N}) where {RT, NL, N} = pair[1]
_getbranchname(::AbstractTree{OneTree}, id::Int) = id

"""
Expand Down Expand Up @@ -768,10 +741,10 @@ AbstractNode subtype, can be inferred from _getinbound and _getoutbounds for
a rooted node.
"""
function _getconnections end
_getconnections(tree::AbstractTree{OneTree, <: Rooted}, node) =
_hasinbound(tree, node) ?
append!([_getinbound(tree, node)], _getoutbounds(tree, node)) :
_getoutbounds(tree, node)
_getconnections(tree::AbstractTree{OneTree, <: Rooted}, node, exclude) =
filter((exclude), _hasinbound(tree, node) ?
append!([_getinbound(tree, node)], _getoutbounds(tree, node)) :
_getoutbounds(tree, node))

"""
_getsiblings(tree::AbstractTree, node::AbstractNode)
Expand All @@ -786,7 +759,7 @@ _getsiblings(tree::AbstractTree{OneTree, <: Rooted}, node) =
append!([_getparent(tree, node)], _getchildren(tree, node)) :
_getchildren(tree, node)
_getsiblings(tree::AbstractTree{OneTree, Unrooted}, node) =
[_conn(tree, branch, node) for branch in _getconnections(tree, node)]
[_conn(tree, branch, node) for branch in _getconnections(tree, node, [])]

"""
_addconnection!(tree::AbstractTree, node::AbstractNode, branch)
Expand Down
Loading

0 comments on commit 09d1431

Please sign in to comment.