|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis |
3 | 3 | Library: pygal 3.1.0 | Python 3.14.3 |
4 | 4 | Quality: 82/100 | Created: 2026-04-12 |
|
23 | 23 | y_t = temperature[:-lag] |
24 | 24 | y_t_lag = temperature[lag:] |
25 | 25 |
|
26 | | -# Color points by time index for temporal structure |
| 26 | +# Temporal quartile masks |
27 | 27 | time_idx = np.arange(len(y_t)) |
28 | | -quartile_bounds = np.percentile(time_idx, [25, 50, 75]) |
29 | | -early = [(float(y_t[i]), float(y_t_lag[i])) for i in range(len(y_t)) if time_idx[i] < quartile_bounds[0]] |
| 28 | +q_bounds = np.percentile(time_idx, [25, 50, 75]) |
| 29 | + |
| 30 | +# Build scatter series with interactive tooltips (pygal dict format) |
| 31 | +early = [ |
| 32 | + {"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"} |
| 33 | + for i in range(len(y_t)) |
| 34 | + if time_idx[i] < q_bounds[0] |
| 35 | +] |
30 | 36 | mid_early = [ |
31 | | - (float(y_t[i]), float(y_t_lag[i])) |
| 37 | + {"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"} |
32 | 38 | for i in range(len(y_t)) |
33 | | - if quartile_bounds[0] <= time_idx[i] < quartile_bounds[1] |
| 39 | + if q_bounds[0] <= time_idx[i] < q_bounds[1] |
34 | 40 | ] |
35 | 41 | mid_late = [ |
36 | | - (float(y_t[i]), float(y_t_lag[i])) |
| 42 | + {"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"} |
| 43 | + for i in range(len(y_t)) |
| 44 | + if q_bounds[1] <= time_idx[i] < q_bounds[2] |
| 45 | +] |
| 46 | +late = [ |
| 47 | + {"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"} |
37 | 48 | for i in range(len(y_t)) |
38 | | - if quartile_bounds[1] <= time_idx[i] < quartile_bounds[2] |
| 49 | + if time_idx[i] >= q_bounds[2] |
39 | 50 | ] |
40 | | -late = [(float(y_t[i]), float(y_t_lag[i])) for i in range(len(y_t)) if time_idx[i] >= quartile_bounds[2]] |
41 | 51 |
|
42 | 52 | # Correlation coefficient |
43 | 53 | r = np.corrcoef(y_t, y_t_lag)[0, 1] |
44 | 54 |
|
45 | | -# Diagonal reference line (y = x) |
| 55 | +# Reference geometry |
46 | 56 | data_min = float(min(y_t.min(), y_t_lag.min())) |
47 | 57 | data_max = float(max(y_t.max(), y_t_lag.max())) |
48 | | -margin = (data_max - data_min) * 0.05 |
49 | | -ref_start = data_min - margin |
50 | | -ref_end = data_max + margin |
| 58 | +pad = (data_max - data_min) * 0.05 |
| 59 | +ref_start = data_min - pad |
| 60 | +ref_end = data_max + pad |
51 | 61 | ref_line = [(ref_start, ref_start), (ref_end, ref_end)] |
52 | 62 |
|
53 | | -# Shared font |
54 | | -font = "DejaVu Sans, Helvetica, Arial, sans-serif" |
| 63 | +# ±1σ envelope around y=x to visualise autocorrelation spread |
| 64 | +sigma = float(np.std(y_t_lag - y_t)) |
| 65 | +upper_env = [(ref_start, ref_start + sigma), (ref_end, ref_end + sigma)] |
| 66 | +lower_env = [(ref_start, ref_start - sigma), (ref_end, ref_end - sigma)] |
55 | 67 |
|
56 | | -# Style |
| 68 | +# Warm-to-cool temporal palette: terracotta → amber → teal → navy |
| 69 | +font = "DejaVu Sans, Helvetica, Arial, sans-serif" |
57 | 70 | custom_style = Style( |
58 | 71 | background="white", |
59 | | - plot_background="#fafafa", |
| 72 | + plot_background="#f8f7f5", |
60 | 73 | foreground="#2a2a2a", |
61 | | - foreground_strong="#2a2a2a", |
62 | | - foreground_subtle="#e0e0e0", |
63 | | - guide_stroke_color="#e0e0e0", |
64 | | - colors=("#1a3a5c", "#306998", "#5a9bd5", "#a3ceed", "#888888"), |
| 74 | + foreground_strong="#1a1a1a", |
| 75 | + foreground_subtle="#d5d5d3", |
| 76 | + guide_stroke_color="#e0dfdd", |
| 77 | + colors=( |
| 78 | + "#c25a3c", # Q1 — terracotta |
| 79 | + "#d4a028", # Q2 — warm amber |
| 80 | + "#2a9d8f", # Q3 — teal |
| 81 | + "#264653", # Q4 — deep navy |
| 82 | + "#c0bebb", # +1σ envelope |
| 83 | + "#c0bebb", # −1σ envelope |
| 84 | + "#888886", # y = x reference |
| 85 | + ), |
65 | 86 | font_family=font, |
66 | 87 | title_font_family=font, |
67 | 88 | title_font_size=56, |
|
72 | 93 | value_font_size=28, |
73 | 94 | tooltip_font_size=28, |
74 | 95 | tooltip_font_family=font, |
75 | | - opacity=0.55, |
76 | | - opacity_hover=0.90, |
77 | | - stroke_opacity=1, |
| 96 | + opacity=0.60, |
| 97 | + opacity_hover=0.95, |
| 98 | + stroke_opacity=0.7, |
78 | 99 | stroke_opacity_hover=1, |
79 | 100 | ) |
80 | 101 |
|
81 | | -# Chart |
| 102 | +# Chart — interactivity enabled for SVG hover tooltips |
82 | 103 | chart = pygal.XY( |
83 | 104 | width=4800, |
84 | 105 | height=2700, |
|
97 | 118 | x_value_formatter=lambda x: f"{x:.1f}", |
98 | 119 | value_formatter=lambda y: f"{y:.1f}", |
99 | 120 | margin_bottom=100, |
100 | | - margin_left=60, |
101 | | - margin_right=40, |
102 | | - margin_top=50, |
| 121 | + margin_left=80, |
| 122 | + margin_right=30, |
| 123 | + margin_top=40, |
103 | 124 | range=(ref_start, ref_end), |
104 | 125 | xrange=(ref_start, ref_end), |
105 | 126 | x_labels_major_count=8, |
106 | 127 | y_labels_major_count=8, |
107 | 128 | print_values=False, |
108 | 129 | print_zeroes=False, |
109 | | - js=[], |
| 130 | + truncate_legend=40, |
110 | 131 | ) |
111 | 132 |
|
112 | | -# Add temporal quartile series |
113 | | -chart.add("Days 1\u2013100", early, stroke=False) |
| 133 | +# Temporal quartile scatter series |
| 134 | +chart.add("Days 1\u2013100", early, stroke=False, dots_size=9) |
114 | 135 | chart.add("Days 101\u2013200", mid_early, stroke=False) |
115 | 136 | chart.add("Days 201\u2013300", mid_late, stroke=False) |
116 | | -chart.add("Days 301\u2013399", late, stroke=False) |
| 137 | +chart.add("Days 301\u2013399", late, stroke=False, dots_size=9) |
| 138 | + |
| 139 | +# ±1σ envelope (no legend entry) |
| 140 | +env_style = {"width": 3, "dasharray": "6, 8", "linecap": "round"} |
| 141 | +chart.add(None, upper_env, stroke=True, show_dots=False, stroke_style=env_style) |
| 142 | +chart.add(None, lower_env, stroke=True, show_dots=False, stroke_style=env_style) |
117 | 143 |
|
118 | | -# Diagonal reference line |
| 144 | +# Diagonal reference line y = x |
119 | 145 | chart.add( |
120 | | - "y = x", |
| 146 | + "y = x (\u00b11\u03c3)", |
121 | 147 | ref_line, |
122 | 148 | stroke=True, |
123 | 149 | show_dots=False, |
|
0 commit comments