Package {phylowise}


Title: Phylogenetic Pairwise Contrasts
Version: 0.0.1
Maintainer: Jordan Douglas <jordan.douglas@auckland.ac.nz>
Author: Jordan Douglas [aut, cre], Lindell Bromham [aut]
Description: A phylogenetic comparative method for finding associations between biological traits and molecular evolutionary rates. The method samples pairs from a phylogeny such that each pair has non-overlapping edge paths, and can therefore be treated as statistically independent observations. Linear regression is performed on the pair contrasts. This approach is similar to phylogenetically independent contrasts (PIC) but without reconstructing the traits at internal nodes, and is better suited for finding trait-rate associations than phylogenetic generalised least squares (PGLS). Refer to Douglas and Bromham (2026) <doi:10.64898/2026.08.13.744736> for further details.
License: GPL (≥ 3)
Encoding: UTF-8
RoxygenNote: 8.0.0
URL: https://github.com/jordandouglas/phylowise
Depends: ape, BMA, phylotate
Imports: Rcpp
LinkingTo: Rcpp
NeedsCompilation: yes
Packaged: 2026-08-16 11:28:21 UTC; jdou557
Repository: CRAN
Date/Publication: 2026-08-21 13:40:02 UTC

phylowise: Phylogenetic Pairwise Contrasts

Description

A phylogenetic comparative method for finding associations between biological traits and molecular evolutionary rates. The method samples pairs from a phylogeny such that each pair has non-overlapping edge paths, and can therefore be treated as statistically independent observations. Linear regression is performed on the pair contrasts. This approach is similar to phylogenetically independent contrasts (PIC) but without reconstructing the traits at internal nodes, and is better suited for finding trait-rate associations than phylogenetic generalised least squares (PGLS). Refer to Douglas and Bromham (2026) doi:10.64898/2026.08.13.744736 for further details.

Author(s)

Maintainer: Jordan Douglas jordan.douglas@auckland.ac.nz

Authors:

See Also

Useful links:


Phylogenetic pairwise contrast test on a series of taxon pairs, using ordinary least squares and Bayesian model averaging (BMA). This method does not do multivariate regression, but it returns the data frame that can be used for it.

Description

Phylogenetic pairwise contrast test on a series of taxon pairs, using ordinary least squares and Bayesian model averaging (BMA). This method does not do multivariate regression, but it returns the data frame that can be used for it.

Usage

PPC.test(
  pairs.df,
  standardise = 2,
  logY = TRUE,
  extreme.value.threshold = 0,
  prior.weight = 0.5,
  OR = 1000,
  epsilon = 1e-06
)

Arguments

pairs.df

data frame of taxon pairs

standardise

should we standardise the evolutionary distance? 0 for no, 1 to standardise by sqrt(distance), 2 to standardise by sqrt(mrca age), 3 to standarise by mrca age. default and recommended setting: 2

logY

should we take the logarithm of the evolutionary distance? default and recommended setting: true

extreme.value.threshold

remove any observations more than this many standard deviations away from the mean response variable; set to zero for no extreme value removal

prior.weight

trait inclusion prior probbaility for Bayesian model averaging (see BMA::bic.glm)

OR

Occam's window for Bayesian model averaging (see BMA::bic.glm)

epsilon

precision of BMA probabilities for Bayes factor calculation: p < epsilon and p > 1-epsilon will be respectively set to epsilon and 1-epsilon to avoid NaN calculations

Value

A vector of p-values and Pearson correlations (one element per trait), a data frame of standardised and logged datapoints for doing regression, and posterior probabilities / Bayes factors from BMA analyses

Examples

# Sample a birth-death tree with 100 taxa
time.tree <- ape::rphylo(birth=10, death=5, n=100)
ntips = length(time.tree$tip.label)

# Simulate traits down the tree under Brownian motion
traits1 <- simulateTrait(time.tree)
traits1.leaf <- traits1[1:ntips]

# Simulate substitutions that have a positive association with traits
sim.result <- simulateSubstitutions(time.tree=time.tree, 
									beta=2, 
									theta=10, 
									traits=traits1, 
									method="TD", 
									number.of.subst=10000)
subst.tree <- sim.result$subst.tree.est

# Sample taxon pairs within a window of (0.01, 0.2) time units
pairs.df <- sampleTaxonPairs(subst.tree=subst.tree, 
							covariate=traits1.leaf, 
							window.tree=time.tree, 
							dist.min=0.01, dist.max=0.2)

# Perform linear regression on the pairs
ppc <- PPC.test(pairs.df)
p.value <- ppc$p.var
pearson <- ppc$rho.var
inclusion.prob <- ppc$bma.probs
bayes.factor <- ppc$bma.bf

# Plot the pairs. Should see a positive trend under these parameters
data.df <- ppc$data.df
plot(data.df$trait, data.df$distance.response, xlab="Trait contrast", ylab="Distance contrast")

