Skip to content

Commit 6b5828e

Browse files
authored
near v0.16.0 (#196)
addAlternativeHybridizations!: several bugs fixed, tests added. directionalconflict: removed unused argument net (API change, but in internal function). fixed links in doc.
1 parent b256b1a commit 6b5828e

9 files changed

+131
-98
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PhyloNetworks"
22
uuid = "33ad39ac-ed31-50eb-9b15-43d0656eaa72"
33
license = "MIT"
4-
version = "0.15.3"
4+
version = "0.16.0"
55

66
[deps]
77
BioSequences = "7e6ae17a-c86d-528c-b3b9-7f778a29fe59"

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ with or without transgressive evolution after reticulations:
7070
Systematic Biology, 67(5):800–820.
7171
[doi:10.1093/sysbio/syy033](https://doi.org/10.1093/sysbio/syy033).
7272
SI on [dryad](http://dx.doi.org/10.5061/dryad.nt2g6)
73-
including a [tutorial for trait evolution](https://datadryad.org/bitstream/handle/10255/dryad.177752/xiphophorus_PCM_analysis.html?sequence=1)
74-
and a [tutorial for network calibration](https://datadryad.org/bitstream/handle/10255/dryad.177754/xiphophorus_networks_calibration.html?sequence=1).
73+
including a tutorial for trait evolution
74+
and a tutorial for network calibration.
7575

7676
Continuous traits, accounting for within-species variation:
7777

src/addHybrid.jl

+4-5
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ function addhybridedge!(net::HybridNetwork, nohybridladder::Bool, no3cycle::Bool
103103
end
104104
hybridpartnernew = (fixroot ? true : rand() > 0.2) # if true: partner hybrid = new edge above edge 2
105105
## check that the new network will be a DAG: no directional conflict
106-
if directionalconflict(net, p1, edge2, hybridpartnernew)
106+
if directionalconflict(p1, edge2, hybridpartnernew)
107107
if fixroot # don't try to change the direction of edge2
108108
push!(blacklist, (e1,e2))
109109
continue
110110
end # else: try harder: change direction of edge2 and move root
111111
hybridpartnernew = !hybridpartnernew # try again with opposite
112-
if directionalconflict(net, p1, edge2, hybridpartnernew)
112+
if directionalconflict(p1, edge2, hybridpartnernew)
113113
push!(blacklist, (e1,e2))
114114
continue
115115
end # else: switching hybridpartnernew worked
@@ -231,8 +231,7 @@ function hybrid3cycle(edge1::Edge, edge2::Edge)
231231
end
232232

233233
"""
234-
directionalconflict(net::HybridNetwork, parent::Node, edge::Edge,
235-
hybridpartnernew::Bool)
234+
directionalconflict(parent::Node, edge::Edge, hybridpartnernew::Bool)
236235
237236
Check if creating a hybrid edge down of `parent` node into the middle of `edge`
238237
would create a directed cycle in `net`, i.e. not a DAG. The proposed hybrid
@@ -243,7 +242,7 @@ Does *not* modify the network.
243242
244243
Output: `true` if a conflict would arise (non-DAG), `false` if no conflict.
245244
"""
246-
function directionalconflict(net::HybridNetwork, parent::Node, edge2::Edge, hybridpartnernew::Bool)
245+
function directionalconflict(parent::Node, edge2::Edge, hybridpartnernew::Bool)
247246
if hybridpartnernew # all edges would retain their directions: use isChild1 fields
248247
c2 = getchild(edge2)
249248
return parent === c2 || isdescendant(parent, c2)

src/addHybrid_snaq.jl

+100-66
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,10 @@ chooseEdgesGamma(net::HybridNetwork) = chooseEdgesGamma(net, false, net.edge)
141141
chooseEdgesGamma(net::HybridNetwork, blacklist::Bool) = chooseEdgesGamma(net, blacklist, net.edge)
142142

143143
# aux function for addHybridization
144-
# that takes the output edge1, edge2, gamma from
145-
# chooseEdgesGamma and created necessary edges
144+
# that takes the output edge1, edge2.
146145
# returns edge3, edge4, and adjusts edge1, edge2 to shorter length
146+
# fixit: problem if edge1 or edge2 have a missing length, coded as -1.0.
147+
# would be best to set lengths of e3, e4 to 0.0, and leave lengths of e1,e2 unchanged
147148
function parameters4createHybrid!(edge1::Edge, edge2::Edge,net::HybridNetwork)
148149
max_edge = maximum([e.number for e in net.edge]);
149150
t1 = rand()*edge1.length;
@@ -343,21 +344,21 @@ end
343344
addHybridizationUpdateSmart!(net::HybridNetwork, N::Integer) = addHybridizationUpdateSmart!(net, false,N)
344345

345346

346-
# ----------------------------------- add alternative hybridizations found in bootstrap ------------------------------------
347+
# --- add alternative hybridizations found in bootstrap
347348
"""
348349
addAlternativeHybridizations!(net::HybridNetwork, BSe::DataFrame;
349350
cutoff=10::Number, top=3::Int)
350351
351-
Modify the network `net` (the best network estimated with snaq) by adding other hybridizations
352-
that are present in the bootstrap networks. By default, it will only consider hybrid edges with
353-
more than 10% bootstrap support (`cutoff`) and it will only include the three top hybridizations
354-
(`top`) sorted by bootstrap support.
352+
Modify the network `net` (the best estimated network) by adding some of
353+
the hybridizations present in the bootstrap networks. By default, it will only
354+
add hybrid edges with more than 10% bootstrap support (`cutoff`) and it will
355+
only include the top 3 hybridizations (`top`) sorted by bootstrap support.
355356
356-
The function also modifies the dataframe `BSe`. In the original `BSe`,
357+
The dataframe `BSe` is also modified. In the original `BSe`,
357358
supposedly obtained with `hybridBootstrapSupport`, hybrid edges that do not
358359
appear in the best network have a missing number.
359-
After the hybrid edges are added with `addAlternativeHybridizations`, `BSe` is modified to include the
360-
edge numbers of the newly added hybrid edges.
360+
After hybrid edges from bootstrap networks are added,
361+
`BSe` is modified to include the edge numbers of the newly added hybrid edges.
361362
To distinguish hybrid edges present in the original network versus new edges,
362363
an extra column of true/false values is also added to `BSe`, named "alternative",
363364
with true for newly added edges absent from the original network.
@@ -367,76 +368,109 @@ major tree topology.
367368
368369
# example
369370
370-
```julia
371-
bootnet = readMultiTopology("bootstrap-networks.txt")
372-
bestnet = readTopology("best.tre")
373-
BSn, BSe, BSc, BSgam, BSedgenum = hybridBootstrapSupport(bootnet, bestnet);
374-
addAlternativeHybridizations!(bestnet,BSe)
375-
using PhyloPlots
376-
plot(bestnet, edgelabel=BSe[[:edge,:BS_hybrid_edge]])
371+
```jldoctest
372+
julia> bootnet = readMultiTopology(joinpath(dirname(pathof(PhyloNetworks)), "..","examples", "bootsnaq.out")); # vector of 10 networks
373+
374+
julia> bestnet = readTopology("((O,(E,#H7:::0.196):0.314):0.332,(((A)#H7:::0.804,B):10.0,(C,D):10.0):0.332);");
375+
376+
julia> BSn, BSe, BSc, BSgam, BSedgenum = hybridBootstrapSupport(bootnet, bestnet);
377+
378+
julia> BSe[1:6,[:edge,:hybrid_clade,:sister_clade,:BS_hybrid_edge]]
379+
6×4 DataFrame
380+
Row │ edge hybrid_clade sister_clade BS_hybrid_edge
381+
│ Int64? String String Float64
382+
─────┼─────────────────────────────────────────────────────
383+
1 │ 7 H7 B 33.0
384+
2 │ 3 H7 E 32.0
385+
3 │ missing c_minus3 c_minus8 44.0
386+
4 │ missing c_minus3 H7 44.0
387+
5 │ missing E O 12.0
388+
6 │ missing c_minus6 c_minus8 9.0
389+
390+
julia> PhyloNetworks.addAlternativeHybridizations!(bestnet, BSe)
391+
392+
julia> BSe[1:6,[:edge,:hybrid_clade,:sister_clade,:BS_hybrid_edge,:alternative]]
393+
6×5 DataFrame
394+
Row │ edge hybrid_clade sister_clade BS_hybrid_edge alternative
395+
│ Int64? String String Float64 Bool
396+
─────┼──────────────────────────────────────────────────────────────────
397+
1 │ 7 H7 B 33.0 false
398+
2 │ 3 H7 E 32.0 false
399+
3 │ 16 c_minus3 c_minus8 44.0 true
400+
4 │ 19 c_minus3 H7 44.0 true
401+
5 │ 22 E O 12.0 true
402+
6 │ missing c_minus6 c_minus8 9.0 false
403+
404+
julia> # using PhyloPlots; plot(bestnet, edgelabel=BSe[:,[:edge,:BS_hybrid_edge]]);
377405
```
378406
"""
379407
function addAlternativeHybridizations!(net::HybridNetwork,BSe::DataFrame; cutoff=10::Number,top=3::Int)
380408
top > 0 || error("top must be greater than 0")
381-
BSe[:alternative] = falses(length(BSe[:hybrid]))
382-
newBSe = BSe[BSe[:BS_hybrid_edge] .> cutoff,:]
383-
newBSe = newBSe[.!ismissing.(newBSe[:hybrid]) & .!ismissing.(newBSe[:sister]),:]
384-
newBSe = newBSe[ismissing.(newBSe[:edge]),:]
385-
newHyb = newBSe[1:top,:]
386-
387-
if(size(newHyb,1) == 0)
388-
@warn "Did not find any alternative hybridizations with bootstrap support greater than the cutoff, so nothign added"
409+
BSe[!,:alternative] = falses(nrow(BSe))
410+
newBSe = subset(BSe,
411+
:BS_hybrid_edge => x -> x.> cutoff, :edge => ByRow( ismissing),
412+
:hybrid => ByRow(!ismissing), :sister => ByRow(!ismissing),
413+
)
414+
top = min(top,nrow(newBSe))
415+
if top==0
416+
@info "no alternative hybridizations with support > cutoff $cutoff%, so nothing added."
389417
return
390418
end
391-
392419
for i in 1:top
393-
hybnum = newHyb[:hybrid][i]
394-
sisnum = newHyb[:sister][i]
395-
edgenum = addHybridBetweenClades!(hybnum,sisnum,net)
396-
ind1 = findall(x->!ismissing(x) && x==hybnum,BSe[:hybrid])
397-
ind2 = findall(x->!ismissing(x) && x==sisnum,BSe[:sister])
420+
hybnum = newBSe[i,:hybrid]
421+
sisnum = newBSe[i,:sister]
422+
edgenum = addHybridBetweenClades!(net, hybnum, sisnum)
423+
if isnothing(edgenum)
424+
@warn "cannot add desired hybrid (BS=$(newBSe[i,:BS_hybrid_edge])): the network would have a directed cycle"
425+
continue
426+
end
427+
ind1 = findall(x->!ismissing(x) && x==hybnum, BSe[!,:hybrid])
428+
ind2 = findall(x->!ismissing(x) && x==sisnum, BSe[!,:sister])
398429
ind = intersect(ind1,ind2)
399-
BSe[ind,:edge] = edgenum
400-
BSe[ind,:alternative] = true
430+
BSe[ind,:edge] .= edgenum
431+
BSe[ind,:alternative] .= true
401432
end
402433
end
403434

404435

405-
## function to a hybrid edge between hybrid clade (hybnum = node number) and sister clade (sisnum = node number)
406-
## the function finds the parent edges to these nodes, and puts a hybrid edge between them
407-
## the function modifies net, and it returns the number of the minor hybrid edge added
408-
function addHybridBetweenClades!(hybnum::Number,sisnum::Number,net::HybridNetwork)
409-
hybind = getIndexNode(hybnum,net)
410-
sisind = getIndexNode(sisnum,net)
436+
"""
437+
addHybridBetweenClades!(net::HybridNetwork, hybnum::Number, sisnum::Number)
411438
412-
## hybridization ed1->ed2
413-
edge1 = getparentedge(net.node[sisind]) # major parent edges
414-
edge2 = getparentedge(net.node[hybind])
439+
Modify `net` by adding a minor hybrid edge from "donor" to "recipient",
440+
where "donor" is the major parent edge `e1` of node number `hybnum` and
441+
"recipient" is the major parent edge `e2` of node number `sisnum`.
442+
The new nodes are currently inserted at the middle of these parent edges.
415443
416-
edge3,edge4 = parameters4createHybrid!(edge1, edge2,net)
417-
hybridnode = createHybrid!(edge1, edge2, edge3, edge4, net, 0.1) ## gamma=0.1, fixed later
418-
if(edge1.length < 0)
419-
setBranchLength!(edge1,-1.0)
420-
setBranchLength!(edge3,-1.0)
421-
end
422-
if(edge2.length < 0)
423-
setBranchLength!(edge2,-1.0)
424-
setBranchLength!(edge4,-1.0)
425-
end
444+
If a hybrid edge from `e1` to `e2` would create a directed cycle in the network,
445+
then this hybrid cannot be added.
446+
In that case, the donor edge `e1` is moved up if its parent is a hybrid node,
447+
to ensure that the sister clade to the new hybrid would be a desired (the
448+
descendant taxa from `e1`) and a new attempt is made to create a hybrid edge.
426449
427-
if(edge2.isChild1)
428-
edge4.hybrid = true
429-
setGamma!(edge4,0.9)
430-
edge4.isChild1 = true
431-
else
432-
edge2.hybrid = true
433-
setGamma!(edge2,0.9)
434-
edge2.isChild1 = false
450+
Output: number of the new hybrid edge, or `nothing` if the desired hybridization
451+
is not possible.
452+
453+
See also:
454+
[`addhybridedge!`](@ref) (used by this method) and
455+
[`directionalconflict`](@ref) to check that `net` would still be a DAG.
456+
"""
457+
function addHybridBetweenClades!(net::HybridNetwork, hybnum::Number, sisnum::Number)
458+
hybind = getIndexNode(hybnum,net)
459+
sisind = getIndexNode(sisnum,net)
460+
e1 = getparentedge(net.node[sisind]) # major parent edges
461+
e2 = getparentedge(net.node[hybind])
462+
p1 = getparent(e1)
463+
if directionalconflict(p1, e2, true) # then: first try to move the donor up
464+
# so long as the descendant taxa (= sister clade) remain the same
465+
while p1.hybrid
466+
e1 = getparentedge(p1) # major parent edge: same descendant taxa
467+
p1 = getparent(e1)
468+
end
469+
directionalconflict(p1, e2, true) && return nothing
435470
end
436-
## used gamma=0.1 to make the new edge a minor edge, but we really do not have gamma value:
437-
emaj = getparentedge(hybridnode)
438-
emaj.gamma = -1
439-
e = getparentedgeminor(hybridnode)
440-
e.gamma = -1
441-
return e.number
471+
hn, he = addhybridedge!(net, e1, e2, true) # he: missing length & gamma by default
472+
# ideally: add option "where" to breakedge!, used by addhybridedge!
473+
# so as to place the new nodes at the base of each clade.
474+
# currently: the new nodes are inserted at the middle of e1 and e2.
475+
return he.number
442476
end

src/auxiliary.jl

+3-8
Original file line numberDiff line numberDiff line change
@@ -576,11 +576,13 @@ end
576576

577577
"""
578578
deleteNode!(net::HybridNetwork, n::Node)
579+
deleteNode!(net::QuartetNetwork, n::Node)
579580
580581
Delete node `n` from a network, i.e. removes it from
581582
net.node, and from net.hybrid or net.leaf as appropriate.
582583
Update attributes `numNodes`, `numTaxa`, `numHybrids`.
583-
Warning: `net.names` is *not* updated.
584+
Warning: `net.names` is *not* updated, and this is a feature (not a bug)
585+
for networks of type QuartetNetwork.
584586
585587
Warning: if the root is deleted, the new root is arbitrarily set to the
586588
first node in the list. This is intentional to save time because this function
@@ -606,13 +608,6 @@ function deleteNode!(net::HybridNetwork, n::Node)
606608
end
607609
end
608610

609-
# function to delete a Node in net.node and
610-
# update numNodes and numTaxa for QuartetNetwork
611-
# if hybrid node, it deletes also from net.hybrid
612-
# and updates numHybrids
613-
# note that net.names is never updated to keep it
614-
# accurate
615-
# if n is leaf, we delete from qnet.leaf
616611
function deleteNode!(net::QuartetNetwork, n::Node)
617612
index = findfirst(no -> no.number == n.number, net.node)
618613
# isEqual (from above) checks for more than node number

src/deprecated.jl

+10-9
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@
1414

1515
function Base.getproperty(mm::PhyloNetworkLinearModel, f::Symbol)
1616
if f === :model
17-
Base.depwarn("accessing the `model` field of PhyloNetworkLinearModel.jl models is deprecated, " *
18-
"as they are no longer wrapped in a `TableRegressionModel` " *
19-
"and can be used directly now", :getproperty)
17+
Base.depwarn("""accessing the 'model' field of PhyloNetworkLinearModel's is
18+
deprecated, as they are no longer wrapped in a TableRegressionModel.
19+
They can be used directly now.""",
20+
:getproperty) # force=true to show warning. currently silent by default
2021
return mm
2122
elseif f === :mf
22-
Base.depwarn("accessing the `mf` field of PhyloNetworkLinearModel.jl models is deprecated, " *
23-
"as they are no longer wrapped in a `TableRegressionModel`." *
24-
"Use `formula(m)` to access the model formula.", :getproperty)
23+
Base.depwarn("""accessing the 'mf' field of PhyloNetworkLinearModel's is
24+
deprecated, as they are no longer wrapped in a TableRegressionModel.
25+
Use `formula(m)` to access the model formula.""", :getproperty)
2526
form = formula(mm)
2627
return ModelFrame{Nothing, typeof(mm)}(form, nothing, nothing, typeof(mm))
2728
elseif f === :mm
28-
Base.depwarn("accessing the `mm` field of PhyloNetworkLinearModel.jl models is deprecated, " *
29-
"as they are no longer wrapped in a `TableRegressionModel`." *
30-
"Use `modelmatrix(m)` to access the model matrix.", :getproperty)
29+
Base.depwarn("""accessing the 'mm' field of PhyloNetworkLinearModel's is
30+
deprecated, as they are no longer wrapped in a TableRegressionModel.
31+
Use `modelmatrix(m)` to access the model matrix.""", :getproperty)
3132
modmatr = modelmatrix(mm)
3233
return ModelMatrix{typeof(modmatr)}(modmatr, Int[])
3334
else

src/types.jl

-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,6 @@ mutable struct QuartetNetwork <: Network
342342
function QuartetNetwork(net::HybridNetwork)
343343
net2 = deepcopy(net); #fixit: maybe we dont need deepcopy of all, maybe only arrays
344344
new(net2.numTaxa,net2.numNodes,net2.numEdges,net2.node,net2.edge,net2.hybrid,net2.leaf,net2.numHybrids, [true for e in net2.edge],[],-1,[], -1.,net2.names,Int8[-1,-1,-1,-1],Int8[-1,-1,-1],[0,0,0],[],true,[])
345-
#new(sum([n.leaf?1:0 for n in net.node]),size(net.node,1),size(net.edge,1),copy(net.node),copy(net.edge),copy(net.hybrid),size(net.hybrid,1), [true for e in net2.edge],[],-1,[],-1.,net2.names,[-1,-1,-1,-1],[-1,-1,-1],[],true,[])
346345
end
347346
QuartetNetwork() = new(0,0,0,[],[],[],[],0,[],[],-1,[],-1.0,[],[],[],[],[],true,[])
348347
end

test/test_addHybrid.jl

+5-5
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ netl1 = readTopology(str_level1);
7070
# directional: throws error if the new network would not be a DAG, e.g. if edge 1 is a directed descendant of edge 2
7171
# case 6
7272
nodeS145 = PhyloNetworks.getparent(netl1.edge[6])
73-
@test PhyloNetworks.directionalconflict(netl1, nodeS145, netl1.edge[15], true)
73+
@test PhyloNetworks.directionalconflict(nodeS145, netl1.edge[15], true)
7474
# case 2
75-
@test PhyloNetworks.directionalconflict(netl1, nodeS145, netl1.edge[18], true)
75+
@test PhyloNetworks.directionalconflict(nodeS145, netl1.edge[18], true)
7676
# case 3 (bad hybrid edge choice leads to a nonDAG)
77-
@test PhyloNetworks.directionalconflict(netl1, nodeS145, netl1.edge[20], true)
78-
@test PhyloNetworks.directionalconflict(netl1, nodeS145, netl1.edge[4], false)
79-
@test !PhyloNetworks.directionalconflict(netl1, nodeS145, netl1.edge[4], true)
77+
@test PhyloNetworks.directionalconflict(nodeS145, netl1.edge[20], true)
78+
@test PhyloNetworks.directionalconflict(nodeS145, netl1.edge[4], false)
79+
@test !PhyloNetworks.directionalconflict(nodeS145, netl1.edge[4], true)
8080
end # of edge checking functions

test/test_bootstrap.jl

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ bootnet = readMultiTopology(joinpath(exdir,"fish3hyb_20boostrap.net"));
1111
# include(string(home, "bootstrap.jl"))
1212
resn, rese, resc, gam, edgenum = hybridBootstrapSupport(bootnet,bestnet);
1313
#@show resn; @show rese; showall(gam); @show edgenum; resc
14-
# plot(bestnet, showIntNodeNumber=true)
14+
# plot(bestnet, shownodenumber=true);
1515

1616
@test resn[1:2,:clade] == ["H26","H25"]
1717
@test resn[1:2,:BS_hybrid_samesisters] == [25.0,100.0]
@@ -35,6 +35,11 @@ resn, rese, resc, gam, edgenum = hybridBootstrapSupport(bootnet,bestnet);
3535
@test gam[:,2] == [.0,.0,.192,.0,.0,.0,.0,.0,.193,.0,.184,.193,.0,.0,.0,.0,.0,.193,.0,.0]
3636
@test gam[:,4] == [.165,.166,.165,.166,.165,.165,.166,.165,.165,.166,.164,.166,.166,.165,.165,.165,.166,.165,.166,.166]
3737
@test edgenum ==[25,39,43,7]
38+
39+
PhyloNetworks.addAlternativeHybridizations!(bestnet, rese, cutoff=4)
40+
@test rese[[5,10,11],:edge] == [54,57,60]
41+
@test ismissing(rese[6,:edge])
42+
@test length(bestnet.hybrid) == 5
3843
end # of testset, hybridBootstrapSupport
3944

4045
@testset "bootsnaq from quartet CF intervals" begin

0 commit comments

Comments
 (0)