phytreon 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- phytreon-0.1.1/LICENSE +21 -0
- phytreon-0.1.1/PKG-INFO +438 -0
- phytreon-0.1.1/README.md +384 -0
- phytreon-0.1.1/phytreon/__init__.py +49 -0
- phytreon-0.1.1/phytreon/comparative/__init__.py +5 -0
- phytreon-0.1.1/phytreon/comparative/ace.py +223 -0
- phytreon-0.1.1/phytreon/comparative/stochastic_mapping.py +134 -0
- phytreon-0.1.1/phytreon/core/__init__.py +5 -0
- phytreon-0.1.1/phytreon/core/io.py +177 -0
- phytreon-0.1.1/phytreon/core/tree.py +243 -0
- phytreon-0.1.1/phytreon/datasets.py +48 -0
- phytreon-0.1.1/phytreon/infer/__init__.py +27 -0
- phytreon-0.1.1/phytreon/infer/_search.py +28 -0
- phytreon-0.1.1/phytreon/infer/align.py +299 -0
- phytreon-0.1.1/phytreon/infer/bootstrap.py +149 -0
- phytreon-0.1.1/phytreon/infer/distance.py +82 -0
- phytreon-0.1.1/phytreon/infer/ml.py +68 -0
- phytreon-0.1.1/phytreon/infer/ml_native.py +389 -0
- phytreon-0.1.1/phytreon/infer/parsimony.py +114 -0
- phytreon-0.1.1/phytreon/infer/pipeline.py +122 -0
- phytreon-0.1.1/phytreon/infer/trim.py +71 -0
- phytreon-0.1.1/phytreon/layout/__init__.py +37 -0
- phytreon-0.1.1/phytreon/layout/base.py +99 -0
- phytreon-0.1.1/phytreon/layout/circular.py +82 -0
- phytreon-0.1.1/phytreon/layout/rectangular.py +78 -0
- phytreon-0.1.1/phytreon/layout/unrooted.py +143 -0
- phytreon-0.1.1/phytreon/plot/__init__.py +14 -0
- phytreon-0.1.1/phytreon/plot/backends.py +403 -0
- phytreon-0.1.1/phytreon/plot/elements.py +854 -0
- phytreon-0.1.1/phytreon/plot/figure.py +333 -0
- phytreon-0.1.1/phytreon/plot/palettes.py +141 -0
- phytreon-0.1.1/phytreon/scene.py +147 -0
- phytreon-0.1.1/phytreon/treeops.py +318 -0
- phytreon-0.1.1/phytreon.egg-info/PKG-INFO +438 -0
- phytreon-0.1.1/phytreon.egg-info/SOURCES.txt +40 -0
- phytreon-0.1.1/phytreon.egg-info/dependency_links.txt +1 -0
- phytreon-0.1.1/phytreon.egg-info/requires.txt +16 -0
- phytreon-0.1.1/phytreon.egg-info/top_level.txt +1 -0
- phytreon-0.1.1/pyproject.toml +43 -0
- phytreon-0.1.1/setup.cfg +4 -0
- phytreon-0.1.1/tests/test_pipeline.py +179 -0
- phytreon-0.1.1/tests/test_smoke.py +231 -0
phytreon-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yiheng Du
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
phytreon-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phytreon
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Phylogenetic trees and publication figures in Python
|
|
5
|
+
Author-email: Yiheng Du <yiheng.du@slu.edu>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Yiheng Du
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/DeweyYihengDu/phytreon
|
|
29
|
+
Project-URL: Repository, https://github.com/DeweyYihengDu/phytreon
|
|
30
|
+
Project-URL: Documentation, https://deweyyihengdu.github.io/phytreon/
|
|
31
|
+
Project-URL: Issues, https://github.com/DeweyYihengDu/phytreon/issues
|
|
32
|
+
Project-URL: Changelog, https://github.com/DeweyYihengDu/phytreon/blob/main/CHANGELOG.md
|
|
33
|
+
Keywords: phylogenetics,phylogenetic-tree,tree,visualization,bioinformatics
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
37
|
+
Requires-Python: >=3.9
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: numpy>=1.22
|
|
41
|
+
Requires-Dist: scipy>=1.8
|
|
42
|
+
Requires-Dist: pandas>=1.4
|
|
43
|
+
Requires-Dist: matplotlib>=3.5
|
|
44
|
+
Requires-Dist: biopython>=1.79
|
|
45
|
+
Provides-Extra: interactive
|
|
46
|
+
Requires-Dist: plotly>=5.0; extra == "interactive"
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
49
|
+
Requires-Dist: plotly>=5.0; extra == "dev"
|
|
50
|
+
Provides-Extra: docs
|
|
51
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
52
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<img src="assets/logo.svg" alt="phytreon logo" width="460">
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<p align="center">
|
|
60
|
+
<b>Phylogenetic trees and publication-quality figures in Python.</b>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<p align="center">
|
|
64
|
+
phytreon combines tree inference, metadata-aware visualization, and
|
|
65
|
+
static/interactive figure export in a fluent, pure-Python workflow.
|
|
66
|
+
</p>
|
|
67
|
+
|
|
68
|
+
<p align="center">
|
|
69
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
|
|
70
|
+
<img src="https://img.shields.io/badge/python-3.9%2B-blue.svg" alt="Python 3.9+">
|
|
71
|
+
<a href="https://github.com/DeweyYihengDu/phytreon/actions/workflows/ci.yml"><img src="https://github.com/DeweyYihengDu/phytreon/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
72
|
+
<a href="https://github.com/DeweyYihengDu/phytreon/tags"><img src="https://img.shields.io/github/v/tag/DeweyYihengDu/phytreon?label=release&color=1F9E94" alt="Latest release"></a>
|
|
73
|
+
<a href="https://deweyyihengdu.github.io/phytreon/"><img src="https://img.shields.io/badge/docs-mkdocs--material-1F9E94.svg" alt="Documentation"></a>
|
|
74
|
+
<img src="https://img.shields.io/badge/pure-Python-1F9E94.svg" alt="Pure Python">
|
|
75
|
+
<img src="https://img.shields.io/badge/backends-matplotlib%20%2B%20plotly-11557c.svg" alt="matplotlib + plotly backends">
|
|
76
|
+
<img src="https://img.shields.io/badge/domain-bioinformatics-00BA38.svg" alt="Bioinformatics">
|
|
77
|
+
</p>
|
|
78
|
+
|
|
79
|
+
### Why phytreon?
|
|
80
|
+
|
|
81
|
+
<table>
|
|
82
|
+
<tr>
|
|
83
|
+
<td width="33%"><b>🌿 Fluent <code>TreeFigure</code> builder</b><br><sub>Compose a figure by chaining visual layers onto a tree.</sub></td>
|
|
84
|
+
<td width="33%"><b>🎨 Static + interactive backends</b><br><sub>matplotlib for PDF/SVG/PNG, plotly for interactive HTML.</sub></td>
|
|
85
|
+
<td width="33%"><b>🧬 Sequence-to-tree pipeline</b><br><sub>One call: align → trim → infer → bootstrap.</sub></td>
|
|
86
|
+
</tr>
|
|
87
|
+
<tr>
|
|
88
|
+
<td><b>🧫 Metadata rings / heatmaps / tracks</b><br><sub>Annotate tips with rings, heatmaps, bars, alignments.</sub></td>
|
|
89
|
+
<td><b>🌳 ML / parsimony / NJ inference</b><br><sub>Pure-Python likelihood, parsimony, and distance trees.</sub></td>
|
|
90
|
+
<td><b>📄 Publication-ready exports</b><br><sub>Vector PDF/SVG, raster PNG, or interactive HTML.</sub></td>
|
|
91
|
+
</tr>
|
|
92
|
+
</table>
|
|
93
|
+
|
|
94
|
+
<p align="center">
|
|
95
|
+
<img src="assets/gallery/tree_of_life_circular.png" alt="Circular microbial tree with domain, phylum, and length metadata rings" width="850">
|
|
96
|
+
</p>
|
|
97
|
+
|
|
98
|
+
<p align="center">
|
|
99
|
+
<i>A circular 16S rRNA tree of common microbes (real NCBI data bundled in <code>examples/</code>),
|
|
100
|
+
with domain / phylum / length rings — built and drawn entirely in phytreon.</i>
|
|
101
|
+
</p>
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Quickstart
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e . # core (numpy, scipy, pandas, matplotlib, biopython)
|
|
109
|
+
pip install -e .[interactive] # + plotly (interactive HTML backend)
|
|
110
|
+
pip install -e .[dev] # + pytest, plotly
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import phytreon as pt
|
|
115
|
+
|
|
116
|
+
tr = pt.datasets.primates() # a small illustrative toy tree
|
|
117
|
+
meta = pt.datasets.primates_metadata().reset_index()
|
|
118
|
+
tr.join_data(meta, on="name") # attach metadata to tips
|
|
119
|
+
|
|
120
|
+
(pt.TreeFigure(tr) # skeleton drawn for you
|
|
121
|
+
.tip_points(color="habitat", size=9) # color mapped from metadata
|
|
122
|
+
.tip_labels()
|
|
123
|
+
.support_labels() # node support values
|
|
124
|
+
).save("tree.pdf") # PDF/SVG/PNG -> matplotlib
|
|
125
|
+
# .save("tree.html") # HTML -> plotly (zoom/hover)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The output backend is chosen from the file extension: `.pdf` / `.svg` / `.png`
|
|
129
|
+
render through matplotlib, `.html` renders an interactive plotly figure.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Gallery
|
|
134
|
+
|
|
135
|
+
Every figure below is produced by a script in [`examples/`](examples/)
|
|
136
|
+
(regenerate with `python examples/<name>.py`).
|
|
137
|
+
|
|
138
|
+
<table>
|
|
139
|
+
<tr>
|
|
140
|
+
<td width="50%">
|
|
141
|
+
<img src="assets/gallery/rectangular.png" alt="Rectangular phylogram with domain-colored tips and bootstrap support"><br>
|
|
142
|
+
<b>Rectangular</b><br>
|
|
143
|
+
<sub>16S tree of life, tips colored by domain, bootstrap support (200 reps) — <code>tree_of_life_demo.py</code></sub>
|
|
144
|
+
</td>
|
|
145
|
+
<td width="50%">
|
|
146
|
+
<img src="assets/gallery/annotated_circular.png" alt="Annotated circular tree with lineage-colored branches, shaped tips, and rings"><br>
|
|
147
|
+
<b>Annotated circular</b><br>
|
|
148
|
+
<sub>Lineage-colored branches, shaped tips, tile + bar rings, three legends — <code>showcase_circular.py</code></sub>
|
|
149
|
+
</td>
|
|
150
|
+
</tr>
|
|
151
|
+
<tr>
|
|
152
|
+
<td width="50%">
|
|
153
|
+
<img src="assets/gallery/tracks.png" alt="Tree with stacked categorical tile tracks and a numeric bar track"><br>
|
|
154
|
+
<b>Aligned tracks</b><br>
|
|
155
|
+
<sub>Stacked categorical tile tracks plus a numeric bar track — <code>tracks_demo.py</code></sub>
|
|
156
|
+
</td>
|
|
157
|
+
<td width="50%">
|
|
158
|
+
<img src="assets/gallery/alignment_track.png" alt="Tree beside a residue-colored multiple sequence alignment raster"><br>
|
|
159
|
+
<b>Alignment track</b><br>
|
|
160
|
+
<sub>The multiple-sequence alignment as a residue raster — <code>tracks_demo.py</code></sub>
|
|
161
|
+
</td>
|
|
162
|
+
</tr>
|
|
163
|
+
</table>
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## From sequences to a tree
|
|
168
|
+
|
|
169
|
+
<p align="center">
|
|
170
|
+
<img src="assets/pipeline.png" alt="phytreon pipeline from FASTA sequences through align, trim, infer, bootstrap, into a TreeFigure, then exported as pdf, svg, png, or html" width="950">
|
|
171
|
+
</p>
|
|
172
|
+
|
|
173
|
+
One configurable call runs **align → trim → infer → bootstrap**, each stage
|
|
174
|
+
opt-in and fully parameterized:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
tree = pt.build_tree(
|
|
178
|
+
"seqs.fasta", # path, list of (name, seq), or Alignment
|
|
179
|
+
aligner="builtin", # pure-Python MSA (or "mafft"/"muscle"/"none")
|
|
180
|
+
align_kw=dict(match=2, gap=-3),
|
|
181
|
+
trim_kw=dict(max_gap=0.4, min_occupancy=0.5, min_conservation=0.3),
|
|
182
|
+
method="nj", # "nj" | "upgma" | "ml" | "parsimony"
|
|
183
|
+
root="midpoint",
|
|
184
|
+
bootstrap=200, # bipartition support
|
|
185
|
+
)
|
|
186
|
+
# distances are JC69-corrected by default (dist_model="jc69"|"k2p"|"raw");
|
|
187
|
+
# negative NJ branch lengths are clamped to 0.
|
|
188
|
+
|
|
189
|
+
# maximum likelihood (pure Python), HKY85 + Γ4 rate variation + NNI:
|
|
190
|
+
ml = pt.build_tree("seqs.fasta", method="ml", ml_model="HKY85", ml_gamma=4,
|
|
191
|
+
bootstrap=100) # bootstrap works for nj/ml/parsimony
|
|
192
|
+
print(ml.data["logL"], ml.data["AIC"], ml.data["gamma_shape"])
|
|
193
|
+
pt.model_finder("seqs.fasta") # rank JC/K80/HKY/GTR ±G by AIC
|
|
194
|
+
# large datasets -> external engines: build_tree(..., method="ml", ml_engine="iqtree")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Each step is also usable on its own: `pt.align`, `pt.trim`,
|
|
198
|
+
`pt.neighbor_joining`, `pt.bootstrap_support`, `pt.infer_ml`,
|
|
199
|
+
`pt.parsimony_tree`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## What phytreon includes
|
|
204
|
+
|
|
205
|
+
| Area | Capabilities |
|
|
206
|
+
|---|---|
|
|
207
|
+
| **I/O & data model** | Newick / Nexus / PhyloXML read-write; metadata joins (`Tree.join_data`) |
|
|
208
|
+
| **Layouts** | rectangular, slanted, dendrogram, circular, fan, radial, inward-circular, unrooted (equal-angle / equal-daylight) |
|
|
209
|
+
| **Inference** | NJ, UPGMA (model-corrected distances), ML (JC69/K80/HKY85/GTR, +Γ, NNI, AIC/BIC), parsimony, bootstrap, built-in MSA, trimming |
|
|
210
|
+
| **Comparative** | ancestral states (parsimony / Mk-ML ER·SYM·ARD / Brownian), stochastic mapping, painted branches, node pies |
|
|
211
|
+
| **Figure tracks** | tip / node / support labels, tip points, metadata rings, heatmaps, bar tracks, alignment rasters |
|
|
212
|
+
| **Tree operations** | rotate, flip, ladderize, collapse, scale clade, midpoint root, cut tree, Robinson-Foulds |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## The `TreeFigure` builder
|
|
217
|
+
|
|
218
|
+
`TreeFigure` starts from a tree skeleton and lets you compose visual layers
|
|
219
|
+
fluently — every method returns the figure, so calls chain.
|
|
220
|
+
|
|
221
|
+
| Method | Draws |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `.branches(color=, size=)` | the tree skeleton (e.g. color by lineage) |
|
|
224
|
+
| `.tip_labels()` / `.node_labels()` / `.support_labels()` | text labels |
|
|
225
|
+
| `.tip_points()` / `.node_points()` / `.points()` | markers (color / size / shape mapping) |
|
|
226
|
+
| `.highlight(node=)` / `.clade_label(...)` | shade / bracket a clade |
|
|
227
|
+
| `.heatmap(df)` | a matrix of cells aligned to the tips (rectangular) |
|
|
228
|
+
| `.ring(df, columns=…)` | concentric metadata rings (circular), tile or bar |
|
|
229
|
+
| `.bar_track(df, col)` | a horizontal bar track |
|
|
230
|
+
| `.alignment(aln)` | a residue-colored MSA raster |
|
|
231
|
+
| `.painted_branches()` | branches painted by stochastic-map state |
|
|
232
|
+
| `.node_pies()` | ancestral-state pies at internal nodes |
|
|
233
|
+
| `.time_axis(geo=True)` | a time / geological-period axis |
|
|
234
|
+
|
|
235
|
+
Continuous columns get a colorbar, categorical ones a legend; tracks, labels,
|
|
236
|
+
and legends are placed so nothing overlaps. Layouts: `rectangular`, `slanted`,
|
|
237
|
+
`dendrogram`, `circular`, `fan`, `radial`, `inward_circular`,
|
|
238
|
+
`unrooted` / `daylight` (equal-daylight), `equal_angle`.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Architecture
|
|
243
|
+
|
|
244
|
+
The single design decision that makes the dual backend work: **layout and
|
|
245
|
+
rendering are completely decoupled.** A layout computes *final cartesian
|
|
246
|
+
coordinates* and emits backend-agnostic primitives; matplotlib and plotly are
|
|
247
|
+
"dumb" renderers that only translate those primitives. Adding a backend means
|
|
248
|
+
writing one translator — nothing in the phylogenetic logic changes.
|
|
249
|
+
|
|
250
|
+
| Module | Responsibility |
|
|
251
|
+
|---|---|
|
|
252
|
+
| `core` | `Tree` / `Node` data model + I/O |
|
|
253
|
+
| `layout` | topology → display coordinates |
|
|
254
|
+
| `scene` | `Path` / `Marker` / `Label` / `Polygon` primitives |
|
|
255
|
+
| `plot` | `TreeFigure` builder + matplotlib / plotly backends |
|
|
256
|
+
| `infer` | alignment / trimming / NJ / ML / parsimony / bootstrap |
|
|
257
|
+
| `comparative` | ancestral states + stochastic mapping |
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Comparison to other Python tools
|
|
262
|
+
|
|
263
|
+
| | phytreon | ete3 | toytree | Bio.Phylo | dendropy |
|
|
264
|
+
|---|:---:|:---:|:---:|:---:|:---:|
|
|
265
|
+
| Fluent figure builder | ✅ | ✗ | partial | ✗ | ✗ |
|
|
266
|
+
| Static **and** interactive backend | ✅ mpl + plotly | own GUI / SVG | toyplot | basic mpl | ✗ |
|
|
267
|
+
| Annotation tracks (heatmap / rings / MSA / bars) | ✅ | partial | partial | ✗ | ✗ |
|
|
268
|
+
| Built-in ML (+Γ) / parsimony | ✅ pure-Python | ✗ | ✗ | ✗ | ✗ |
|
|
269
|
+
| Comparative (ancestral states / stochastic map) | ✅ | ✗ | ✗ | ✗ | partial |
|
|
270
|
+
| Pure Python, pip-installable | ✅ | ✅ (Qt for GUI) | ✅ | ✅ | ✅ |
|
|
271
|
+
|
|
272
|
+
phytreon's niche is a fluent figure builder plus a self-contained phylogenetics
|
|
273
|
+
stack, with optional external tools for large-scale alignments or rigorous
|
|
274
|
+
large-scale ML (`aligner="mafft"`, `ml_engine="iqtree"`).
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Validation
|
|
279
|
+
|
|
280
|
+
`validation/validate.py` checks the core algorithms in **pure Python** (no
|
|
281
|
+
external tools):
|
|
282
|
+
|
|
283
|
+
- the likelihood engine (pattern-compressed, rescaled) matches an independent
|
|
284
|
+
naive re-implementation to **machine precision** (|Δ| ≈ 1e-13);
|
|
285
|
+
- **neighbor-joining recovers a tree exactly from its own additive (patristic)
|
|
286
|
+
distances** (Robinson-Foulds = 0, via `pt.robinson_foulds`) — the defining
|
|
287
|
+
guarantee of NJ;
|
|
288
|
+
- ML recovers a known clade and reports a finite logL / AIC.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## More
|
|
293
|
+
|
|
294
|
+
<details>
|
|
295
|
+
<summary><b>Reshaping trees (move branches freely)</b></summary>
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
pt.ladderize(tree) # tidy ordering
|
|
299
|
+
pt.rotate(tree, node) # flip a clade's vertical order
|
|
300
|
+
pt.flip(tree, node_a, node_b) # swap two clades' positions
|
|
301
|
+
pt.collapse_low_support(tree, 70) # weak edges -> polytomies
|
|
302
|
+
pt.scale_clade(tree, node, 0.5) # de-emphasize a clade's branch lengths
|
|
303
|
+
pt.midpoint_root(tree) # root an unrooted (NJ) tree
|
|
304
|
+
clusters = pt.cut_tree(tree, k=4) # {tip_name: cluster_id}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Because layout derives tip rows from child order, `rotate` / `flip` are exactly
|
|
308
|
+
how you nudge branches up and down on the plot.
|
|
309
|
+
</details>
|
|
310
|
+
|
|
311
|
+
<details>
|
|
312
|
+
<summary><b>Comparative & time-scaled trees</b></summary>
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
pt.stochastic_map(tree, trait, n=200) # stochastic character mapping
|
|
316
|
+
(pt.TreeFigure(tree).painted_branches() # branches painted by inferred state
|
|
317
|
+
.tip_labels())
|
|
318
|
+
|
|
319
|
+
(pt.TreeFigure(dated_tree) # branch lengths = time
|
|
320
|
+
.time_axis(geo=True, gridlines=True, unit="Mya") # geological bands
|
|
321
|
+
.tip_labels())
|
|
322
|
+
```
|
|
323
|
+
</details>
|
|
324
|
+
|
|
325
|
+
<details>
|
|
326
|
+
<summary><b>Circular tree with metadata rings</b></summary>
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
(pt.TreeFigure(tree, layout="circular", extent=320)
|
|
330
|
+
.ring(meta_df, # DataFrame indexed by tip name
|
|
331
|
+
columns=["habitat", "diet", "body_mass_kg"],
|
|
332
|
+
width=0.13, gap=0.03) # each column -> one ring
|
|
333
|
+
.tip_labels()
|
|
334
|
+
).save("rings.png")
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Rings stack outward (categorical → palette, numeric → gradient, each with its
|
|
338
|
+
own legend); tip labels are pushed outside all rings automatically, so the
|
|
339
|
+
tree, rings, labels, and legends never overlap.
|
|
340
|
+
</details>
|
|
341
|
+
|
|
342
|
+
<details>
|
|
343
|
+
<summary><b>Colors</b></summary>
|
|
344
|
+
|
|
345
|
+
Categorical aesthetics use an HCL **hue-wheel** palette by default (for 3
|
|
346
|
+
levels: a balanced `#F8766D #00BA38 #619CFF`); continuous aesthetics use a
|
|
347
|
+
dark-blue → light-blue gradient. Override per element:
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
fig.tip_points(color="habitat", palette="dark2") # hue | set2 | dark2 | tab10
|
|
351
|
+
fig.heatmap(mat, cmap="viridis") # name, or ("#fff","#c00") gradient
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
See `phytreon/plot/palettes.py` (`hue_palette`, `lerp_color`).
|
|
355
|
+
</details>
|
|
356
|
+
|
|
357
|
+
<details>
|
|
358
|
+
<summary><b>Environment notes</b></summary>
|
|
359
|
+
|
|
360
|
+
- **Static figures** use matplotlib (`.save("x.pdf"/".png"/".svg")`) and are
|
|
361
|
+
always reliable. **Interactive** output uses plotly (`.save("x.html")`).
|
|
362
|
+
- plotly's static PNG export (kaleido) is flaky on some Windows setups; pin
|
|
363
|
+
`kaleido==0.2.1` for plotly 5, or just use matplotlib for static figures and
|
|
364
|
+
HTML for interactivity.
|
|
365
|
+
- The plotly backend's legends are click-to-toggle traces and track placement
|
|
366
|
+
is heuristic (it cannot measure text); the matplotlib backend is the reference
|
|
367
|
+
for exact, publication-quality layout.
|
|
368
|
+
</details>
|
|
369
|
+
|
|
370
|
+
<details>
|
|
371
|
+
<summary><b>Caveats</b></summary>
|
|
372
|
+
|
|
373
|
+
- The **built-in aligner** is a single-pass progressive aligner (linear gaps);
|
|
374
|
+
fine for small/medium inputs, but use MAFFT (`aligner="mafft"`) for
|
|
375
|
+
publication alignments.
|
|
376
|
+
- **Native ML / parsimony assume binary trees**; NNI search skips polytomies
|
|
377
|
+
(resolve them first if needed). Pure-Python ML / MSA target tens of taxa —
|
|
378
|
+
see `benchmark/`.
|
|
379
|
+
</details>
|
|
380
|
+
|
|
381
|
+
<details>
|
|
382
|
+
<summary><b>Extending</b></summary>
|
|
383
|
+
|
|
384
|
+
- **New layout**: subclass `phytreon.layout.base.Layout`, implement `compute()`
|
|
385
|
+
(write `node.x` / `node.y`), `branch_path()`, `child_connector()`, and
|
|
386
|
+
register it in `phytreon/layout/__init__.py::LAYOUTS`. The builder and both
|
|
387
|
+
backends pick it up automatically.
|
|
388
|
+
- **New element**: subclass `phytreon.plot.figure._Element`, implement
|
|
389
|
+
`apply(ctx)` to read coordinates and append `scene` primitives (use
|
|
390
|
+
`ctx.resolve_color(...)` for metadata-driven aesthetics + legends), then add
|
|
391
|
+
it with `TreeFigure.add(...)`.
|
|
392
|
+
</details>
|
|
393
|
+
|
|
394
|
+
<details>
|
|
395
|
+
<summary><b>Example data (real, from NCBI)</b></summary>
|
|
396
|
+
|
|
397
|
+
`examples/data/` ships a small **microbial "tree of life" 16S rRNA** set
|
|
398
|
+
downloaded from NCBI — common model organisms and type strains across the
|
|
399
|
+
major bacterial phyla plus four archaea (a natural outgroup) — so the whole
|
|
400
|
+
pipeline runs on real, public sequences:
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
examples/data/tol_16S.fasta 18 unaligned 16S sequences (mostly RefSeq NR_*)
|
|
404
|
+
examples/data/tol_16S_aligned.fasta built-in MSA of the above (cached)
|
|
405
|
+
examples/data/tol_metadata.csv domain / phylum / organism / accession / length
|
|
406
|
+
examples/data/fetch_example_data.py re-download script (Entrez)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
An NJ/ML tree on this set recovers the four archaea as a monophyletic clade
|
|
410
|
+
(deep bacterial splits from a single 16S gene are, as expected, only weakly
|
|
411
|
+
supported). Accession versions may advance over time, so re-downloading need
|
|
412
|
+
not byte-match the shipped snapshot; the cached alignment keeps the examples
|
|
413
|
+
reproducible offline. Full accession list and license: `examples/data/SOURCES.md`.
|
|
414
|
+
</details>
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Examples & tests
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
python examples/demo.py # rect / circular / heatmap / nj / ancestral
|
|
422
|
+
python examples/pipeline_demo.py # raw sequences -> align -> trim -> NJ -> bootstrap
|
|
423
|
+
python examples/tree_of_life_demo.py # real 16S -> tree + circular metadata rings
|
|
424
|
+
python examples/showcase_circular.py # lineage colors + tile + bar rings + shapes
|
|
425
|
+
python examples/tracks_demo.py # rectangular tile / bar tracks + alignment track
|
|
426
|
+
python examples/ml_demo.py # native pure-Python ML tree (HKY85)
|
|
427
|
+
python validation/validate.py # pure-Python correctness checks
|
|
428
|
+
python benchmark/benchmark.py # timings + validated-core guidance
|
|
429
|
+
pytest -q # 37 tests
|
|
430
|
+
|
|
431
|
+
# docs: pip install mkdocs-material mkdocstrings[python]; mkdocs serve
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
<p align="center">
|
|
437
|
+
<sub>MIT licensed · Built for reproducible phylogenetic visualization in Python.</sub>
|
|
438
|
+
</p>
|