Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions plots/scatter-lag/implementations/altair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
""" pyplots.ai
scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis
Library: altair 6.0.0 | Python 3.14.3
Quality: 90/100 | Created: 2026-04-12
"""

import altair as alt
import numpy as np
import pandas as pd


# Data - synthetic AR(1) process with moderate autocorrelation
np.random.seed(42)
n_points = 500
lag = 1
phi = 0.85
noise = np.random.normal(0, 1, n_points)
values = np.zeros(n_points)
values[0] = noise[0]
for i in range(1, n_points):
values[i] = phi * values[i - 1] + noise[i]

y_t = values[:-lag]
y_t_lag = values[lag:]
r_value = np.corrcoef(y_t, y_t_lag)[0, 1]

df = pd.DataFrame({"y_t": y_t, "y_t_lag": y_t_lag, "time_index": np.arange(n_points - lag)})

# Reference line (y = x diagonal)
margin = 0.5
axis_min = min(df["y_t"].min(), df["y_t_lag"].min()) - margin
axis_max = max(df["y_t"].max(), df["y_t_lag"].max()) + margin
ref_df = pd.DataFrame({"x": [axis_min, axis_max], "y": [axis_min, axis_max]})

# Annotation for correlation coefficient
annot_df = pd.DataFrame({"x": [axis_max - 0.3], "y": [axis_min + 0.5], "label": [f"r = {r_value:.3f}"]})

# Reference line
reference_line = (
alt.Chart(ref_df).mark_line(strokeDash=[8, 6], strokeWidth=1.5, color="#aaaaaa").encode(x="x:Q", y="y:Q")
)

# Scatter points with reduced size/opacity to prevent overplotting
points = (
alt.Chart(df)
.mark_point(size=45, filled=True, strokeWidth=0.5, stroke="white", opacity=0.45)
.encode(
x=alt.X("y_t:Q", title="y(t)", scale=alt.Scale(domain=[axis_min, axis_max]), axis=alt.Axis(tickCount=10)),
y=alt.Y(
"y_t_lag:Q", title="y(t + 1)", scale=alt.Scale(domain=[axis_min, axis_max]), axis=alt.Axis(tickCount=10)
),
color=alt.Color(
"time_index:Q",
scale=alt.Scale(scheme="viridis"),
legend=alt.Legend(
title="Time Index",
titleFontSize=16,
labelFontSize=16,
gradientLength=280,
gradientThickness=14,
orient="right",
offset=10,
),
),
tooltip=[
alt.Tooltip("y_t:Q", title="y(t)", format=".2f"),
alt.Tooltip("y_t_lag:Q", title="y(t+1)", format=".2f"),
alt.Tooltip("time_index:Q", title="Time Index"),
],
)
)

# Correlation annotation
annotation = (
alt.Chart(annot_df)
.mark_text(align="right", baseline="bottom", fontSize=20, fontWeight="bold", color="#333333")
.encode(x="x:Q", y="y:Q", text="label:N")
)

chart = (
(reference_line + points + annotation)
.properties(
width=1600,
height=900,
title=alt.Title(
"scatter-lag · altair · pyplots.ai",
fontSize=28,
subtitle=f"AR(1) process (φ = {phi}) | lag = {lag}",
subtitleFontSize=18,
subtitleColor="#666666",
),
)
.configure_axis(
labelFontSize=18,
titleFontSize=22,
grid=True,
gridOpacity=0.15,
gridWidth=0.5,
domainWidth=0,
tickSize=6,
tickWidth=0.8,
tickColor="#999999",
labelColor="#444444",
titleColor="#333333",
)
.configure_view(strokeWidth=0)
.configure_title(anchor="start", offset=10)
)