Build a distance matrix from a tree (time tree or substitution tree)

Description

Build a distance matrix from a tree (time tree or substitution tree)

Usage

getDistanceMatrix(tree)

Arguments

tree

a tree (phylo object)

Value

A distance matrix, where dij is the mean distance from leaf i and j to their ancestor

Examples

# Sample a coalescent tree with 10 taxa and then build a distance matrix
tree <- ape::rcoal(10)
dmat <- getDistanceMatrix(tree)

Plot a set of sampled pairs onto a tree

Description

Plot a set of sampled pairs onto a tree

Usage

plotPairs(
  tree,
  pairs.df,
  edge.col = "red",
  show.tip.label = FALSE,
  edge.width = 1,
  edge.width.pairs = 3,
  label.cex = 1
)

Arguments

tree

the binary rooted tree used to get samples

pairs.df

a data frame of pairs, obtained using phylowise::sampleTaxonPairs

edge.col

the pairs will be highlighted in this colour

show.tip.label

display tip labels on the tree?

edge.width

edge line width of all branches on the tree

edge.width.pairs

edge line width of paired branches

label.cex

tree tip label font size, if show.tip.label=TRUE

Value

No return value, function is called to make a plot

Examples

# Sample a birth-death tree with 100 taxa
time.tree <- ape::rphylo(birth=10, death=5, n=100)
ntips = length(time.tree$tip.label)

# Simulate traits down the tree under Brownian motion
traits1 <- simulateTrait(time.tree)
traits1.leaf <- traits1[1:ntips]

# Simulate substitutions that have a positive association with traits
sim.result <- simulateSubstitutions(time.tree=time.tree, 
									beta=1, 
									theta=10, 
									traits=traits1, 
									method="TD", 
									number.of.subst=10000)
subst.tree <- sim.result$subst.tree.est

# Sample taxon pairs within a window of (0.01, 0.2) time units
pairs.df <- sampleTaxonPairs(subst.tree=subst.tree, 
							covariate=traits1.leaf, 
							window.tree=time.tree, 
							dist.min=0.01, dist.max=0.2)

# Plot the pairs onto the substitution tree
plotPairs(subst.tree, pairs.df, show.tip.label=TRUE)

# Plot the pairs onto the time tree
plotPairs(time.tree, pairs.df, show.tip.label=TRUE, edge.col="#008cba")

Read a single summary tree or a posterior distribution of annotated nexus trees generated by the BEAST software suites, and apply burn-in. Built on top of the phylotate::read_annotated function.

Description

Read a single summary tree or a posterior distribution of annotated nexus trees generated by the BEAST software suites, and apply burn-in. Built on top of the phylotate::read_annotated function.

Usage

readBeastTrees(nexus.file, burnin = 0.1)

Arguments

nexus.file

a nexus file

burnin

proportion of burn-in to apply to the file (default: 0.1)

Value

A list of trees

Examples

# Read in the small mammal.trees file generated by BEAST, available in the phylowise package
treefile <- system.file("extdata", "mammals.trees", package = "phylowise")
trees <- readBeastTrees(treefile, burnin=0)
tree1 <- trees[[1]]
plot(tree1)
print(tree1$node.comment)

Sample a set of taxon pairs from the tree. These taxa will have non-overlapping edges between their paths and can this be treated as statistically independent. The algorithm iteratively searches for two random taxa that satisfy sampling requirements until there are no more valid pairs. Each pair must descend from an MRCA with dist.min <= tMRCA <= dist.max. c++ is used to speed up the runtime of this code.

Description

Sample a set of taxon pairs from the tree. These taxa will have non-overlapping edges between their paths and can this be treated as statistically independent. The algorithm iteratively searches for two random taxa that satisfy sampling requirements until there are no more valid pairs. Each pair must descend from an MRCA with dist.min <= tMRCA <= dist.max. c++ is used to speed up the runtime of this code.

Usage

sampleTaxonPairs(
  subst.tree,
  covariate,
  dist.min,
  dist.max = Inf,
  response = NULL,
  nested = TRUE,
  maximise = TRUE,
  youngest = FALSE,
  window.tree = subst.tree,
  distance.matrix = NULL,
  verbose = FALSE
)

Arguments

subst.tree

a binary rooted tree, with branch lengths in units of change (phylo object)

covariate

a data frame of traits at the tips of the tree (rows are taxa, columns are traits); rows should be in the same order as tips in the tree

dist.min

minimum distance that two tips must be apart from their ancestor (mean of both distances), on window.tree

dist.max

maximum distance that two tips must be apart from their ancestor (mean of both distances), on window.tree

response

provide a vector as a response trait instead of genetic distances in the tree (optional)

nested

can taxon pairs be nested with each other?

maximise

