|
1 | 1 | """ pyplots.ai |
2 | 2 | scatter-basic: Basic Scatter Plot |
3 | | -Library: plotly 6.5.0 | Python 3.13.11 |
4 | | -Quality: 93/100 | Created: 2025-12-22 |
| 3 | +Library: plotly 6.5.2 | Python 3.14 |
| 4 | +Quality: 92/100 | Created: 2025-12-22 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
|
10 | 10 |
|
11 | 11 | # Data: Study hours vs exam scores (realistic educational context) |
12 | 12 | np.random.seed(42) |
13 | | -study_hours = np.random.uniform(1, 10, 100) |
14 | | -exam_scores = 45 + study_hours * 5 + np.random.randn(100) * 8 |
| 13 | +n_students = 120 |
| 14 | +study_hours = np.random.uniform(1, 10, n_students) |
| 15 | +base_score = 45 + study_hours * 5 |
| 16 | +exam_scores = base_score + np.random.randn(n_students) * 8 |
15 | 17 | exam_scores = np.clip(exam_scores, 0, 100) |
16 | 18 |
|
17 | | -# Create figure |
| 19 | +# Inject outliers to show scatter plot's outlier-detection value |
| 20 | +study_hours[0], exam_scores[0] = 8.5, 52.0 # High effort, low result |
| 21 | +study_hours[1], exam_scores[1] = 2.0, 78.0 # Low effort, high result |
| 22 | +study_hours[2], exam_scores[2] = 9.2, 55.0 # Another underperformer |
| 23 | + |
| 24 | +# Linear regression for trend line |
| 25 | +coeffs = np.polyfit(study_hours, exam_scores, 1) |
| 26 | +trend_x = np.array([0.5, 10.5]) |
| 27 | +trend_y = np.polyval(coeffs, trend_x) |
| 28 | +r_value = np.corrcoef(study_hours, exam_scores)[0, 1] |
| 29 | + |
| 30 | +# Color palette |
| 31 | +python_blue = "#306998" |
| 32 | +accent_orange = "#D4782F" |
| 33 | + |
18 | 34 | fig = go.Figure() |
19 | 35 |
|
| 36 | +# Trend line (behind markers) |
| 37 | +fig.add_trace( |
| 38 | + go.Scatter( |
| 39 | + x=trend_x, |
| 40 | + y=trend_y, |
| 41 | + mode="lines", |
| 42 | + line={"color": "rgba(48, 105, 152, 0.4)", "width": 2.5, "dash": "dash"}, |
| 43 | + showlegend=False, |
| 44 | + hoverinfo="skip", |
| 45 | + ) |
| 46 | +) |
| 47 | + |
| 48 | +# Main scatter — size 11 avoids congestion in dense regions |
20 | 49 | fig.add_trace( |
21 | 50 | go.Scatter( |
22 | 51 | x=study_hours, |
23 | 52 | y=exam_scores, |
24 | 53 | mode="markers", |
25 | | - marker={"size": 16, "color": "#306998", "opacity": 0.7}, |
26 | | - hovertemplate="Hours: %{x:.1f}<br>Score: %{y:.1f}<extra></extra>", |
| 54 | + marker={"size": 11, "color": python_blue, "opacity": 0.6, "line": {"width": 1.2, "color": "white"}}, |
| 55 | + showlegend=False, |
| 56 | + hovertemplate="<b>Student</b><br>Study: %{x:.1f} h<br>Score: %{y:.1f}%<extra></extra>", |
| 57 | + ) |
| 58 | +) |
| 59 | + |
| 60 | +# Outlier diamonds |
| 61 | +fig.add_trace( |
| 62 | + go.Scatter( |
| 63 | + x=[8.5, 2.0, 9.2], |
| 64 | + y=[52.0, 78.0, 55.0], |
| 65 | + mode="markers", |
| 66 | + marker={ |
| 67 | + "size": 15, |
| 68 | + "color": accent_orange, |
| 69 | + "opacity": 0.9, |
| 70 | + "line": {"width": 2, "color": "white"}, |
| 71 | + "symbol": "diamond", |
| 72 | + }, |
| 73 | + showlegend=False, |
| 74 | + hoverinfo="skip", |
27 | 75 | ) |
28 | 76 | ) |
29 | 77 |
|
30 | | -# Layout |
| 78 | +# Annotations — each outlier gets its own label for clarity |
| 79 | +ann_base = { |
| 80 | + "showarrow": True, |
| 81 | + "arrowhead": 2, |
| 82 | + "arrowsize": 1.2, |
| 83 | + "arrowwidth": 2, |
| 84 | + "arrowcolor": accent_orange, |
| 85 | + "align": "center", |
| 86 | + "font": {"size": 16, "color": accent_orange, "family": "Arial, sans-serif"}, |
| 87 | + "bgcolor": "rgba(255,255,255,0.85)", |
| 88 | + "bordercolor": accent_orange, |
| 89 | + "borderwidth": 1.5, |
| 90 | + "borderpad": 5, |
| 91 | +} |
| 92 | +annotations = [ |
| 93 | + {**ann_base, "x": 2.0, "y": 78.0, "text": "Low effort,<br>high score", "ax": -75, "ay": -45}, |
| 94 | + {**ann_base, "x": 8.5, "y": 52.0, "text": "High effort,<br>low score", "ax": 80, "ay": 40}, |
| 95 | + {**ann_base, "x": 9.2, "y": 55.0, "text": "Underperformer", "ax": 75, "ay": -35}, |
| 96 | + # Correlation coefficient near trend line |
| 97 | + { |
| 98 | + "x": 8.5, |
| 99 | + "y": np.polyval(coeffs, 8.5) + 4, |
| 100 | + "text": f"<b>r = {r_value:.2f}</b>", |
| 101 | + "showarrow": False, |
| 102 | + "bgcolor": "rgba(255,255,255,0.8)", |
| 103 | + "borderpad": 4, |
| 104 | + "font": {"size": 17, "color": python_blue, "family": "Arial, sans-serif"}, |
| 105 | + }, |
| 106 | +] |
| 107 | + |
31 | 108 | fig.update_layout( |
32 | | - title={"text": "scatter-basic · plotly · pyplots.ai", "font": {"size": 28}, "x": 0.5, "xanchor": "center"}, |
| 109 | + title={ |
| 110 | + "text": "scatter-basic · plotly · pyplots.ai", |
| 111 | + "font": {"size": 28, "color": "#2C3E50", "family": "Arial Black, Arial, sans-serif"}, |
| 112 | + "x": 0.5, |
| 113 | + "xanchor": "center", |
| 114 | + "y": 0.95, |
| 115 | + }, |
33 | 116 | xaxis={ |
34 | | - "title": {"text": "Study Hours (h)", "font": {"size": 22}}, |
| 117 | + "title": {"text": "Study Hours (h)", "font": {"size": 22, "family": "Arial, sans-serif"}, "standoff": 12}, |
35 | 118 | "tickfont": {"size": 18}, |
36 | 119 | "showgrid": True, |
37 | 120 | "gridwidth": 1, |
38 | | - "gridcolor": "rgba(0,0,0,0.1)", |
| 121 | + "gridcolor": "rgba(0,0,0,0.06)", |
| 122 | + "range": [0, 11], |
| 123 | + "zeroline": False, |
| 124 | + "dtick": 2, |
39 | 125 | }, |
40 | 126 | yaxis={ |
41 | | - "title": {"text": "Exam Score (%)", "font": {"size": 22}}, |
| 127 | + "title": {"text": "Exam Score (%)", "font": {"size": 22, "family": "Arial, sans-serif"}, "standoff": 12}, |
42 | 128 | "tickfont": {"size": 18}, |
43 | 129 | "showgrid": True, |
44 | 130 | "gridwidth": 1, |
45 | | - "gridcolor": "rgba(0,0,0,0.1)", |
| 131 | + "gridcolor": "rgba(0,0,0,0.06)", |
| 132 | + "range": [35, 105], |
| 133 | + "zeroline": False, |
| 134 | + "dtick": 10, |
46 | 135 | }, |
47 | 136 | template="plotly_white", |
48 | 137 | showlegend=False, |
49 | | - margin={"l": 80, "r": 40, "t": 80, "b": 80}, |
| 138 | + margin={"l": 80, "r": 40, "t": 90, "b": 70}, |
| 139 | + annotations=annotations, |
| 140 | + plot_bgcolor="white", |
| 141 | + paper_bgcolor="#FAFBFC", |
| 142 | + hoverlabel={"bgcolor": "white", "font_size": 14, "font_color": python_blue}, |
50 | 143 | ) |
51 | 144 |
|
52 | | -# Save as PNG (4800x2700 px) |
53 | 145 | fig.write_image("plot.png", width=1600, height=900, scale=3) |
54 | | - |
55 | | -# Save interactive HTML |
56 | 146 | fig.write_html("plot.html") |
0 commit comments