Skip to content

Commit 5c39236

Browse files
feat(seaborn): implement tree-phylogenetic (#3108)
## Implementation: `tree-phylogenetic` - seaborn Implements the **seaborn** version of `tree-phylogenetic`. **File:** `plots/tree-phylogenetic/implementations/seaborn.py` **Parent Issue:** #3070 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20620332761)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 9547839 commit 5c39236

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
""" pyplots.ai
2+
tree-phylogenetic: Phylogenetic Tree Diagram
3+
Library: seaborn 0.13.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import seaborn as sns
10+
from matplotlib.patches import Patch
11+
from scipy.cluster.hierarchy import dendrogram, linkage
12+
from scipy.spatial.distance import squareform
13+
14+
15+
# Set seaborn style for better aesthetics
16+
sns.set_theme(style="white")
17+
18+
# Define primate species for phylogenetic tree
19+
species = ["Human", "Chimpanzee", "Gorilla", "Orangutan", "Gibbon", "Baboon", "Macaque", "Marmoset", "Lemur", "Tarsier"]
20+
21+
# Create evolutionary distance matrix (symmetric)
22+
# Based on approximate mitochondrial DNA divergence (millions of years ago)
23+
np.random.seed(42)
24+
n_species = len(species)
25+
26+
# Base distances representing evolutionary divergence
27+
# Closer species have smaller distances
28+
base_distances = np.array(
29+
[
30+
[0, 6, 9, 14, 18, 25, 25, 35, 55, 58], # Human
31+
[6, 0, 9, 14, 18, 25, 25, 35, 55, 58], # Chimpanzee
32+
[9, 9, 0, 14, 18, 25, 25, 35, 55, 58], # Gorilla
33+
[14, 14, 14, 0, 18, 25, 25, 35, 55, 58], # Orangutan
34+
[18, 18, 18, 18, 0, 25, 25, 35, 55, 58], # Gibbon
35+
[25, 25, 25, 25, 25, 0, 10, 35, 55, 58], # Baboon
36+
[25, 25, 25, 25, 25, 10, 0, 35, 55, 58], # Macaque
37+
[35, 35, 35, 35, 35, 35, 35, 0, 55, 58], # Marmoset
38+
[55, 55, 55, 55, 55, 55, 55, 55, 0, 50], # Lemur
39+
[58, 58, 58, 58, 58, 58, 58, 58, 50, 0], # Tarsier
40+
]
41+
)
42+
43+
# Convert distance matrix to condensed form for hierarchical clustering
44+
condensed_distances = squareform(base_distances)
45+
46+
# Perform hierarchical clustering using UPGMA (average linkage)
47+
# This is commonly used for phylogenetic analysis
48+
linkage_matrix = linkage(condensed_distances, method="average")
49+
50+
# Create figure with seaborn styling
51+
fig, ax = plt.subplots(figsize=(16, 9))
52+
53+
# Define colors using Python palette and colorblind-safe colors
54+
# Assign each species to a clade index for consistent coloring
55+
clade_colors = {
56+
"Human": "#306998", # Python Blue - Great Apes
57+
"Chimpanzee": "#306998",
58+
"Gorilla": "#306998",
59+
"Orangutan": "#4A90D9", # Light blue - Asian great ape
60+
"Gibbon": "#4A90D9",
61+
"Baboon": "#FFD43B", # Python Yellow - Old World Monkeys
62+
"Macaque": "#FFD43B",
63+
"Marmoset": "#2ECC71", # Green - New World Monkey
64+
"Lemur": "#E74C3C", # Red - Prosimians
65+
"Tarsier": "#E74C3C",
66+
}
67+
68+
# Define link colors based on cluster membership
69+
# Map each leaf to its clade color for dendrogram branches
70+
leaf_colors = [clade_colors[s] for s in species]
71+
72+
# Create a color mapping function for dendrogram links
73+
# Links below leaves inherit the leaf color, higher links use a neutral color
74+
n = len(species)
75+
76+
77+
def get_link_color(link_id):
78+
"""Return color for dendrogram link based on cluster composition."""
79+
if link_id < n:
80+
return leaf_colors[link_id]
81+
# For internal nodes, get the cluster members
82+
cluster_idx = int(link_id - n)
83+
left_child = int(linkage_matrix[cluster_idx, 0])
84+
right_child = int(linkage_matrix[cluster_idx, 1])
85+
86+
# Get colors of both children
87+
left_color = get_link_color(left_child)
88+
right_color = get_link_color(right_child)
89+
90+
# If both children have same color, use that color
91+
if left_color == right_color:
92+
return left_color
93+
# Otherwise use neutral gray for mixed clades
94+
return "#808080"
95+
96+
97+
# Build link color list for all links in dendrogram
98+
link_colors = [get_link_color(i + n) for i in range(len(linkage_matrix))]
99+
100+
# Plot dendrogram (phylogenetic tree) with custom clade colors
101+
dendro = dendrogram(
102+
linkage_matrix,
103+
labels=species,
104+
orientation="left",
105+
ax=ax,
106+
leaf_font_size=18,
107+
link_color_func=lambda k: link_colors[k - n] if k >= n else leaf_colors[k],
108+
)
109+
110+
# Make branch lines thicker for improved visibility
111+
for line_collection in ax.collections:
112+
line_collection.set_linewidth(3)
113+
114+
# Style the dendrogram with seaborn aesthetics
115+
ax.set_xlabel("Evolutionary Distance (Million Years)", fontsize=20, fontweight="bold")
116+
ax.set_title("Primate Evolution · tree-phylogenetic · seaborn · pyplots.ai", fontsize=24, fontweight="bold", pad=20)
117+
118+
# Adjust tick parameters for readability
119+
ax.tick_params(axis="x", labelsize=16)
120+
ax.tick_params(axis="y", labelsize=18)
121+
122+
# Add subtle grid on x-axis only
123+
ax.grid(axis="x", alpha=0.3, linestyle="--")
124+
ax.set_axisbelow(True)
125+
126+
# Add scale bar annotation
127+
ax.annotate(
128+
"Scale: branch length = evolutionary distance",
129+
xy=(0.98, 0.02),
130+
xycoords="axes fraction",
131+
fontsize=14,
132+
ha="right",
133+
va="bottom",
134+
style="italic",
135+
color="#666666",
136+
)
137+
138+
# Color the species labels based on clade
139+
for label in ax.get_yticklabels():
140+
species_name = label.get_text()
141+
if species_name in clade_colors:
142+
label.set_color(clade_colors[species_name])
143+
label.set_fontweight("bold")
144+
145+
# Add legend for clades (positioned to avoid overlap)
146+
legend_elements = [
147+
Patch(facecolor="#306998", edgecolor="none", label="Great Apes"),
148+
Patch(facecolor="#4A90D9", edgecolor="none", label="Lesser Apes"),
149+
Patch(facecolor="#FFD43B", edgecolor="none", label="Old World Monkeys"),
150+
Patch(facecolor="#2ECC71", edgecolor="none", label="New World Monkeys"),
151+
Patch(facecolor="#E74C3C", edgecolor="none", label="Prosimians"),
152+
]
153+
ax.legend(handles=legend_elements, loc="lower left", fontsize=14, title="Clades", title_fontsize=16, framealpha=0.9)
154+
155+
# Remove top and right spines for cleaner look
156+
sns.despine(ax=ax, top=True, right=True)
157+
158+
# Adjust layout
159+
plt.tight_layout()
160+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: seaborn
2+
specification_id: tree-phylogenetic
3+
created: '2025-12-31T13:54:59Z'
4+
updated: '2025-12-31T14:24:15Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20620332761
7+
issue: 3070
8+
python_version: 3.13.11
9+
library_version: 0.13.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/tree-phylogenetic/seaborn/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/tree-phylogenetic/seaborn/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent visual presentation with clear clade-based color coding throughout branches
17+
and labels
18+
- Biologically accurate evolutionary distance matrix for primate species
19+
- Professional typography with proper font sizes and weights for all text elements
20+
- Thoughtful color palette using distinct, colorblind-safe colors for 5 clades
21+
- Clean layout with appropriate use of seaborn aesthetic styling
22+
weaknesses:
23+
- Contains a helper function (get_link_color) which violates KISS principle - inline
24+
logic preferred
25+
- Title format includes Primate Evolution prefix before spec-id which deviates from
26+
standard format

0 commit comments

Comments
 (0)