|
1 | 1 | """ pyplots.ai |
2 | 2 | density-basic: Basic Density Plot |
3 | | -Library: plotly 6.5.0 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: plotly 6.5.2 | Python 3.14 |
| 4 | +Quality: /100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
8 | 8 | import plotly.graph_objects as go |
| 9 | +from scipy.stats import gaussian_kde |
9 | 10 |
|
10 | 11 |
|
11 | | -# Data - Test scores with realistic bimodal distribution |
| 12 | +# Data - SAT Math scores with bimodal distribution |
12 | 13 | np.random.seed(42) |
13 | | -scores = np.concatenate( |
| 14 | +sat_scores = np.concatenate( |
14 | 15 | [ |
15 | | - np.random.normal(72, 10, 300), # Main group around 72 |
16 | | - np.random.normal(88, 5, 100), # High achievers around 88 |
| 16 | + np.random.normal(540, 60, 350), # Main group around 540 |
| 17 | + np.random.normal(680, 35, 150), # High achievers around 680 |
17 | 18 | ] |
18 | 19 | ) |
| 20 | +sat_scores = np.clip(sat_scores, 200, 800) # SAT range |
19 | 21 |
|
20 | | -# Compute KDE using Silverman's rule of thumb for bandwidth |
21 | | -n = len(scores) |
22 | | -std = np.std(scores, ddof=1) |
23 | | -iqr = np.percentile(scores, 75) - np.percentile(scores, 25) |
24 | | -bandwidth = 0.9 * min(std, iqr / 1.34) * n ** (-0.2) |
| 22 | +# KDE using scipy |
| 23 | +kde = gaussian_kde(sat_scores) |
| 24 | +x_grid = np.linspace(350, 800, 500) |
| 25 | +density = kde(x_grid) |
25 | 26 |
|
26 | | -# Evaluate density at each point on a grid |
27 | | -x_range = np.linspace(scores.min() - 10, scores.max() + 10, 500) |
28 | | -density = np.zeros_like(x_range) |
29 | | -for xi in scores: |
30 | | - density += np.exp(-0.5 * ((x_range - xi) / bandwidth) ** 2) |
31 | | -density /= n * bandwidth * np.sqrt(2 * np.pi) |
32 | | - |
33 | | -# Create figure |
| 27 | +# Plot |
34 | 28 | fig = go.Figure() |
35 | 29 |
|
36 | | -# Density curve with fill |
37 | 30 | fig.add_trace( |
38 | 31 | go.Scatter( |
39 | | - x=x_range, |
| 32 | + x=x_grid, |
40 | 33 | y=density, |
41 | 34 | mode="lines", |
42 | 35 | fill="tozeroy", |
43 | | - fillcolor="rgba(48, 105, 152, 0.3)", |
44 | | - line={"color": "#306998", "width": 4}, |
| 36 | + fillcolor="rgba(48, 105, 152, 0.25)", |
| 37 | + line={"color": "#306998", "width": 3.5}, |
45 | 38 | name="Density", |
46 | | - hovertemplate="Score: %{x:.1f}<br>Density: %{y:.4f}<extra></extra>", |
| 39 | + hovertemplate="Score: %{x:.0f}<br>Density: %{y:.4f}<extra></extra>", |
47 | 40 | ) |
48 | 41 | ) |
49 | 42 |
|
50 | | -# Rug plot showing individual observations |
| 43 | +# Rug plot |
51 | 44 | fig.add_trace( |
52 | 45 | go.Scatter( |
53 | | - x=scores, |
54 | | - y=[-0.001] * len(scores), |
| 46 | + x=sat_scores, |
| 47 | + y=np.zeros(len(sat_scores)), |
55 | 48 | mode="markers", |
56 | | - marker={"symbol": "line-ns", "size": 12, "color": "#306998", "line": {"width": 1.5}}, |
| 49 | + marker={"symbol": "line-ns", "size": 12, "color": "#306998", "opacity": 0.35, "line": {"width": 1.5}}, |
57 | 50 | name="Observations", |
58 | | - hovertemplate="Score: %{x:.1f}<extra></extra>", |
| 51 | + hovertemplate="Score: %{x:.0f}<extra></extra>", |
59 | 52 | ) |
60 | 53 | ) |
61 | 54 |
|
62 | 55 | # Layout |
63 | 56 | fig.update_layout( |
64 | 57 | title={"text": "density-basic · plotly · pyplots.ai", "font": {"size": 36}, "x": 0.5, "xanchor": "center"}, |
65 | 58 | xaxis={ |
66 | | - "title": {"text": "Test Score", "font": {"size": 28}}, |
| 59 | + "title": {"text": "SAT Math Score (points)", "font": {"size": 28}}, |
67 | 60 | "tickfont": {"size": 22}, |
68 | | - "showgrid": True, |
69 | | - "gridwidth": 1, |
70 | | - "gridcolor": "rgba(128, 128, 128, 0.2)", |
| 61 | + "showgrid": False, |
71 | 62 | "zeroline": False, |
72 | 63 | }, |
73 | 64 | yaxis={ |
74 | 65 | "title": {"text": "Density", "font": {"size": 28}}, |
75 | 66 | "tickfont": {"size": 22}, |
76 | | - "showgrid": True, |
| 67 | + "gridcolor": "rgba(128, 128, 128, 0.15)", |
77 | 68 | "gridwidth": 1, |
78 | | - "gridcolor": "rgba(128, 128, 128, 0.2)", |
79 | 69 | "zeroline": False, |
80 | 70 | "rangemode": "tozero", |
81 | 71 | }, |
82 | 72 | template="plotly_white", |
83 | 73 | showlegend=True, |
84 | 74 | legend={ |
85 | 75 | "font": {"size": 20}, |
86 | | - "x": 0.98, |
87 | | - "y": 0.98, |
| 76 | + "x": 0.97, |
| 77 | + "y": 0.95, |
88 | 78 | "xanchor": "right", |
89 | 79 | "yanchor": "top", |
90 | 80 | "bgcolor": "rgba(255, 255, 255, 0.8)", |
| 81 | + "borderwidth": 0, |
91 | 82 | }, |
92 | | - margin={"l": 100, "r": 60, "t": 100, "b": 100}, |
| 83 | + margin={"l": 90, "r": 40, "t": 90, "b": 90}, |
| 84 | + plot_bgcolor="white", |
93 | 85 | ) |
94 | 86 |
|
95 | | -# Save as PNG and HTML |
| 87 | +# Save |
96 | 88 | fig.write_image("plot.png", width=1600, height=900, scale=3) |
97 | 89 | fig.write_html("plot.html", include_plotlyjs="cdn") |
0 commit comments