Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions plots/heatmap-cohort-retention/implementations/seaborn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
""" pyplots.ai
heatmap-cohort-retention: Cohort Retention Heatmap
Library: seaborn 0.13.2 | Python 3.14.3
Quality: 90/100 | Created: 2026-03-16
"""

import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns


# Seaborn styling
sns.set_context("talk", font_scale=1.1)
sns.set_style("white")

# Data
np.random.seed(42)

cohort_labels = [
"Jan 2024",
"Feb 2024",
"Mar 2024",
"Apr 2024",
"May 2024",
"Jun 2024",
"Jul 2024",
"Aug 2024",
"Sep 2024",
"Oct 2024",
]
n_cohorts = len(cohort_labels)
n_periods = n_cohorts
cohort_sizes = np.random.randint(800, 2500, size=n_cohorts)

retention_data = np.full((n_cohorts, n_periods), np.nan)
for i in range(n_cohorts):
max_periods = n_periods - i
retention_data[i, 0] = 100.0
base_decay = np.random.uniform(0.72, 0.85)
for j in range(1, max_periods):
prev = retention_data[i, j - 1]
decay = base_decay + np.random.uniform(-0.03, 0.03)
decay = min(decay, 0.98)
retention_data[i, j] = round(prev * decay, 1)

period_labels = [f"Month {i}" for i in range(n_periods)]
df_heatmap = pd.DataFrame(retention_data, index=cohort_labels, columns=period_labels)

# Custom colormap: warm tones for low retention, cool tones for high
cmap = mcolors.LinearSegmentedColormap.from_list("retention", ["#f7e1d0", "#e8c4a0", "#7fbf7b", "#1b7837", "#00441b"])

# Plot
fig, ax = plt.subplots(figsize=(16, 9))
fig.set_facecolor("#fafafa")
ax.set_facecolor("#f0f0f0")

mask = df_heatmap.isna()

# Annotation array with "%" suffix
annot_array = df_heatmap.copy()
annot_strings = annot_array.map(lambda v: f"{v:.0f}%" if not np.isnan(v) else "")

sns.heatmap(
df_heatmap,
mask=mask,
annot=annot_strings,
fmt="",
cmap=cmap,
vmin=0,
vmax=100,
linewidths=2.5,
linecolor="#fafafa",
ax=ax,
annot_kws={"fontsize": 13, "fontweight": "bold"},
cbar_kws={"label": "Retention %", "shrink": 0.75, "aspect": 30, "pad": 0.02},
square=False,
)

# Style
y_labels = [f"{label} (n={size:,})" for label, size in zip(cohort_labels, cohort_sizes, strict=True)]
ax.set_yticklabels(y_labels, rotation=0, fontsize=16)
ax.set_xticklabels(period_labels, rotation=0, fontsize=16)
ax.set_xlabel("Periods Since Signup", fontsize=20, labelpad=12)
ax.set_ylabel("Signup Cohort", fontsize=20, labelpad=12)
ax.set_title(
"heatmap-cohort-retention · seaborn · pyplots.ai", fontsize=24, fontweight="medium", pad=24, color="#333333"
)

# Colorbar styling
cbar = ax.collections[0].colorbar
cbar.ax.tick_params(labelsize=16)
cbar.set_label("Retention %", fontsize=18, labelpad=10)
cbar.outline.set_visible(False)

# X-axis at top
ax.xaxis.tick_top()
ax.xaxis.set_label_position("top")

# Remove all spines
sns.despine(ax=ax, top=True, right=True, bottom=True, left=True)
ax.tick_params(axis="both", length=0)

