Skip to content

Commit 30ff44a

Browse files
fix(seaborn): address review feedback for line-retention-cohort
Attempt 2/3 - fixes based on AI review
1 parent b4771fb commit 30ff44a

1 file changed

Lines changed: 65 additions & 18 deletions

File tree

  • plots/line-retention-cohort/implementations

plots/line-retention-cohort/implementations/seaborn.py

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
line-retention-cohort: User Retention Curve by Cohort
33
Library: seaborn 0.13.2 | Python 3.14.3
44
Quality: 87/100 | Created: 2026-03-16
@@ -21,33 +21,43 @@
2121
decay_rates = [0.18, 0.16, 0.14, 0.12, 0.10]
2222
floors = [8, 10, 14, 18, 22]
2323

24+
endpoint_values = {}
25+
2426
for (cohort_label, cohort_size), decay, floor in zip(cohorts.items(), decay_rates, floors, strict=True):
2527
retention = 100 * np.exp(-decay * weeks) + floor * (1 - np.exp(-0.3 * weeks))
2628
retention[0] = 100.0
2729
retention = np.clip(retention, 0, 100)
2830
noise = np.random.normal(0, 0.8, len(weeks))
2931
noise[0] = 0
3032
retention = np.clip(retention + noise, 0, 100)
33+
label = f"{cohort_label} (n={cohort_size:,})"
34+
endpoint_values[label] = retention[-1]
3135
for w, r in zip(weeks, retention, strict=True):
32-
records.append({"week": w, "retention": r, "cohort": f"{cohort_label} (n={cohort_size:,})"})
36+
records.append({"week": w, "retention": r, "cohort": label})
3337

3438
df = pd.DataFrame(records)
3539

40+
# Custom palette starting with Python Blue
41+
palette = ["#306998", "#E8922A", "#3A9E78", "#D94F4F", "#8B6DB0"]
42+
3643
# Plot - use seaborn style, context, and hue-based grouping
3744
sns.set_theme(
3845
style="whitegrid",
46+
font="sans-serif",
3947
rc={
4048
"axes.spines.top": False,
4149
"axes.spines.right": False,
42-
"grid.alpha": 0.15,
43-
"grid.linewidth": 0.8,
50+
"axes.spines.left": False,
51+
"grid.alpha": 0.12,
52+
"grid.linewidth": 0.6,
4453
"axes.grid.axis": "y",
54+
"axes.facecolor": "#FAFAFA",
55+
"figure.facecolor": "white",
56+
"font.family": "sans-serif",
4557
},
4658
)
4759
sns.set_context("talk", font_scale=1.1)
4860

49-
palette = sns.color_palette("colorblind", n_colors=5)
50-
5161
fig, ax = plt.subplots(figsize=(16, 9))
5262

5363
sns.lineplot(
@@ -64,28 +74,65 @@
6474
ax=ax,
6575
)
6676

77+
# Progressive emphasis: older cohorts thinner/lighter, newer bolder
6778
cohort_labels = df["cohort"].unique()
6879
for i, line in enumerate(ax.lines[: len(cohort_labels)]):
69-
weight = 1.5 + i * 0.4
80+
weight = 1.5 + i * 0.5
7081
line.set_linewidth(weight)
7182
line.set_markersize(5 + i * 1.5)
72-
line.set_alpha(0.5 + i * 0.12)
73-
74-
ax.axhline(y=20, color="#888888", linestyle="--", linewidth=1.2, alpha=0.5, zorder=1)
75-
ax.text(12.3, 20, "20% target", fontsize=13, color="#888888", va="center", fontstyle="italic")
83+
line.set_alpha(0.45 + i * 0.13)
84+
85+
# 20% reference line
86+
ax.axhline(y=20, color="#AAAAAA", linestyle="--", linewidth=1.0, alpha=0.6, zorder=1)
87+
ax.text(12.3, 15, "20% target", fontsize=14, color="#999999", va="center", fontstyle="italic")
88+
89+
# Endpoint annotations for data storytelling
90+
sorted_endpoints = sorted(endpoint_values.items(), key=lambda x: list(endpoint_values.keys()).index(x[0]))
91+
placed_positions = []
92+
for i, (_label, val) in enumerate(sorted_endpoints):
93+
color = palette[i]
94+
# Avoid overlap with other annotations by nudging
95+
pos = val
96+
for prev in placed_positions:
97+
if abs(pos - prev) < 4:
98+
pos = prev + 4 if pos >= prev else prev - 4
99+
placed_positions.append(pos)
100+
ax.annotate(
101+
f"{val:.0f}%",
102+
xy=(12, val),
103+
xytext=(12.6, pos),
104+
fontsize=13,
105+
fontweight="bold",
106+
color=color,
107+
va="center",
108+
ha="left",
109+
)
76110

77111
# Style
78-
ax.set_title("line-retention-cohort · seaborn · pyplots.ai", fontsize=24, fontweight="medium", pad=20)
79-
ax.set_xlabel("Weeks Since Signup", fontsize=20)
80-
ax.set_ylabel("Retained Users (%)", fontsize=20)
81-
ax.tick_params(axis="both", labelsize=16)
112+
ax.set_title("line-retention-cohort · seaborn · pyplots.ai", fontsize=24, fontweight="bold", pad=24, color="#333333")
113+
ax.set_xlabel("Weeks Since Signup", fontsize=20, color="#555555", labelpad=12)
114+
ax.set_ylabel("Retained Users (%)", fontsize=20, color="#555555", labelpad=12)
115+
ax.tick_params(axis="both", labelsize=16, colors="#666666")
82116

83-
ax.set_xlim(-0.3, 12.5)
84-
ax.set_ylim(0, 105)
117+
ax.set_xlim(-0.3, 13.5)
118+
ax.set_ylim(0, 108)
85119
ax.set_xticks(weeks)
86120

87-
legend = ax.legend(fontsize=14, frameon=False, loc="upper right", title="Signup Cohort", title_fontsize=15)
121+
# Use sns.despine for seaborn-idiomatic spine removal
122+
sns.despine(ax=ax, left=True, bottom=False)
123+
124+
legend = ax.legend(
125+
fontsize=13,
126+
frameon=True,
127+
fancybox=True,
128+
framealpha=0.85,
129+
edgecolor="#DDDDDD",
130+
loc="upper right",
131+
title="Signup Cohort",
132+
title_fontsize=15,
133+
)
88134
legend.get_title().set_fontweight("semibold")
135+
legend.get_frame().set_linewidth(0.5)
89136

90137
plt.tight_layout()
91138
plt.savefig("plot.png", dpi=300, bbox_inches="tight")

0 commit comments

Comments
 (0)