# Save
chart.save("plot.png", scale_factor=3.0)
chart.save("plot.html")
238 changes: 238 additions & 0 deletions plots/scatter-lag/metadata/altair.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
library: altair
specification_id: scatter-lag
created: '2026-04-12T18:11:36Z'
updated: '2026-04-12T18:24:56Z'
generated_by: claude-opus-4-5-20251101
workflow_run: 24313009897
issue: 5251
python_version: 3.14.3
library_version: 6.0.0
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/altair/plot.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/altair/plot.html
quality_score: 90
review:
strengths:
- Viridis colormap with opacity=0.45 and white edge strokes creates a professional,
layered look
- 'All required spec features present: diagonal reference line, time-index color
coding, correlation annotation, configurable lag'
- Altair layer composition (reference_line + points + annotation) is idiomatic and
clean
- Explicit font sizing throughout ensures legibility at 4800x2700px
- Subtle grid (opacity 0.15), no axis border, and removed domain line give a polished
clean background
weaknesses:
- DE-01 held back by lacking a truly distinctive visual signature (e.g., custom
highlight color, marginal distribution element)
- DE-02 could reach 6/6 with more generous outer padding and intentional whitespace
strategy
- 'VQ-06 partial: axis labels use mathematical notation without physical units (appropriate
for abstract data but not 2/2)'
- 'LM-02: could leverage Altair''s selection/brush interactivity or faceting to
show multiple lag values side-by-side'
image_description: A scatter lag plot showing 499 points from a synthetic AR(1)
process (phi=0.85). The x-axis is labeled "y(t)" and the y-axis "y(t + 1)", both
ranging from about -5 to 6. Points are colored using the viridis colormap (purple/blue
= early time index, yellow-green = late time index), with opacity=0.45 and white
edge strokes. A dashed gray diagonal reference line (y=x) runs from bottom-left
to top-right, making the strong positive autocorrelation immediately visible.
A bold "r = 0.834" annotation appears in the lower-right of the plot area. The
title reads "scatter-lag · altair · pyplots.ai" with subtitle "AR(1) process (phi
= 0.85) | lag = 1". A vertical viridis color legend labeled "Time Index" is positioned
on the right. The background is white with an extremely subtle grid, no axis border,
and clean typography throughout.
criteria_checklist:
visual_quality:
score: 29
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 8
max: 8
passed: true
comment: 'All font sizes explicitly set: title 28pt, subtitle 18pt, axis titles
22pt, tick labels 18pt, legend 16pt, annotation 20pt bold'
- id: VQ-02
name: No Overlap
score: 6
max: 6
passed: true
comment: No text or data element collisions. Legend well-separated from plot
area.
- id: VQ-03
name: Element Visibility
score: 6
max: 6
passed: true
comment: 500 points at size=45, opacity=0.45. Matches 300+ guideline. White
strokes help distinguish overlapping points.
- id: VQ-04
name: Color Accessibility
score: 4
max: 4
passed: true
comment: Viridis is perceptually uniform and colorblind-safe.
- id: VQ-05
name: Layout & Canvas
score: 4
max: 4
passed: true
comment: Plot fills ~70% of canvas with balanced margins. Well-distributed
elements.
- id: VQ-06
name: Axis Labels & Title
score: 1
max: 2
passed: true
comment: Labels y(t) and y(t+1) are descriptive but carry no physical units.
design_excellence:
score: 13
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 5
max: 8
passed: true
comment: 'Above defaults: viridis colormap, opacity with white edge strokes,
dashed reference line, no border/domain lines. Cohesive but not publication-ready.'
- id: DE-02
name: Visual Refinement
score: 4
max: 6
passed: true
comment: Subtle grid (0.15 opacity), removed axis border, no domain line,
soft tick colors. More refined than defaults.
- id: DE-03
name: Data Storytelling
score: 4
max: 6
passed: true
comment: r=0.834 annotation quantifies the pattern; time coloring reveals
temporal structure; diagonal reference guides interpretation.
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: 'Correct lag plot: scatter of y(t) vs y(t+k).'
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: Diagonal reference line, color by time index, correlation annotation,
configurable lag variable all present.
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: X=y(t), Y=y(t+1). Correct orientation. All 499 points visible.
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title exactly 'scatter-lag · altair · pyplots.ai'. Legend labeled
'Time Index'.
data_quality:
score: 15
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 6
max: 6
passed: true
comment: 'Shows all key aspects: autocorrelation pattern, diagonal reference,
temporal coloring, correlation quantification.'
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: AR(1) process (phi=0.85) is a real, neutral statistical scenario.
Parameters clearly labeled in subtitle.
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: 500 observations with realistic AR(1) value range (~-5 to 6). Correlation
r=0.834 expected for phi=0.85.
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: 'Linear: imports -> data -> chart layers -> save. No functions or
classes.'
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: np.random.seed(42) set.
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: altair, numpy, pandas — all used, none unnecessary.
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Layer composition (reference_line + points + annotation) is clean
and idiomatic Altair.
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: Saves plot.png with scale_factor=3.0 and plot.html. Current altair
6.0.0 API.
library_features:
score: 8
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 5
max: 5
passed: true
comment: 'Expert Altair: declarative encoding with typed fields (Q/N), layer
composition via +, alt.Scale/Axis/Legend, configure_* global styling.'
- id: LM-02
name: Distinctive Features
score: 3
max: 5
passed: true
comment: Layer composition, interactive tooltips, HTML export, declarative
viridis scheme. Distinctly Altair but basic within library capabilities.
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- layer-composition
- annotations
- hover-tooltips
- html-export
patterns:
- data-generation
dataprep: []
styling:
- custom-colormap
- alpha-blending
- edge-highlighting
Loading