API reference
Plotting
phytreon.plot
Plotting: the :class:TreeFigure builder, elements and backends.
ColorScale
Maps raw data values to colours and emits legend entries.
gradient(n=32)
n hex colours sampled low->high across the continuous range.
DensiTreeFigure
Bases: _Renderable
Overlay a set of trees sharing the same taxa.
consensus picks the tree whose tip order the others are rotated to
match; by default the first one. align=False skips the rotation pass
and draws the trees exactly as given (faster, but usually a tangle).
Tips must be shared across the set; a tree missing some of the reference's taxa is still drawn, it simply contributes no edges for them.
reference
property
The tree whose tip order the cloud is aligned to.
RenderContext
Shared state handed to every element while a figure is built.
add_scale(scale)
Register a ColorScale: continuous -> colorbar, categorical -> legend.
Registering the same key twice is a no-op: several elements commonly
read the same column (tip points and a ring both coloured by
phylum), and each would otherwise emit its own identical legend.
resolve_color(spec, nodes, default='black', palette='curated', cmap=None, baseline=None, order=None, swatch='point')
Return (func, scale).
func(node) -> color. scale is a :class:ColorScale if the
spec mapped a data column (so the caller can register a legend),
else None. palette / cmap choose the categorical /
continuous colours when spec names a data column.
resolve_shape(spec, nodes, default='o')
Map a categorical column to marker shapes.
Returns (func, legend) where func(node) -> marker and
legend is a list of (label, color, marker) triples (or None
for a constant shape).
TangleFigure
Bases: _Renderable
Two trees drawn face to face, their shared tips joined by links.
left / right accept either a :class:~phytreon.core.tree.Tree (a
default :class:~phytreon.plot.figure.TreeFigure is built for it) or a
ready-made TreeFigure, so the two sides can be styled independently.
Only tips whose names appear in both trees are linked; unmatched tips are still drawn, just left unconnected.
The middle band holds both the tip labels and the links. By default its
width is estimated from the longest label so the names fit; pass gap
(a fraction of the two trees' combined depth) to set it yourself.
tip_labels selects which sides are named -- "both" (default),
"left", "right" or False.
connect(color=None, width=0.7, dash='dot', opacity=1.0, highlight_discordant=False, inset=None)
Style the tip-to-tip links.
color may be a literal colour or the name of a data column on the
left tree's tips (which also emits a legend). highlight_discordant
instead colours every link that crosses another one -- after
:meth:untangle those are the taxa the two trees order differently.
It flags crossings, so it can come up empty on trees that still
conflict (see :func:phytreon.treeops.crossing_number); check
:func:phytreon.treeops.robinson_foulds before concluding the two
trees agree.
inset is a (left, right) pair of fractions of the middle band
to leave clear at each end, so the links start past the tip labels
rather than running behind them. It defaults to the space the labels
are estimated to need; pass an explicit pair to tune it (or (0, 0)
to run the links tip to tip).
crossings()
Current number of crossing links (see
:func:phytreon.treeops.crossing_number).
untangle(*, fix='left', rounds=3)
Rotate clades so the two tip orders line up as closely as possible.
Delegates to :func:phytreon.treeops.untangle; both trees are
modified in place (rotation reorders children, so the topology and
every branch length are untouched -- only the reading order moves).
TreeFigure
Bases: _Renderable
A composable tree figure.
TreeFigure(tree, layout=...) starts with the branch skeleton already
drawn; chain methods (tip_labels, tip_points, heatmap …) to
add elements, then save/show. Set skeleton=False to start
empty (e.g. to draw branches yourself with a custom colour).
add(element)
Append a custom :class:_Element (for extensions).
branches(color='black', size=1.0)
Draw (or restyle) the tree skeleton. size is a single width
applied to every branch in the tree -- there is no per-branch/data
variation. A figure only ever has one skeleton layer: calling this
again (e.g. to override the skeleton=True default added by
__init__) replaces it in place rather than drawing a second,
overlapping set of lines.
collapsed_clades(**kwargs)
Draw a triangle for each clade collapsed by
:func:phytreon.treeops.collapse_clade.
connections(pairs, **kwargs)
Curved links between pairs of tips/nodes (HGT, co-occurrence …).
node_bars(**kwargs)
Interval bars at internal nodes (e.g. 95% HPD divergence times).
scale_bar(**kwargs)
A compact branch-length scale bar.
build_color_scale(title, values, cmap=None, palette='curated', baseline=None, order=None, swatch='point')
Build a colour scale (categorical or continuous) from raw values.
palette-- name of the qualitative palette (default"curated"= the eight-hue colourblind-safe default; also"hue"for the raw HCL hue wheel,"set2","dark2","tab10").cmap-- continuous colour spec:Nonefor the default blue gradient, a(low, high)hex pair, or a matplotlib colormap name.baseline-- one category, or a list of them, to render in neutral grey. Baseline levels do not consume a palette slot, so the remaining levels keep the saturated colours. Use it for the "expected" or default state: when one level covers most of the tree, colouring it as loudly as the rare ones spends most of the figure's ink on the least informative category and buries the exceptions.order-- explicit category order for the legend (levels not listed follow, sorted). Categorical levels are otherwise sorted alphabetically, which rarely matches a meaningful progression.swatch--"point"or"patch"; how the legend key is drawn.
Inference
phytreon.infer
Tree inference: alignment, trimming, distance/ML methods, bootstrap.
align_external(records, tool='mafft', path=None, extra_args=None)
Align via MAFFT or MUSCLE if installed. Raises if the tool is absent.
bootstrap_support(aln, builder=None, n=100, as_percent=True, seed=None, reference=None)
Return (reference_tree_with_support, {split: support}).
Support is computed on bipartitions, so it is correct even if the
reference tree is (midpoint-)rooted differently from the replicates.
Each internal node gets node.support set to its split's frequency
(0-100 if as_percent, else 0-1).
build_tree(sequences, *, aligner='builtin', align_kw=None, trim_kw=None, method='nj', dist_model='jc69', root='none', bootstrap=0, ml_engine='native', ml_model='HKY85', ml_gamma=0, ml_search=True, ml_tool='iqtree', parsimony_model='fitch', seed=None, return_alignment=False)
Build a tree from sequences.
Parameters
sequences FASTA path/string, list of (name, seq), or an
pre-built :class:Alignment.
aligner "builtin" (pure Python), "mafft"/"muscle"
(external), or "none" (input already aligned).
align_kw forwarded to the aligner (seqtype, match, gap ...).
trim_kw None to skip trimming, else forwarded to
:func:phytreon.infer.trim.trim (max_gap,
min_occupancy, min_conservation ...).
method "nj" | "upgma" | "ml" | "parsimony"/"mp".
bootstrap number of bootstrap replicates (0 = none; distance methods).
Protein sequences work the same way -- pass ml_model="JTT"/"WAG"/
"LG" for method="ml" (:func:phytreon.infer.ml_native.ml_tree
validates the model matches the data's alphabet); dist_model="poisson"
for method="nj"/"upgma".
Single-cell CRISPR lineage-tracing character matrices (see
:func:phytreon.infer.lineage.read_allele_table) work through
method="parsimony" too -- pass parsimony_model="camin_sokal" for
the irreversible model (:func:phytreon.infer.lineage.lineage_tree)
instead of the default reversible Fitch parsimony
(:func:phytreon.infer.parsimony.parsimony_tree).
camin_sokal_score(tree, aln, data=None)
Irreversible ("Camin-Sokal") parsimony cost: the minimum number of
independent mutation/scar-acquisition events needed to explain the
observed states, given that state "0" (ancestral/wild-type) can only
transition to a derived state -- never revert, never interconvert
directly with a different derived state -- and the tree's root is fixed
ancestral at every site (see :func:sankoff_score's root_state).
Works equally for CRISPR lineage-barcode data or any other irreversible
somatic-mutation signal (single gene or a combination of genes).
Assumes aln encodes its ancestral state as character "0" at
every site, which :func:read_allele_table/:func:read_mutation_matrix
guarantee.
distance_matrix(alignment, model='identity')
Compute (names, matrix) from a Biopython MultipleSeqAlignment.
model is any name accepted by Biopython's DistanceCalculator
(e.g. "identity", "blastn", "blosum62").
distance_matrix_model(aln, model='jc69')
Pairwise evolutionary distances with a substitution-model correction.
model:
* "raw" -- uncorrected p-distance,
* "jc69" -- Jukes-Cantor: d = -3/4 ln(1 - 4p/3),
* "k2p" -- Kimura 2-parameter (transition/transversion separated),
* "poisson" -- Poisson correction (protein analogue of JC69):
d = -ln(1 - p).
"jc69"/"k2p" need nucleotide data; on non-nucleotide alignments
they fall back to raw p-distance (unchanged). "poisson" is an
explicit opt-in for protein data and is applied as requested. Gapped
positions are ignored pairwise.
expression_dendrogram(expr, *, genes=None, metric='correlation', method='upgma')
Hierarchical-clustering dendrogram of transcriptional similarity for one gene or a small combination of genes.
This is not a phylogenetic tree. It groups cells by how alike their
expression is (cell state/type), not by shared ancestry. For real
single-cell lineage reconstruction, use
:func:phytreon.infer.lineage.lineage_tree on CRISPR scar data
(:func:~phytreon.infer.lineage.read_allele_table) or somatic-mutation
data (:func:~phytreon.infer.lineage.read_mutation_matrix) instead.
method is "upgma" (default -- ultrametric, the conventional
choice for expression dendrograms) or "nj". Result carries
tree.data["tree_type"] = "expression_similarity_dendrogram".
expression_distance_matrix(expr, *, genes=None, metric='correlation')
Pairwise transcriptional dissimilarity between cells/samples, from a
numeric expression matrix (cells as rows, genes as columns; CSV path or
an existing :class:pandas.DataFrame).
genes optionally restricts the comparison to one gene or a small
combination (rather than the whole transcriptome) -- e.g.
genes=["CD3D"] groups cells purely by that one marker's expression.
metric is any value accepted by
:func:scipy.spatial.distance.pdist ("correlation", "euclidean",
"cosine", ...); see :func:_effective_metric for the single-gene
fallback.
infer_fasttree(aln, nucleotide=None, path=None)
Approximate-ML tree with FastTree.
infer_iqtree(aln, model='MFP', bootstrap=0, path=None, extra_args=None)
ML tree with IQ-TREE. model='MFP' runs ModelFinder Plus.
lineage_tree(aln, start=None, search=True, max_sweeps=20)
Minimum-Camin-Sokal-cost lineage tree by NNI hill-climbing (from an NJ start on raw Hamming distance).
Result carries tree.data['camin_sokal_score'] (the reconstruction's
cost), ['min_possible_score'] (topology-independent lower bound) and
['excess_origins'] (their difference -- homoplasy the tree couldn't
avoid). Camin-Sokal's bounds don't correspond to Fitch parsimony's
ci/ri (those formulas assume a reversible model), so those
fields are intentionally not set here.
log_likelihood(tree, alignment, model='HKY85', params=None, gamma=0, shape=0.5)
Felsenstein log-likelihood of tree for alignment under a model
(nucleotide JC69/K80/HKY85/GTR, or protein JTT/WAG/LG; optionally with
gamma discrete-rate categories).
ml_tree(alignment, model='HKY85', search=True, gamma=0, start=None, max_sweeps=20)
Maximum-likelihood tree (pure Python).
model is a nucleotide model (JC69 / K80 / HKY85 / GTR)
for DNA/RNA, or a protein model (JTT / WAG / LG) for amino
acid sequences -- the state space (4 or 20) follows from the model
name, so pass one matching your alignment's alphabet. gamma is the
number of discrete +G rate categories (0 = off; 4 is typical) -- this
models among-site rate variation and usually fits real data much better.
With search=True the topology is refined by NNI hill-climbing from the
start tree (NJ by default). Result carries tree.data['logL'],
['model'], ['model_params'], ['gamma_shape'].
model_finder(alignment, models=None, gammas=(0, 4), start=None)
Rank substitution models by AIC on a fixed (NJ) tree -- a lightweight ModelFinder. Returns a list of dicts sorted by AIC (best first).
models defaults to the nucleotide set (JC69/K80/HKY85/GTR) or the
protein set (JTT/WAG/LG) depending on the alignment's alphabet; pass an
explicit tuple to override.
neighbor_joining(names, matrix, nonneg=True)
Neighbor-joining tree from a square distance matrix.
nonneg=True (default) clamps NJ's negative branch lengths to 0.
p_distance_matrix(aln)
Uncorrected pairwise p-distance (kept for callers that want it).
parsimony_score(tree, aln, data=None)
Total Fitch parsimony score (number of changes) of tree.
parsimony_tree(aln, start=None, search=True, max_sweeps=50)
Minimum-parsimony tree by NNI hill-climbing (from an NJ start).
Result carries tree.data['parsimony_score'] plus the consistency index
(ci) and retention index (ri). Note: NNI returns one locally
optimal tree; equally-parsimonious alternatives are not enumerated
(tree.data['mp_search'] = 'NNI-local').
read_allele_table(source, *, cell_col='cellBC', intbc_col='intBC', site_cols=('r1', 'r2', 'r3'), missing_markers=('NC',))
Build an :class:Alignment from a Cassiopeia-style CRISPR allele
table (tab-separated path, or an existing :class:pandas.DataFrame).
One character per (intBC, site) combination. Each cell's raw edit
string (e.g. "CGCCG[111:3D]AATGG") is reduced to its bracketed edit
label ("111:3D"), the ancestral/uncut state ("[None]"
substring) is normalized to a dedicated ancestral label, and any value
containing one of missing_markers (Cassiopeia uses "NC") is
treated as missing/dropout. Combinations with no row at all (allele
dropout at the row level, not just a missing-marker value) are also
encoded as missing, not silently omitted.
The character actually assigned to the ancestral state is guaranteed to
be code "0" at every site -- including a site that happens to be
edited in 100% of profiled cells (this occurs in real data; without the
fix, :func:phytreon.infer.matrix.read_character_matrix would recode
whichever real edit happens to sort first as if it were ancestral).
read_character_matrix(source, *, taxa_col=None, missing=None)
Build an :class:Alignment from a discrete character/trait matrix.
source is a path to a CSV/TSV file, or an existing
:class:pandas.DataFrame (taxa as rows, one column per character).
Missing values (NaN, or an explicit missing sentinel) are
encoded as ambiguous states, so they never force a false character
change under parsimony scoring.
taxa_col names the column holding taxon labels; if omitted, the
DataFrame's existing index (or the file's first column) is used.
Each column may use any small set of hashable states (numbers, strings, booleans, ...); states are recoded internally to single printable characters (up to 62 distinct states per column).
read_fasta(source)
Read FASTA from a path or a raw string.
read_mutation_matrix(source, *, taxa_col=None, wild_type='WT', missing=None)
Build an :class:Alignment from a general single-cell somatic
mutation/genotype matrix (cells as rows, one column per gene or locus;
path or an existing :class:pandas.DataFrame).
Each value is either wild_type (the reference/unmutated state,
default "WT") or a specific mutation/variant call (any value --
e.g. an amino-acid change, a VCF-style allele notation, or just a
clone/genotype label); NaN, or an explicit missing sentinel, is
treated as missing data. This is :func:read_allele_table generalized
beyond CRISPR allele tables: same irreversible-mutation model, same
guarantee that the wild-type state lands on code "0" at every gene
even if that gene happens to be mutated in every profiled cell, just
without the CRISPR-specific [position:size TYPE] bracket parsing --
column values are used as given.
reconstruct_ancestral_mutations(tree, aln, *, site_names=None, data=None)
Trace back which mutation(s)/scar(s) arose on which branch, under
the same Camin-Sokal model :func:camin_sokal_score minimizes -- the
piece that turns a :func:lineage_tree topology into an actual
reconstructed process, not just "these cells are related".
For every node, node.data["mutations_acquired"] lists the site
names (from site_names, defaulting to "site0", "site1",
... if not given) that transitioned from ancestral to derived on that
node's incoming branch (an empty list at the root, and at any branch
with no new mutation). Ties among equally-optimal reconstructions are
broken toward matching the parent's state (the standard Sankoff
traceback convention), so an already-derived site isn't reported as
spuriously re-arising partway down a clade that simply inherited it.
sankoff_score(tree, aln, cost_matrix, *, root_state=0, data=None)
Sankoff parsimony cost of tree for aln under an arbitrary
(k, k) cost_matrix (state slots as encoded by
:func:_encode_lineage -- slot 0 is the ancestral state).
root_state fixes the tree's root to a specific state slot rather
than optimizing over every possible root state, which is the right
choice whenever the true ancestral state is known a priori -- as it is
here: the clonal progenitor cell predates all CRISPR editing, so the
root is always ancestral (slot 0, the default). Pass root_state=None
to instead optimize freely over the root state (standard unrooted
Sankoff parsimony).
tree_from_alignment(alignment, method='nj', model='identity')
One-shot: alignment -> distances -> NJ/UPGMA tree.
trim_terminal_gaps(aln, max_gap=0.9)
Trim only the leading/trailing gap-rich columns, keep the interior.
upgma(names, matrix, nonneg=True)
UPGMA (ultrametric) tree from a square distance matrix.
Comparative methods
phytreon.comparative
Comparative methods (ancestral state reconstruction, stochastic mapping).
ace_continuous(tree, trait)
Ancestral states for a continuous trait via Felsenstein's independent-contrasts weighted averaging (Brownian motion).
Returns {node: estimated_value} and writes data['ace_value'].
ace_ml(tree, trait, model='ER', default_length=1.0)
Marginal ML ancestral states under an Mk model (ER/SYM/ARD).
Returns {node: {state: posterior_prob}} for internal nodes and writes
data['ace_probs'] / data['ace_state'] onto every node. Fitted
rates are stored on tree.root.data['_ace_model'].
ace_parsimony(tree, trait)
Fitch parsimony reconstruction of a discrete trait.
trait maps tip name -> state. Tips missing from the dict are
treated as fully ambiguous. Each node gets data['ace_state'];
the function returns {"score": int, "states": [...]}.
stochastic_map(tree, trait, n=200, model='ER', rate=None, seed=None)
Stochastic character map a discrete trait ({tip: state}) over tree.
model is the Mk model (ER/SYM/ARD); n is the number of
simulated histories (default 200 -- raise for smoother posteriors).
Layouts
phytreon.layout
Layout engines: topology -> display coordinates.
CircularLayout
Bases: Layout
Radius = distance from root, angle = leaf row mapped onto an arc.
start / extent (degrees) control the fan opening, so a fan
plot is just a circular plot with extent < 360. Branch elbows
become a radial segment plus an arc connector, both sampled to
polylines so the backends stay dumb.
CircularSlantedLayout
Bases: CircularLayout
Circular tree with straight, diagonal edges
(layout="circular_slanted") -- the polar counterpart of
:class:~phytreon.layout.rectangular.SlantedLayout.
Same node coordinates as the ordinary circular layout, but each edge is a single straight line drawn directly from the parent node to the child node, with no radial-spoke-plus-arc elbow. Branches fan out from every fork, giving the cleaner "starburst" look that reads better than the right-angle style on many circular trees.
Like any slanted/triangular tree this trades a little metric fidelity for looks: a straight chord between two polar points dips nearer the centre mid-edge, so the radius partway along a branch no longer equals the cumulative distance. Best for cladogram-style trees, or wherever the branching pattern matters more than reading exact radial distance off the middle of an edge (tip radii are still exact).
DaylightLayout
Bases: EqualAngleLayout
Equal-daylight unrooted layout (Felsenstein, Inferring Phylogenies).
Starts from equal-angle, then iteratively rotates the subtrees around each internal node so the blank wedges ("daylight") between them are equal -- this spreads the tree out and removes the lopsidedness of equal-angle.
DendrogramLayout
Bases: Layout
Top-down dendrogram (layout="dendrogram"): root at the top,
tips along the bottom, height increasing downward -- the hclust look.
It is a rectangular tree with the axes transposed: leaf order runs along x, distance-from-root runs down y.
EqualAngleLayout
InwardCircularLayout
Bases: CircularLayout
Circular tree drawn inward (layout="inward_circular"): the root
sits on the outer rim and the tips point toward the centre.
Layout
Abstract base. Subclasses implement :meth:compute.
branch_path(node)
Points of the branch connecting node to its parent.
child_connector(node)
Points of the connector spanning node's children.
For a rectangular tree this is the vertical bar; for a circular
tree it is the arc. Returns [] for leaves.
RectangularLayout
Bases: Layout
x = distance from root, y = leaf row.
Branches are drawn as elbows: a horizontal segment from the parent's x to the node's x at the node's row, plus a vertical connector at the parent spanning its children's rows.
SlantedLayout
Bases: RectangularLayout
Same node coordinates as rectangular, but edges are drawn as direct
diagonal segments parent->child (layout="slanted").
Tree operations
phytreon.treeops
Tree manipulation: reshape topology and reading order without re-inferring the tree.
These cover the common reshaping operations -- rotate / flip /
collapse / scale_clade and cut_tree. Layout assigns tip rows by
depth-first leaf order, so reordering node.children is exactly what moves
branches up/down on the plot -- that is how "freely adjust branch positions"
works here.
All functions mutate the tree in place and return it (or the cluster map for
:func:cut_tree), so they chain.
collapse_clade(tree, node, *, name=None)
Collapse node's subtree into a single tip, drawn as a triangle.
This is a display operation, the standard way to compress a large tree
down to the clades you actually want to discuss. The clade's children are
dropped and the summary needed to draw it is recorded on
node.data["_collapsed"]:
n (tips collapsed), near / far (branch length from node to
its closest and farthest leaf) and leaves (the names). A triangle
whose two sides use near and far therefore shows how deep and how
ragged the hidden clade is, the same convention iTOL uses.
Collapsing a clade that already contains a collapsed one is accounted for: the outer summary counts the tips hidden inside the inner clade and reaches to their true depth, rather than treating the inner clade as a single tip sitting at its own node.
The tree is modified in place, so work on a copy
(Tree.from_newick(tree.write())) to keep the original. Renders via
:meth:~phytreon.plot.figure.TreeFigure.collapsed_clades.
collapse_low_support(tree, threshold)
Contract internal edges whose support < threshold into
polytomies.
The collapsed node's children are re-parented to its parent and the collapsed branch length is added onto each child.
crossing_number(t1, t2)
Number of crossing tip-to-tip links when t1 and t2 face each
other in a tanglegram.
Two links cross exactly when their tips appear in opposite vertical order in the two trees, so this is the number of inverted pairs between the two tip orderings. Tips present in only one tree are ignored.
Unlike :func:robinson_foulds, this measures display discordance -- it
depends on how each tree's children happen to be ordered, so minimise it
with :func:untangle before reading anything into the value.
Zero crossings does not mean the two trees are the same: it only means
some tip order satisfies both, which conflicting splits can still allow.
Always read :func:robinson_foulds alongside it for the actual
topological difference.
cut_tree(tree, height=None, k=None)
Cut the tree into clusters and return {tip_name: cluster_id}.
Provide exactly one of
height-- cut at a fixed root-distance; each maximal subtree whose stem crosses the line becomes a cluster.k-- choose the height that yieldskclusters (by collapsing the deepest internal splits first, like hierarchicalcutree(k=)).
flip(tree, node_a, node_b)
Swap the vertical positions of two clades.
node_a and node_b must be siblings or share a path; we swap them
within their common parent's child list when they are siblings, else we
swap the two ancestor branches descending from their MRCA.
group_clade(tree, mapping, key='group', default=None)
Label clades for colouring.
mapping maps a node -> group label; every node in that node's subtree
gets data[key] = label (later mappings win for nested clades). Nodes
not covered get default. Use with branches(color=key) /
tip_labels(color=key) to colour by lineage.
group_otu(tree, mapping, key='group', default=None)
Like :func:group_clade but keyed by tip-name sets.
mapping maps a group label -> list of tip names; the label is applied
to the smallest clade (MRCA subtree) containing those tips.
ladderize(tree, ascending=True)
Order every node's children by subtree size (delegates to Tree).
midpoint_root(tree)
Re-root on the midpoint of the longest leaf-to-leaf path.
The standard way to give an unrooted (NJ) tree a sensible root; returns a new tree (the input is left unchanged).
prune_to_taxa(tree, taxa, *, strict=True)
Restrict tree to only the given leaf names, returning a new tree
(the input is left unchanged).
Subtrees with no kept leaves are dropped entirely; internal nodes left
with a single surviving child are collapsed away (their branch length is
added onto that child), so the result is a clean, minimal tree over
exactly taxa rather than one padded with unary "pass-through" nodes.
strict=True (default) raises ValueError listing any requested
name that isn't a leaf of tree (mirrors :meth:Tree.get_mrca); pass
strict=False to silently keep only whichever names are present.
robinson_foulds(t1, t2, normalized=False)
Robinson-Foulds (symmetric-difference) distance between two trees.
Counts bipartitions present in one tree but not the other (rooting-
independent). normalized=True divides by the maximum possible (2n-6).
rotate(tree, node)
Reverse the child order at node.
Flips the vertical arrangement of that clade's subtrees.
scale_clade(tree, node, factor)
Multiply every branch length inside node's subtree by factor.
swap_children(tree, node, order)
Set an arbitrary child order at node by index permutation.
untangle(t1, t2, *, fix='left', rounds=3)
Rotate nodes so the two trees' shared tips line up, and return the
resulting :func:crossing_number.
Rotating a node reverses the order of its children, which slides that clade up or down the plot without changing the topology -- so untangling only changes how the trees read, never what they say. The search is a greedy hill-climb over single rotations (the standard heuristic; it finds a good arrangement, not a provably optimal one).
fix="left" holds t1's tip order and rotates only t2;
fix="right" does the reverse; fix=None alternates between the two,
which usually untangles further but leaves neither tree in its original
order. Both trees are modified in place.
Core
phytreon.core.tree
Unified tree data model.
A small, dependency-light Node / Tree pair that is the single
representation used by every other sub-package (layout, inference,
comparative methods, plotting). Parsing is delegated to Biopython
(see :mod:phytreon.core.io) but we immediately convert into our own
nodes so the rest of the library never touches Biopython internals.
Design notes
Node.datais a free-formdictused for everything that is not a first-class attribute: joined metadata columns, ancestral-state reconstructions, bootstrap values from external programs, etc. This is the analogue oftreeio's "treedata" -- arbitrary data travelling alongside the topology.- Layout fills
x/y(display coordinates) and, for polar layouts,_r/_angle. Keeping them on the node keeps geoms simple.
Node
A single tree node (works for both tips and internal nodes).
depth(use_lengths=True)
Distance from the root (sum of branch lengths or edge count).
traverse(order='preorder')
Iterate over this node and all descendants.
order is one of "preorder", "postorder" or
"levelorder".
Tree
A rooted tree: a thin wrapper around a root :class:Node.
get_mrca(names, strict=True)
Most recent common ancestor of the named tips.
strict=True (default) raises ValueError if any requested
name is not a leaf of this tree, rather than silently computing the
MRCA of whichever names were found (which can return a misleadingly
small clade -- e.g. a single leaf -- if only one name matched).
join_data(df, on='name')
Attach a :class:pandas.DataFrame of metadata to nodes.
Each row is matched to a node by on (a column name; "name"
matches against node.name). Every other column becomes an
entry in node.data. Returns self for chaining.
ladderize(ascending=True)
Order children by subtree size for a tidy 'laddered' look.
read(path, fmt='newick')
classmethod
Read a tree file.
fmt accepts the Biopython Phylo formats (newick, nexus,
phyloxml ...) plus "beast" / "mrbayes", which read an
annotated NEXUS tree and keep the per-node estimates (ages, HPD
intervals, posterior probabilities) on node.data instead of
discarding them.
search_nodes(**kwargs)
Return nodes whose attributes / data match all kwargs.