plt.tight_layout()
plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor=fig.get_facecolor())
225 changes: 225 additions & 0 deletions plots/heatmap-cohort-retention/metadata/seaborn.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
library: seaborn
specification_id: heatmap-cohort-retention
created: '2026-03-16T20:46:04Z'
updated: '2026-03-16T20:57:29Z'
generated_by: claude-opus-4-5-20251101
workflow_run: 23165008151
issue: 4570
python_version: 3.14.3
library_version: 0.13.2
preview_url: https://storage.googleapis.com/pyplots-images/plots/heatmap-cohort-retention/seaborn/plot.png
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/heatmap-cohort-retention/seaborn/plot_thumb.png
preview_html: null
quality_score: 90
review:
strengths:
- Excellent spec compliance — all required features implemented correctly
- Custom colormap with warm-to-green transition creates a distinctive professional
look
- 'Clean visual refinement: all spines removed, tick marks hidden, custom background,
styled colorbar'
- Idiomatic seaborn usage with mask, annotations, and context/style configuration
- Well-structured readable code with proper reproducibility
weaknesses:
- Annotation font size (13pt) could be slightly larger for better readability at
full resolution
- Color palette is not perceptually uniform — mid-range retention values may be
harder to distinguish
- Some cohorts show very similar decay patterns limiting cross-cohort comparison
insight
image_description: The plot displays a triangular cohort retention heatmap with
10 monthly cohorts (Jan 2024 – Oct 2024) on the y-axis and 10 periods (Month 0
– Month 9) on the x-axis positioned at the top. Each cell shows bold retention
percentages with a "%" suffix. The color scheme transitions from light tan/peach
(low retention) through medium green to dark forest green (high retention), using
a custom colormap. Cohort sizes (n=921 to n=2,438) are shown in parentheses next
to each cohort label. The triangular shape is correctly formed — earlier cohorts
have more columns. The background is light gray (#fafafa), all spines are removed,
tick marks are hidden, and the colorbar on the right displays "Retention %" from
0–100 with its outline removed. The title reads "heatmap-cohort-retention · seaborn
· pyplots.ai" in medium weight.
criteria_checklist:
visual_quality:
score: 28
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 7
max: 8
passed: true
comment: All font sizes explicitly set (title 24pt, labels 20pt, ticks 16pt,
annotations 13pt). Annotations slightly small but readable.
- id: VQ-02
name: No Overlap
score: 6
max: 6
passed: true
comment: No overlapping text. Clean cell spacing with linewidths=2.5.
- id: VQ-03
name: Element Visibility
score: 6
max: 6
passed: true
comment: Heatmap cells well-sized with clear separation and good contrast.
- id: VQ-04
name: Color Accessibility
score: 3
max: 4
passed: true
comment: Custom tan-to-green palette avoids red-green issues but is not perceptually
uniform.
- id: VQ-05
name: Layout & Canvas
score: 4
max: 4
passed: true
comment: Plot fills canvas well, colorbar appropriately sized and positioned.
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: 'Descriptive labels: Periods Since Signup, Signup Cohort.'
design_excellence:
score: 15
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 6
max: 8
passed: true
comment: Custom colormap, custom background, styled colorbar — clearly above
defaults.
- id: DE-02
name: Visual Refinement
score: 5
max: 6
passed: true
comment: All spines removed, tick marks hidden, custom background, colorbar
outline removed.
- id: DE-03
name: Data Storytelling
score: 4
max: 6
passed: true
comment: Triangular shape communicates cohort narrative, color intensity shows
retention decay.
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Correct triangular cohort retention heatmap.
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: 'All spec features present: triangular shape, Period 0 = 100%, colormap,
annotations, cohort sizes, Month N labels, colorbar.'
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: Cohorts on Y, periods on X, retention as color. Correct.
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title format correct. Colorbar serves as legend.
data_quality:
score: 14
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 5
max: 6
passed: true
comment: Shows varying decay rates, different cohort sizes, full triangular
pattern. Some similar decay profiles.
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Monthly SaaS cohort retention — real-world neutral business scenario.
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: Realistic retention values and cohort sizes.
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: 'Linear flow: imports, data, plot, save. No functions or classes.'
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: np.random.seed(42) set.
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: All imports used.
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Clean and Pythonic. Elegant .map() for annotations.
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: Saves as plot.png with dpi=300. No deprecated API.
library_mastery:
score: 8
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 5
max: 5
passed: true
comment: Expert use of sns.heatmap with mask, annot, fmt, cbar_kws. Idiomatic
set_context, set_style, despine.
- id: LM-02
name: Distinctive Features
score: 3
max: 5
passed: true
comment: Leverages seaborn-specific mask, built-in annotations, cbar_kws,
sns.despine.
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- colorbar
- annotations
patterns:
- data-generation
- matrix-construction
dataprep: []
styling:
- custom-colormap
- edge-highlighting
Loading