|
1 | 1 | """ pyplots.ai |
2 | 2 | heatmap-basic: Basic Heatmap |
3 | | -Library: plotnine 0.15.2 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: plotnine 0.15.3 | Python 3.14.3 |
| 4 | +Quality: 92/100 | Updated: 2026-02-16 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
8 | 8 | import pandas as pd |
9 | 9 | from plotnine import ( |
10 | 10 | aes, |
11 | 11 | element_blank, |
| 12 | + element_rect, |
12 | 13 | element_text, |
13 | 14 | geom_text, |
14 | 15 | geom_tile, |
15 | 16 | ggplot, |
16 | 17 | labs, |
| 18 | + scale_color_identity, |
17 | 19 | scale_fill_gradient2, |
| 20 | + scale_x_discrete, |
| 21 | + scale_y_discrete, |
18 | 22 | theme, |
19 | 23 | theme_minimal, |
20 | 24 | ) |
21 | 25 |
|
22 | 26 |
|
23 | | -# Data - 8x8 matrix with meaningful patterns (performance metrics by region and quarter) |
| 27 | +# Data - 8x8 matrix: quarterly growth rates (%) by department |
24 | 28 | np.random.seed(42) |
25 | | -rows = ["Region A", "Region B", "Region C", "Region D", "Region E", "Region F", "Region G", "Region H"] |
26 | | -cols = ["Q1 2023", "Q2 2023", "Q3 2023", "Q4 2023", "Q1 2024", "Q2 2024", "Q3 2024", "Q4 2024"] |
| 29 | +departments = ["Engineering", "Marketing", "Sales", "Finance", "Operations", "HR", "Research", "Support"] |
| 30 | +quarters = ["Q1 '23", "Q2 '23", "Q3 '23", "Q4 '23", "Q1 '24", "Q2 '24", "Q3 '24", "Q4 '24"] |
27 | 31 |
|
28 | | -# Create data with a trend and variation |
29 | | -base_values = np.linspace(-30, 40, 8) # Trend across columns |
30 | | -row_effects = np.random.uniform(-10, 10, 8) # Row-specific offsets |
31 | | -values = np.zeros((8, 8)) |
32 | | -for i in range(8): |
33 | | - for j in range(8): |
34 | | - values[i, j] = base_values[j] + row_effects[i] + np.random.uniform(-8, 8) |
| 32 | +# Vectorized growth rates with recovery trend and departmental variation |
| 33 | +base_trend = np.linspace(-18, 22, 8) |
| 34 | +dept_offsets = np.array([-6, 10, 14, -3, 4, -10, 8, -5]) |
| 35 | +values = np.round(base_trend[np.newaxis, :] + dept_offsets[:, np.newaxis] + np.random.normal(0, 4, (8, 8)), 1) |
35 | 36 |
|
36 | | -# Create long-form DataFrame for plotnine |
37 | | -data = [] |
38 | | -for i, row in enumerate(rows): |
39 | | - for j, col in enumerate(cols): |
40 | | - data.append({"x": col, "y": row, "value": round(values[i, j], 1)}) |
| 37 | +# Inject distinctive extreme values for storytelling focal points |
| 38 | +values[5, 0] = -32.5 # HR deep crisis in Q1 '23 |
| 39 | +values[2, 7] = 38.2 # Sales strong recovery in Q4 '24 |
| 40 | +values[6, 6] = 33.7 # Research surge in Q3 '24 |
41 | 41 |
|
42 | | -df = pd.DataFrame(data) |
| 42 | +# Build long-form DataFrame via meshgrid indexing |
| 43 | +dept_idx, qtr_idx = np.meshgrid(np.arange(8), np.arange(8), indexing="ij") |
| 44 | +df = pd.DataFrame( |
| 45 | + { |
| 46 | + "Department": pd.Categorical( |
| 47 | + [departments[i] for i in dept_idx.ravel()], categories=departments[::-1], ordered=True |
| 48 | + ), |
| 49 | + "Quarter": pd.Categorical([quarters[j] for j in qtr_idx.ravel()], categories=quarters, ordered=True), |
| 50 | + "Growth (%)": values.ravel(), |
| 51 | + } |
| 52 | +) |
| 53 | + |
| 54 | +# Conditional text color: white on dark blue, dark gray on mid, dark brown on gold |
| 55 | +df["text_color"] = np.where(df["Growth (%)"] < -12, "white", np.where(df["Growth (%)"] < 18, "#3a3a3a", "#4a2e00")) |
43 | 56 |
|
44 | | -# Preserve ordering |
45 | | -df["x"] = pd.Categorical(df["x"], categories=cols, ordered=True) |
46 | | -df["y"] = pd.Categorical(df["y"], categories=rows[::-1], ordered=True) # Reverse for top-to-bottom |
| 57 | +# Signed annotation labels |
| 58 | +df["label"] = [f"{v:+.1f}" for v in df["Growth (%)"]] |
47 | 59 |
|
48 | 60 | # Plot |
49 | 61 | plot = ( |
50 | | - ggplot(df, aes(x="x", y="y", fill="value")) |
51 | | - + geom_tile(color="white", size=0.5) |
52 | | - + geom_text(aes(label="value"), size=12, color="black") |
| 62 | + ggplot(df, aes(x="Quarter", y="Department")) |
| 63 | + + geom_tile(aes(fill="Growth (%)"), color="white", size=1.2) |
| 64 | + + geom_text(aes(label="label", color="text_color"), size=10, fontweight="bold", show_legend=False) |
53 | 65 | + scale_fill_gradient2( |
54 | | - low="#306998", # Python Blue for negative |
55 | | - mid="white", |
56 | | - high="#FFD43B", # Python Yellow for positive |
57 | | - midpoint=0, |
58 | | - name="Value", |
| 66 | + low="#14405e", mid="#ede8e3", high="#c47d00", midpoint=0, name="Growth (%)", limits=(-35, 40) |
| 67 | + ) |
| 68 | + + scale_color_identity() |
| 69 | + + scale_x_discrete(expand=(0, 0.5)) |
| 70 | + + scale_y_discrete(expand=(0, 0.5)) |
| 71 | + + labs( |
| 72 | + x="Quarter", |
| 73 | + y="Department", |
| 74 | + title="Quarterly Growth by Department · heatmap-basic · plotnine · pyplots.ai", |
| 75 | + subtitle="Year-over-year growth rate (%) across departments, Q1 2023 – Q4 2024", |
59 | 76 | ) |
60 | | - + labs(x="Time Period", y="Region", title="heatmap-basic · plotnine · pyplots.ai") |
61 | 77 | + theme_minimal() |
62 | 78 | + theme( |
63 | 79 | figure_size=(16, 9), |
64 | | - text=element_text(size=14), |
65 | | - axis_title=element_text(size=20), |
66 | | - axis_text_x=element_text(size=14, rotation=45, ha="right"), |
67 | | - axis_text_y=element_text(size=16), |
68 | | - plot_title=element_text(size=24), |
69 | | - legend_title=element_text(size=16), |
| 80 | + text=element_text(family="sans-serif"), |
| 81 | + plot_title=element_text(size=24, ha="center", weight="bold", margin={"b": 2}), |
| 82 | + plot_subtitle=element_text(size=16, ha="center", color="#555555", margin={"b": 8}), |
| 83 | + axis_title_x=element_text(size=20, margin={"t": 10}), |
| 84 | + axis_title_y=element_text(size=20, margin={"r": 8}), |
| 85 | + axis_text_x=element_text(size=16, rotation=45, ha="right", margin={"t": 4}), |
| 86 | + axis_text_y=element_text(size=16, ha="right", margin={"r": 4}), |
| 87 | + legend_title=element_text(size=16, weight="bold"), |
70 | 88 | legend_text=element_text(size=14), |
| 89 | + legend_position="right", |
| 90 | + legend_key_height=40, |
71 | 91 | panel_grid_major=element_blank(), |
72 | 92 | panel_grid_minor=element_blank(), |
| 93 | + panel_background=element_rect(fill="white", color="none"), |
| 94 | + plot_background=element_rect(fill="#f7f7f7", color="none"), |
| 95 | + plot_margin=0.02, |
73 | 96 | ) |
74 | 97 | ) |
75 | 98 |
|
|
0 commit comments