should we maximise the trait difference?

youngest

take the youngest pair at each step (and therefore increase the number of pairs)?

window.tree

tree that is the basis for building the distance matrix, if it is not provided; should have same taxa as 'tree'

distance.matrix

provide an n x n distance matrix rather than recalculate from scratch

verbose

print some statements along the way

Value

A data frame, where each row is a pair of taxa.

Examples

# Sample a birth-death tree with 100 taxa
time.tree <- ape::rphylo(birth=10, death=5, n=100)
ntips = length(time.tree$tip.label)

# Simulate traits down the tree under Brownian motion
traits1 <- simulateTrait(time.tree)
traits1.leaf <- traits1[1:ntips]

# Simulate substitutions that have a positive association with traits
sim.result <- simulateSubstitutions(time.tree=time.tree, 
									beta=1, 
									theta=10, 
									traits=traits1, 
									method="TD", 
									number.of.subst=10000)
subst.tree <- sim.result$subst.tree.est

# Sample taxon pairs within a window of (0.01, 0.2) time units
pairs.df <- sampleTaxonPairs(subst.tree=subst.tree, 
							covariate=traits1.leaf, 
							window.tree=time.tree, 
							dist.min=0.01, dist.max=0.2)

# If iterating this, we can precompute the distance matrix to save time
dmat <- getDistanceMatrix(time.tree)
for (i in 1:100){
    pairs.df <- sampleTaxonPairs(subst.tree=subst.tree, 
								covariate=traits1.leaf, 
								distance.matrix=dmat, 
								dist.min=0.01, 
								dist.max=0.2)
}

# We can also do this with traits-vs-traits rather than rates-vs-traits
traits2 <- simulateTrait(time.tree) # Unassociated with traits1
traits2.leaf <- traits2[1:ntips]
pairs.df <- sampleTaxonPairs(subst.tree=subst.tree, 
							response=traits1.leaf, 
							covariate=traits2.leaf, 
							window.tree=time.tree, 
							dist.min=0.01, 
							dist.max=0.2)

Simulate a substitution count down each branch of a tree. First, the branch rates are sampled from a correlated, uncorrelated or OU process that can be dependent on traits. Then the number of substitutions along each branch is sampled from a Poisson distribution.

Description

Simulate a substitution count down each branch of a tree. First, the branch rates are sampled from a correlated, uncorrelated or OU process that can be dependent on traits. Then the number of substitutions along each branch is sampled from a Poisson distribution.

Usage

simulateSubstitutions(
  time.tree,
  nu = 0.5,
  sigma = 0.5,
  beta = 0,
  theta = 1,
  traits = NULL,
  number.of.subst = 1000,
  method = c("AC", "UCLN", "TD")
)

Arguments

time.tree

a binary rooted tree (phylo object)

nu

variance scale of autocorrelated clock or TD

sigma

standard deviation of UCLN

beta

effect size of traits on rates (if traits is not NULL)

theta

theta term for OU process in TD

traits

one trait per node; set to NULL if rates are conditionally independent of traits

number.of.subst

expected number of substitutions per unit of time

method

clock model may be uncorrelated lognormal (UCLN), autocorrelated lognormal (AC), or trait dependent (TD)

Value

Two trees, with branch lengths set to either subst. rates or counts, and two vectors, one of true branch rates and one of estimated branch rates (i.e., count divided by time, which can evaluate to zero)

Examples

# Sample a birth-death tree with 50 taxa
time.tree <- ape::rphylo(birth=10, death=5, n=50)

# Simulate traits down the tree under Brownian motion
traits <- simulateTrait(time.tree)

# Simulate substitutions that have a positive association with traits 
sim.result <- simulateSubstitutions(time.tree=time.tree, 
								beta=1, 
								theta=10, 
								sigma=0.5, 
								traits=traits, 
								method="TD", 
								number.of.subst=10000)
subst.tree <- sim.result$subst.tree.est
node.rates <- sim.result$node.rates.est
plot(subst.tree)
axis(1)

# Simulate substitutions with no association with traits (Brownian)
sim.result.brownian <- simulateSubstitutions(time.tree=time.tree, 
									sigma=0.5, 
									method="AC", 
									number.of.subst=10000)

Simulate a trait down a tree using Brownian motion (BM)

Description

Simulate a trait down a tree using Brownian motion (BM)

Usage

simulateTrait(time.tree, sigma = 1, root.value = 0)

Arguments

time.tree

a binary rooted tree (phylo object)

sigma

standard deviation for BM

root.value

trait value at the root of the tree

Value

A vector of traits, one element for each node in the tree, ordered by node number

Examples

# Sample a birth-death tree with 50 taxa
time.tree <- ape::rphylo(birth=10, death=5, n=50)

# Simulate traits down the tree under Brownian motion
traits <- simulateTrait(time.tree)