Skip to content

Commit e743d59

Browse files
feat(bokeh): implement scatter-lag (#5274)
## Implementation: `scatter-lag` - bokeh Implements the **bokeh** version of `scatter-lag`. **File:** `plots/scatter-lag/implementations/bokeh.py` **Parent Issue:** #5251 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/24313009887)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 1ab9619 commit e743d59

File tree

2 files changed

+342
-0
lines changed

2 files changed

+342
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
""" pyplots.ai
2+
scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis
3+
Library: bokeh 3.9.0 | Python 3.14.3
4+
Quality: 85/100 | Created: 2026-04-12
5+
"""
6+
7+
import numpy as np
8+
from bokeh.io import export_png, save
9+
from bokeh.models import ColorBar, ColumnDataSource, Label, LinearColorMapper
10+
from bokeh.palettes import Viridis256
11+
from bokeh.plotting import figure
12+
from bokeh.resources import CDN
13+
from bokeh.transform import linear_cmap
14+
15+
16+
# Data - AR(1) process with moderate positive autocorrelation
17+
np.random.seed(42)
18+
n_obs = 500
19+
phi = 0.85
20+
noise = np.random.normal(0, 1, n_obs)
21+
series = np.zeros(n_obs)
22+
series[0] = noise[0]
23+
for i in range(1, n_obs):
24+
series[i] = phi * series[i - 1] + noise[i]
25+
26+
lag = 1
27+
y_t = series[:-lag]
28+
y_t_lag = series[lag:]
29+
time_index = np.arange(len(y_t))
30+
31+
correlation = np.corrcoef(y_t, y_t_lag)[0, 1]
32+
33+
source = ColumnDataSource(data={"y_t": y_t, "y_t_lag": y_t_lag, "time_index": time_index})
34+
35+
# Plot
36+
color_mapper = LinearColorMapper(palette=Viridis256, low=time_index.min(), high=time_index.max())
37+
38+
p = figure(
39+
width=4800, height=2700, title="scatter-lag · bokeh · pyplots.ai", x_axis_label="y(t)", y_axis_label="y(t + 1)"
40+
)
41+
42+
p.scatter(
43+
x="y_t",
44+
y="y_t_lag",
45+
source=source,
46+
size=20,
47+
fill_color=linear_cmap("time_index", palette=Viridis256, low=time_index.min(), high=time_index.max()),
48+
line_color="white",
49+
line_width=0.8,
50+
fill_alpha=0.8,
51+
)
52+
53+
# Diagonal reference line (y = x)
54+
axis_min = min(y_t.min(), y_t_lag.min()) - 0.5
55+
axis_max = max(y_t.max(), y_t_lag.max()) + 0.5
56+
p.line(
57+
[axis_min, axis_max], [axis_min, axis_max], line_color="#AAAAAA", line_dash="dashed", line_width=3, line_alpha=0.5
58+
)
59+
60+
# Color bar for time index
61+
color_bar = ColorBar(
62+
color_mapper=color_mapper,
63+
title="Time Index",
64+
title_text_font_size="22pt",
65+
title_standoff=20,
66+
major_label_text_font_size="18pt",
67+
label_standoff=12,
68+
width=50,
69+
padding=40,
70+
)
71+
p.add_layout(color_bar, "right")
72+
73+
# Correlation annotation
74+
corr_label = Label(
75+
x=axis_min + 0.3, y=axis_max - 0.5, text=f"r = {correlation:.3f}", text_font_size="24pt", text_color="#333333"
76+
)
77+
p.add_layout(corr_label)
78+
79+
# Style
80+
p.title.text_font_size = "32pt"
81+
p.title.text_font_style = "normal"
82+
p.xaxis.axis_label_text_font_size = "24pt"
83+
p.yaxis.axis_label_text_font_size = "24pt"
84+
p.xaxis.major_label_text_font_size = "20pt"
85+
p.yaxis.major_label_text_font_size = "20pt"
86+
87+
p.xaxis.minor_tick_line_color = None
88+
p.yaxis.minor_tick_line_color = None
89+
p.xaxis.major_tick_line_color = None
90+
p.yaxis.major_tick_line_color = None
91+
92+
p.xgrid.grid_line_alpha = 0.2
93+
p.ygrid.grid_line_alpha = 0.2
94+
p.xgrid.grid_line_width = 1
95+
p.ygrid.grid_line_width = 1
96+
97+
p.outline_line_color = None
98+
p.background_fill_color = "#FFFFFF"
99+
p.border_fill_color = "#FFFFFF"
100+
101+
p.toolbar_location = None
102+
103+
# Save
104+
export_png(p, filename="plot.png")
105+
save(p, filename="plot.html", resources=CDN, title="scatter-lag · bokeh · pyplots.ai")
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
library: bokeh
2+
specification_id: scatter-lag
3+
created: '2026-04-12T18:12:37Z'
4+
updated: '2026-04-12T18:33:40Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 24313009887
7+
issue: 5251
8+
python_version: 3.14.3
9+
library_version: 3.9.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/bokeh/plot.png
11+
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/bokeh/plot.html
12+
quality_score: 85
13+
review:
14+
strengths:
15+
- 'Perfect spec compliance: diagonal reference line, time-colored points, r annotation,
16+
and configurable lag all implemented'
17+
- Viridis colormap is an excellent and justified choice for temporal progression
18+
— colorblind-safe and perceptually uniform
19+
- Clean KISS code structure with np.random.seed(42) ensuring reproducibility
20+
- linear_cmap transform and ColorBar show good idiomatic Bokeh usage
21+
- r=0.834 annotation directly communicates autocorrelation magnitude to the viewer
22+
weaknesses:
23+
- 'DE-01/DE-02 not publication-ready: clean layout but no distinctive typographic
24+
hierarchy or intentional spacing refinements'
25+
- 'LM-02: No HoverTool or interactive features that are distinctly Bokeh — missing
26+
a key differentiating capability that would appear in the HTML export'
27+
- Axis labels use abstract notation y(t)/y(t+1) rather than domain-specific labels
28+
with units
29+
- Marker size=20 is at the minimum bound for 499 points; slightly larger markers
30+
(25-30) with lower alpha would improve readability
31+
image_description: The plot displays approximately 499 scatter points colored using
32+
the Viridis palette (purple = early time period, yellow/green = late time period),
33+
plotting y(t) vs y(t+1) for an AR(1) time series process. A dashed diagonal reference
34+
line (y=x) runs from the lower-left to upper-right of the plot. The correlation
35+
coefficient annotation "r = 0.834" appears in the upper-left area of the plot
36+
interior. A "Time Index" color bar is displayed on the right side, scaled 0–499.
37+
The title "scatter-lag · bokeh · pyplots.ai" appears at the top-left. Axis labels
38+
show time-series notation on x and y axes. The data shows a clear positive autocorrelation
39+
pattern with points clustering along the diagonal. The background is white with
40+
subtle grid lines (alpha=0.2) and no outer frame. The visual density of overlapping
41+
semi-transparent points near the diagonal creates a band-like effect, reinforcing
42+
the autocorrelation story. White point outlines provide visual separation between
43+
overlapping markers.
44+
criteria_checklist:
45+
visual_quality:
46+
score: 26
47+
max: 30
48+
items:
49+
- id: VQ-01
50+
name: Text Legibility
51+
score: 7
52+
max: 8
53+
passed: true
54+
comment: 'Font sizes explicitly set: 32pt title, 24pt axis labels, 20pt tick
55+
labels. All readable at 4800x2700. Minor: title positioned top-left vs centered.'
56+
- id: VQ-02
57+
name: No Overlap
58+
score: 5
59+
max: 6
60+
passed: true
61+
comment: No text overlap. Point overlap natural for 499 points; alpha=0.8
62+
and white outlines mitigate it.
63+
- id: VQ-03
64+
name: Element Visibility
65+
score: 5
66+
max: 6
67+
passed: true
68+
comment: size=20 is at the lower bound for 300+ points (spec says 20-50).
69+
Points visible but slightly small.
70+
- id: VQ-04
71+
name: Color Accessibility
72+
score: 4
73+
max: 4
74+
passed: true
75+
comment: Viridis is perceptually uniform and colorblind-safe. Excellent choice.
76+
- id: VQ-05
77+
name: Layout & Canvas
78+
score: 3
79+
max: 4
80+
passed: true
81+
comment: Good proportions. Minor excess whitespace on edges. Colorbar uses
82+
right margin effectively.
83+
- id: VQ-06
84+
name: Axis Labels & Title
85+
score: 2
86+
max: 2
87+
passed: true
88+
comment: Descriptive time-series notation labels. Title format correct.
89+
design_excellence:
90+
score: 13
91+
max: 20
92+
items:
93+
- id: DE-01
94+
name: Aesthetic Sophistication
95+
score: 5
96+
max: 8
97+
passed: true
98+
comment: Thoughtful Viridis mapping for temporal progression, white background,
99+
no frame, white point outlines. Above well-configured default but not publication-ready.
100+
- id: DE-02
101+
name: Visual Refinement
102+
score: 4
103+
max: 6
104+
passed: true
105+
comment: Minor ticks removed, major ticks removed, outline removed, grid at
106+
alpha=0.2. Genuine refinements above default. Grid still present.
107+
- id: DE-03
108+
name: Data Storytelling
109+
score: 4
110+
max: 6
111+
passed: true
112+
comment: Temporal color progression + r=0.834 annotation guides viewer to
113+
autocorrelation story. Diagonal reference gives comparison anchor. Good
114+
focal point.
115+
spec_compliance:
116+
score: 15
117+
max: 15
118+
items:
119+
- id: SC-01
120+
name: Plot Type
121+
score: 5
122+
max: 5
123+
passed: true
124+
comment: 'Correct lag plot: scatter of y(t) vs y(t+lag).'
125+
- id: SC-02
126+
name: Required Features
127+
score: 4
128+
max: 4
129+
passed: true
130+
comment: Diagonal reference line, points colored by time index, correlation
131+
annotation, configurable lag - all present.
132+
- id: SC-03
133+
name: Data Mapping
134+
score: 3
135+
max: 3
136+
passed: true
137+
comment: X=y(t), Y=y(t+1), correct lag-1 mapping.
138+
- id: SC-04
139+
name: Title & Legend
140+
score: 3
141+
max: 3
142+
passed: true
143+
comment: Title 'scatter-lag · bokeh · pyplots.ai' matches required format.
144+
Color bar correctly labeled.
145+
data_quality:
146+
score: 14
147+
max: 15
148+
items:
149+
- id: DQ-01
150+
name: Feature Coverage
151+
score: 5
152+
max: 6
153+
passed: true
154+
comment: 'AR(1) with phi=0.85 demonstrates strong positive autocorrelation
155+
clearly. Minor: only one lag shown, only positive autocorrelation case demonstrated.'
156+
- id: DQ-02
157+
name: Realistic Context
158+
score: 5
159+
max: 5
160+
passed: true
161+
comment: AR(1) time series / temperature anomaly context is realistic and
162+
scientifically neutral.
163+
- id: DQ-03
164+
name: Appropriate Scale
165+
score: 4
166+
max: 4
167+
passed: true
168+
comment: Values in range ~[-5, 5] appropriate for temperature anomalies.
169+
code_quality:
170+
score: 10
171+
max: 10
172+
items:
173+
- id: CQ-01
174+
name: KISS Structure
175+
score: 3
176+
max: 3
177+
passed: true
178+
comment: Clean Imports -> Data -> Plot -> Save. No classes or extra functions.
179+
- id: CQ-02
180+
name: Reproducibility
181+
score: 2
182+
max: 2
183+
passed: true
184+
comment: np.random.seed(42) set.
185+
- id: CQ-03
186+
name: Clean Imports
187+
score: 2
188+
max: 2
189+
passed: true
190+
comment: 'All imports used: numpy, export_png/save, ColorBar/ColumnDataSource/Label/LinearColorMapper,
191+
Viridis256, figure, CDN, linear_cmap.'
192+
- id: CQ-04
193+
name: Code Elegance
194+
score: 2
195+
max: 2
196+
passed: true
197+
comment: Clean, Pythonic. No over-engineering or fake UI elements.
198+
- id: CQ-05
199+
name: Output & API
200+
score: 1
201+
max: 1
202+
passed: true
203+
comment: Saves as plot.png via export_png(). Current Bokeh API.
204+
library_mastery:
205+
score: 7
206+
max: 10
207+
items:
208+
- id: LM-01
209+
name: Idiomatic Usage
210+
score: 4
211+
max: 5
212+
passed: true
213+
comment: Good use of ColumnDataSource, LinearColorMapper, linear_cmap transform,
214+
ColorBar via add_layout. Follows Bokeh recommended patterns.
215+
- id: LM-02
216+
name: Distinctive Features
217+
score: 3
218+
max: 5
219+
passed: false
220+
comment: linear_cmap transform for data-driven per-point coloring and HTML
221+
export are Bokeh-specific. Missing HoverTool which is a truly unique Bokeh
222+
interactive feature.
223+
verdict: REJECTED
224+
impl_tags:
225+
dependencies: []
226+
techniques:
227+
- colorbar
228+
- annotations
229+
- html-export
230+
patterns:
231+
- data-generation
232+
- columndatasource
233+
dataprep: []
234+
styling:
235+
- custom-colormap
236+
- alpha-blending
237+
- edge-highlighting

0 commit comments

Comments
 (0)