Skip to content

Commit 2390f0a

Browse files
feat(altair): implement scatter-lag (#5272)
## Implementation: `scatter-lag` - altair Implements the **altair** version of `scatter-lag`. **File:** `plots/scatter-lag/implementations/altair.py` **Parent Issue:** #5251 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/24313009897)* --------- 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 f0de48c commit 2390f0a

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
""" pyplots.ai
2+
scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis
3+
Library: altair 6.0.0 | Python 3.14.3
4+
Quality: 90/100 | Created: 2026-04-12
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
11+
12+
# Data - synthetic AR(1) process with moderate autocorrelation
13+
np.random.seed(42)
14+
n_points = 500
15+
lag = 1
16+
phi = 0.85
17+
noise = np.random.normal(0, 1, n_points)
18+
values = np.zeros(n_points)
19+
values[0] = noise[0]
20+
for i in range(1, n_points):
21+
values[i] = phi * values[i - 1] + noise[i]
22+
23+
y_t = values[:-lag]
24+
y_t_lag = values[lag:]
25+
r_value = np.corrcoef(y_t, y_t_lag)[0, 1]
26+
27+
df = pd.DataFrame({"y_t": y_t, "y_t_lag": y_t_lag, "time_index": np.arange(n_points - lag)})
28+
29+
# Reference line (y = x diagonal)
30+
margin = 0.5
31+
axis_min = min(df["y_t"].min(), df["y_t_lag"].min()) - margin
32+
axis_max = max(df["y_t"].max(), df["y_t_lag"].max()) + margin
33+
ref_df = pd.DataFrame({"x": [axis_min, axis_max], "y": [axis_min, axis_max]})
34+
35+
# Annotation for correlation coefficient
36+
annot_df = pd.DataFrame({"x": [axis_max - 0.3], "y": [axis_min + 0.5], "label": [f"r = {r_value:.3f}"]})
37+
38+
# Reference line
39+
reference_line = (
40+
alt.Chart(ref_df).mark_line(strokeDash=[8, 6], strokeWidth=1.5, color="#aaaaaa").encode(x="x:Q", y="y:Q")
41+
)
42+
43+
# Scatter points with reduced size/opacity to prevent overplotting
44+
points = (
45+
alt.Chart(df)
46+
.mark_point(size=45, filled=True, strokeWidth=0.5, stroke="white", opacity=0.45)
47+
.encode(
48+
x=alt.X("y_t:Q", title="y(t)", scale=alt.Scale(domain=[axis_min, axis_max]), axis=alt.Axis(tickCount=10)),
49+
y=alt.Y(
50+
"y_t_lag:Q", title="y(t + 1)", scale=alt.Scale(domain=[axis_min, axis_max]), axis=alt.Axis(tickCount=10)
51+
),
52+
color=alt.Color(
53+
"time_index:Q",
54+
scale=alt.Scale(scheme="viridis"),
55+
legend=alt.Legend(
56+
title="Time Index",
57+
titleFontSize=16,
58+
labelFontSize=16,
59+
gradientLength=280,
60+
gradientThickness=14,
61+
orient="right",
62+
offset=10,
63+
),
64+
),
65+
tooltip=[
66+
alt.Tooltip("y_t:Q", title="y(t)", format=".2f"),
67+
alt.Tooltip("y_t_lag:Q", title="y(t+1)", format=".2f"),
68+
alt.Tooltip("time_index:Q", title="Time Index"),
69+
],
70+
)
71+
)
72+
73+
# Correlation annotation
74+
annotation = (
75+
alt.Chart(annot_df)
76+
.mark_text(align="right", baseline="bottom", fontSize=20, fontWeight="bold", color="#333333")
77+
.encode(x="x:Q", y="y:Q", text="label:N")
78+
)
79+
80+
chart = (
81+
(reference_line + points + annotation)
82+
.properties(
83+
width=1600,
84+
height=900,
85+
title=alt.Title(
86+
"scatter-lag · altair · pyplots.ai",
87+
fontSize=28,
88+
subtitle=f"AR(1) process (φ = {phi}) | lag = {lag}",
89+
subtitleFontSize=18,
90+
subtitleColor="#666666",
91+
),
92+
)
93+
.configure_axis(
94+
labelFontSize=18,
95+
titleFontSize=22,
96+
grid=True,
97+
gridOpacity=0.15,
98+
gridWidth=0.5,
99+
domainWidth=0,
100+
tickSize=6,
101+
tickWidth=0.8,
102+
tickColor="#999999",
103+
labelColor="#444444",
104+
titleColor="#333333",
105+
)
106+
.configure_view(strokeWidth=0)
107+
.configure_title(anchor="start", offset=10)
108+
)
109+
110+
# Save
111+
chart.save("plot.png", scale_factor=3.0)
112+
chart.save("plot.html")
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
library: altair
2+
specification_id: scatter-lag
3+
created: '2026-04-12T18:11:36Z'
4+
updated: '2026-04-12T18:24:56Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 24313009897
7+
issue: 5251
8+
python_version: 3.14.3
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/altair/plot.png
11+
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/altair/plot.html
12+
quality_score: 90
13+
review:
14+
strengths:
15+
- Viridis colormap with opacity=0.45 and white edge strokes creates a professional,
16+
layered look
17+
- 'All required spec features present: diagonal reference line, time-index color
18+
coding, correlation annotation, configurable lag'
19+
- Altair layer composition (reference_line + points + annotation) is idiomatic and
20+
clean
21+
- Explicit font sizing throughout ensures legibility at 4800x2700px
22+
- Subtle grid (opacity 0.15), no axis border, and removed domain line give a polished
23+
clean background
24+
weaknesses:
25+
- DE-01 held back by lacking a truly distinctive visual signature (e.g., custom
26+
highlight color, marginal distribution element)
27+
- DE-02 could reach 6/6 with more generous outer padding and intentional whitespace
28+
strategy
29+
- 'VQ-06 partial: axis labels use mathematical notation without physical units (appropriate
30+
for abstract data but not 2/2)'
31+
- 'LM-02: could leverage Altair''s selection/brush interactivity or faceting to
32+
show multiple lag values side-by-side'
33+
image_description: A scatter lag plot showing 499 points from a synthetic AR(1)
34+
process (phi=0.85). The x-axis is labeled "y(t)" and the y-axis "y(t + 1)", both
35+
ranging from about -5 to 6. Points are colored using the viridis colormap (purple/blue
36+
= early time index, yellow-green = late time index), with opacity=0.45 and white
37+
edge strokes. A dashed gray diagonal reference line (y=x) runs from bottom-left
38+
to top-right, making the strong positive autocorrelation immediately visible.
39+
A bold "r = 0.834" annotation appears in the lower-right of the plot area. The
40+
title reads "scatter-lag · altair · pyplots.ai" with subtitle "AR(1) process (phi
41+
= 0.85) | lag = 1". A vertical viridis color legend labeled "Time Index" is positioned
42+
on the right. The background is white with an extremely subtle grid, no axis border,
43+
and clean typography throughout.
44+
criteria_checklist:
45+
visual_quality:
46+
score: 29
47+
max: 30
48+
items:
49+
- id: VQ-01
50+
name: Text Legibility
51+
score: 8
52+
max: 8
53+
passed: true
54+
comment: 'All font sizes explicitly set: title 28pt, subtitle 18pt, axis titles
55+
22pt, tick labels 18pt, legend 16pt, annotation 20pt bold'
56+
- id: VQ-02
57+
name: No Overlap
58+
score: 6
59+
max: 6
60+
passed: true
61+
comment: No text or data element collisions. Legend well-separated from plot
62+
area.
63+
- id: VQ-03
64+
name: Element Visibility
65+
score: 6
66+
max: 6
67+
passed: true
68+
comment: 500 points at size=45, opacity=0.45. Matches 300+ guideline. White
69+
strokes help distinguish overlapping points.
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.
76+
- id: VQ-05
77+
name: Layout & Canvas
78+
score: 4
79+
max: 4
80+
passed: true
81+
comment: Plot fills ~70% of canvas with balanced margins. Well-distributed
82+
elements.
83+
- id: VQ-06
84+
name: Axis Labels & Title
85+
score: 1
86+
max: 2
87+
passed: true
88+
comment: Labels y(t) and y(t+1) are descriptive but carry no physical units.
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: 'Above defaults: viridis colormap, opacity with white edge strokes,
99+
dashed reference line, no border/domain lines. Cohesive but not publication-ready.'
100+
- id: DE-02
101+
name: Visual Refinement
102+
score: 4
103+
max: 6
104+
passed: true
105+
comment: Subtle grid (0.15 opacity), removed axis border, no domain line,
106+
soft tick colors. More refined than defaults.
107+
- id: DE-03
108+
name: Data Storytelling
109+
score: 4
110+
max: 6
111+
passed: true
112+
comment: r=0.834 annotation quantifies the pattern; time coloring reveals
113+
temporal structure; diagonal reference guides interpretation.
114+
spec_compliance:
115+
score: 15
116+
max: 15
117+
items:
118+
- id: SC-01
119+
name: Plot Type
120+
score: 5
121+
max: 5
122+
passed: true
123+
comment: 'Correct lag plot: scatter of y(t) vs y(t+k).'
124+
- id: SC-02
125+
name: Required Features
126+
score: 4
127+
max: 4
128+
passed: true
129+
comment: Diagonal reference line, color by time index, correlation annotation,
130+
configurable lag variable all present.
131+
- id: SC-03
132+
name: Data Mapping
133+
score: 3
134+
max: 3
135+
passed: true
136+
comment: X=y(t), Y=y(t+1). Correct orientation. All 499 points visible.
137+
- id: SC-04
138+
name: Title & Legend
139+
score: 3
140+
max: 3
141+
passed: true
142+
comment: Title exactly 'scatter-lag · altair · pyplots.ai'. Legend labeled
143+
'Time Index'.
144+
data_quality:
145+
score: 15
146+
max: 15
147+
items:
148+
- id: DQ-01
149+
name: Feature Coverage
150+
score: 6
151+
max: 6
152+
passed: true
153+
comment: 'Shows all key aspects: autocorrelation pattern, diagonal reference,
154+
temporal coloring, correlation quantification.'
155+
- id: DQ-02
156+
name: Realistic Context
157+
score: 5
158+
max: 5
159+
passed: true
160+
comment: AR(1) process (phi=0.85) is a real, neutral statistical scenario.
161+
Parameters clearly labeled in subtitle.
162+
- id: DQ-03
163+
name: Appropriate Scale
164+
score: 4
165+
max: 4
166+
passed: true
167+
comment: 500 observations with realistic AR(1) value range (~-5 to 6). Correlation
168+
r=0.834 expected for phi=0.85.
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: 'Linear: imports -> data -> chart layers -> save. No functions or
179+
classes.'
180+
- id: CQ-02
181+
name: Reproducibility
182+
score: 2
183+
max: 2
184+
passed: true
185+
comment: np.random.seed(42) set.
186+
- id: CQ-03
187+
name: Clean Imports
188+
score: 2
189+
max: 2
190+
passed: true
191+
comment: altair, numpy, pandas — all used, none unnecessary.
192+
- id: CQ-04
193+
name: Code Elegance
194+
score: 2
195+
max: 2
196+
passed: true
197+
comment: Layer composition (reference_line + points + annotation) is clean
198+
and idiomatic Altair.
199+
- id: CQ-05
200+
name: Output & API
201+
score: 1
202+
max: 1
203+
passed: true
204+
comment: Saves plot.png with scale_factor=3.0 and plot.html. Current altair
205+
6.0.0 API.
206+
library_features:
207+
score: 8
208+
max: 10
209+
items:
210+
- id: LM-01
211+
name: Idiomatic Usage
212+
score: 5
213+
max: 5
214+
passed: true
215+
comment: 'Expert Altair: declarative encoding with typed fields (Q/N), layer
216+
composition via +, alt.Scale/Axis/Legend, configure_* global styling.'
217+
- id: LM-02
218+
name: Distinctive Features
219+
score: 3
220+
max: 5
221+
passed: true
222+
comment: Layer composition, interactive tooltips, HTML export, declarative
223+
viridis scheme. Distinctly Altair but basic within library capabilities.
224+
verdict: APPROVED
225+
impl_tags:
226+
dependencies: []
227+
techniques:
228+
- layer-composition
229+
- annotations
230+
- hover-tooltips
231+
- html-export
232+
patterns:
233+
- data-generation
234+
dataprep: []
235+
styling:
236+
- custom-colormap
237+
- alpha-blending
238+
- edge-highlighting

0 commit comments

Comments
 (0)