make ultrametric
Make Trees Ultrametric (Penalized Likelihood)¶
This page summarizes the penalized likelihood approaches available in
toytree.mod for adjusting edge lengths to make a tree ultrametric.
These methods aim to balance model fit with penalties that discourage
excessive rate variation.
Clock model (edges_make_ultrametric_pl_clock)¶
Assumes a strict molecular clock (single rate across all branches). This is the most constrained model and typically yields the smoothest ultrametric tree. Use this when you expect rate constancy or want the simplest correction.
Discrete rates model (edges_make_ultrametric_pl_discrete)¶
Allows a small number of discrete rate categories across branches. This model accommodates heterogeneity while still limiting complexity by restricting the number of rates.
Relaxed rates model (edges_make_ultrametric_pl_relaxed)¶
Allows branch-specific rates with a penalty that discourages abrupt rate changes. This is the most flexible option and is often used when rate variation is expected across the tree.
Notes¶
All methods optimize edge lengths subject to a penalty term, so results depend on the chosen model and penalty settings. If you have calibration constraints, include them in the input parameters to guide the fit.
Parameter notes¶
Most penalized likelihood functions accept optimization controls such as
max_iter, max_fun, and max_refine. Increasing these can improve
convergence or solution quality, but also increases runtime. Use higher
values if the fit fails to converge or if you suspect local optima.
Additional model-specific parameters:
ncategories(discrete model): number of rate categories. Larger values increase flexibility but also complexity.lam(relaxed model): strength of the rate-variation penalty. Larger values impose stronger smoothing of rates across branches.calibrations: constraints on node ages. Calibrations can strongly influence the inferred ultrametric tree.
If convergence warnings appear, try increasing max_iter / max_fun,
and consider adjusting lam or ncategories.
Runnable examples (30 tips)¶
import toytree
tree = toytree.rtree.unittree(30, seed=42)
# strict clock
clock = tree.mod.edges_make_ultrametric_pl_clock()
c1, a1, m1 = clock.draw(layout='r', tip_labels=False, scale_bar=True)
c1
# discrete rates
discrete = tree.mod.edges_make_ultrametric_pl_discrete()
c2, a2, m2 = discrete.draw(layout='r', tip_labels=False, scale_bar=True)
c2
# relaxed rates
relaxed = tree.mod.edges_make_ultrametric_pl_relaxed()
c3, a3, m3 = relaxed.draw(layout='r', tip_labels=False, scale_bar=True)
c3
Model selection¶
Use PHIIC to compare model fit across clock, discrete, and relaxed penalized likelihood fits. Lower PHIIC indicates a better tradeoff between fit and complexity.
# Grid search for discrete ncategories and relaxed lam
ncategories_grid = [2, 3, 4, 5]
lam_grid = [0.1, 0.5, 1.0, 2.0, 5.0]
phiic_discrete = {}
for k in ncategories_grid:
fit = tree.mod.edges_make_ultrametric_pl_discrete(full=True, ncategories=k)
phiic_discrete[k] = fit['PHIIC']
phiic_relaxed = {}
for lam in lam_grid:
fit = tree.mod.edges_make_ultrametric_pl_relaxed(full=True, lam=lam)
phiic_relaxed[lam] = fit['PHIIC']
phiic_discrete, phiic_relaxed
Use PHIIC to compare models fitted under different assumptions. Lower PHIIC indicates a better balance of fit and complexity.
Discrete model (edges_make_ultrametric_pl_discrete)
Choose ncategories by fitting several candidates (e.g., 2–5) and selecting
the model with the lowest PHIIC.
Relaxed model (edges_make_ultrametric_pl_relaxed)
Choose the penalty strength lam (lambda) by fitting a grid of values and
selecting the lowest PHIIC. Larger lam penalizes rate variation more strongly.
Model choice Fit the clock, discrete, and relaxed models, then select the model with the lowest PHIIC as the best tradeoff between fit and parsimony.
# PHIIC-based model selection
clock_fit = tree.mod.edges_make_ultrametric_pl_clock(full=True)
discrete_fit = tree.mod.edges_make_ultrametric_pl_discrete(full=True)
relaxed_fit = tree.mod.edges_make_ultrametric_pl_relaxed(full=True)
phiic = {
'clock': clock_fit['PHIIC'],
'discrete': discrete_fit['PHIIC'],
'relaxed': relaxed_fit['PHIIC'],
}
phiic