Skip to content

Python tutorial

The same ground as the CLI tutorial, covered through the Python API.


1. Load a tree and construct a PhytClust object

PhytClust works on a Biopython Phylo tree. The constructor accepts a path to a Newick file, a Newick string, or an already-parsed tree object.

from Bio import Phylo
from phytclust import PhytClust
from phytclust.config import OutlierConfig

tree = Phylo.read("examples/sample_tree.nwk", "newick")

pc = PhytClust(
    tree,
    min_cluster_size=2,
    outlier=OutlierConfig(size_threshold=3, prefer_fewer=True),
    optimize_polytomies=True,
)

Outlier settings are passed as an OutlierConfig, as above. The constructor has no outlier_size_threshold= or prefer_fewer_outliers= parameters — those names exist only as the CLI flags --outlier-size-threshold and --prefer-fewer-outliers, which the CLI routes into OutlierConfig for you.

2. Fixed-k clustering

With the number of clusters known in advance:

result = pc.run(k=5)

print(result["mode"])        # "k"
print(result["selected_k"])  # 5
print(result["clusters"][0]) # leaf name -> cluster id, for k = 5

clusters is always a list of leaf-to-cluster-ID maps, one per selected k. In exact-k mode the list holds a single entry. scores is None in this mode.

When k is not known in advance, the global search scores every k up to max_k and returns the most prominent peaks:

result = pc.run(top_n=3, max_k=120)

print(result["mode"])           # "global"
print(result["k_values"])       # e.g. [7, 23, 45], ranked
print(len(result["clusters"]))  # 3 — one cluster map per selected k

result["scores"] holds the score vector for plotting or further analysis. result["k_values"] is ordered by rank, strongest first.

4. Multi-resolution mode

One representative k per logarithmic bin, giving a coarse-to-fine view:

result = pc.run(by_resolution=True, num_bins=4, max_k=120)

print(result["mode"])      # "resolution"
print(result["k_values"])  # one k per bin, e.g. [3, 12, 38, 95]

5. Tuning peak selection

Peak detection is controlled by PeakConfig:

from phytclust import PeakConfig

peak_cfg = PeakConfig(
    prominence_weight=0.7,    # 1 = rank by prominence, 0 = by score height
    ranking_mode="adjusted",  # "raw" | "adjusted"
    min_prominence=5.0,       # ignore small bumps in the score curve
    min_k=2,                  # ignore peaks below this k
    resolution_fallback_mode="max_score",   # "none" | "max_score": if a bin
                                            # holds no peak, take its max-score k
)

pc = PhytClust(tree, peak_config=peak_cfg)
result = pc.run(top_n=3, max_k=120)
print(result["k_values"])

resolution_fallback_mode applies only in resolution mode; it has no effect on the global search shown here.

6. Plot and output settings

Plot defaults live in RuntimeConfig, set once on the PhytClust object:

from phytclust import (
    RuntimeConfig, PlotConfig,
    ScorePlotConfig, ClusterPlotConfig,
)

runtime_cfg = RuntimeConfig(
    plot=PlotConfig(
        scores=ScorePlotConfig(
            log_scale_y=False,
            fig_width=22,
            fig_height=11,
        ),
        cluster=ClusterPlotConfig(
            height_scale=0.25,
            marker_size=60,
        ),
    )
)

pc = PhytClust(tree, runtime_config=runtime_cfg)
result = pc.run(top_n=3, max_k=100)

pc.plot(results_dir="results/python", save=True)
pc.save(results_dir="results/python", filename="phytclust_results.tsv")

To retrieve a single partition without re-running, use pc.get_clusters(k=7). Note that it keys its map by Bio.Phylo clade objects rather than by leaf name; use clade.name to get the label.

7. Comparing several k

plot_multiple_k renders one tree image per k, whether the values came from peak detection or from your own list:

from phytclust.viz.cluster import plot_multiple_k

plot_multiple_k(
    pc,
    k_values=[3, 5, 8],
    results_dir="results/python_multi",
    save=True,
)

Comparing the images side by side shows how clusters subdivide as granularity increases.

Full keyword arguments for plot_multiple_k, plot_clusters and plot_cluster are documented on the visualisation page, which is their reference.

8. Batch processing

Running the same analysis over a directory of trees and collecting the results:

from pathlib import Path
from Bio import Phylo
from phytclust import PhytClust, PeakConfig
from phytclust.config import OutlierConfig
import pandas as pd

peak_cfg = PeakConfig(prominence_weight=0.7, min_prominence=5.0)

rows = []
for tree_file in Path("data/trees/").glob("*.nwk"):
    tree = Phylo.read(str(tree_file), "newick")
    pc = PhytClust(
        tree,
        min_cluster_size=2,
        outlier=OutlierConfig(size_threshold=3),
        peak_config=peak_cfg,
    )
    result = pc.run(top_n=1, max_k=100)

    rows.append({
        "tree": tree_file.stem,
        "best_k": result["selected_k"],
        "n_leaves": len(list(tree.get_terminals())),
    })

    # optional: write per-tree outputs
    pc.save(results_dir=f"results/{tree_file.stem}")

summary = pd.DataFrame(rows)
print(summary)

9. Working with results

Each entry of clusters corresponds to the k at the same position in k_values:

import pandas as pd

result = pc.run(top_n=3, max_k=100)

for k, clusters in zip(result["k_values"], result["clusters"]):
    print(f"k={k}: {len(set(clusters.values()))} clusters")

    df = pd.DataFrame([
        {"leaf": leaf, "cluster": cid}
        for leaf, cid in clusters.items()
    ])

    # outlier clusters carry ID -1
    real_clusters = df[df["cluster"] != -1]
    outliers = df[df["cluster"] == -1]
    print(f"  {len(real_clusters)} leaves clustered, {len(outliers)} outliers")