{"spec_id":"scatter-lag","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nscatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-06-24\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1\n\n# Data — synthetic AR(1) process with strong positive autocorrelation\nnp.random.seed(42)\nn_points = 500\nlag = 1\nphi = 0.85\nnoise = np.random.normal(0, 1, n_points)\nvalues = np.zeros(n_points)\nvalues[0] = noise[0]\nfor i in range(1, n_points):\n    values[i] = phi * values[i - 1] + noise[i]\n\ny_t = values[:-lag]\ny_t_lag = values[lag:]\nr_value = np.corrcoef(y_t, y_t_lag)[0, 1]\n\ndf = pd.DataFrame({\"y_t\": y_t, \"y_t_lag\": y_t_lag, \"time_index\": np.arange(n_points - lag)})\n\n# Axis domain with margin\nmargin = 0.4\naxis_min = min(df[\"y_t\"].min(), df[\"y_t_lag\"].min()) - margin\naxis_max = max(df[\"y_t\"].max(), df[\"y_t_lag\"].max()) + margin\ndomain = [axis_min, axis_max]\n\n# Reference line (y = x diagonal — perfect persistence baseline)\nref_df = pd.DataFrame({\"x\": [axis_min, axis_max], \"y\": [axis_min, axis_max]})\n\n# Correlation annotation (top-left, away from the dense cluster)\nannot_df = pd.DataFrame({\"x\": [axis_min + 0.25], \"y\": [axis_max - 0.3], \"label\": [f\"r = {r_value:.3f}\"]})\n\ntitle_str = \"scatter-lag · python · altair · anyplot.ai\"\nsubtitle_str = f\"AR(1) process (φ = {phi}) | lag = {lag} | n = {n_points - lag}\"\n\n# Chart layers\nreference_line = (\n    alt.Chart(ref_df).mark_line(strokeDash=[8, 6], strokeWidth=1.5, color=INK_MUTED).encode(x=\"x:Q\", y=\"y:Q\")\n)\n\npoints = (\n    alt.Chart(df)\n    .mark_point(size=40, filled=True, strokeWidth=0.5, stroke=PAGE_BG, opacity=0.5)\n    .encode(\n        x=alt.X(\"y_t:Q\", title=\"y(t)\", scale=alt.Scale(domain=domain), axis=alt.Axis(tickCount=8)),\n        y=alt.Y(\"y_t_lag:Q\", title=\"y(t + 1)\", scale=alt.Scale(domain=domain), axis=alt.Axis(tickCount=8)),\n        # Imprint sequential colormap: brand green → blue (single-polarity continuous)\n        color=alt.Color(\n            \"time_index:Q\",\n            scale=alt.Scale(range=[\"#009E73\", \"#4467A3\"]),\n            legend=alt.Legend(\n                title=\"Time Index\",\n                titleFontSize=10,\n                labelFontSize=10,\n                gradientLength=180,\n                gradientThickness=12,\n                orient=\"right\",\n                offset=8,\n            ),\n        ),\n        tooltip=[\n            alt.Tooltip(\"y_t:Q\", title=\"y(t)\", format=\".2f\"),\n            alt.Tooltip(\"y_t_lag:Q\", title=\"y(t+1)\", format=\".2f\"),\n            alt.Tooltip(\"time_index:Q\", title=\"Time Index\"),\n        ],\n    )\n)\n\n# OLS regression line — slope ≈ φ, contrasts with the y=x diagonal to show autocorrelation strength\nregression_line = (\n    alt.Chart(df)\n    .transform_regression(\"y_t\", \"y_t_lag\", method=\"linear\")\n    .mark_line(strokeWidth=2.5, color=BRAND, opacity=0.85)\n    .encode(x=\"y_t:Q\", y=\"y_t_lag:Q\")\n)\n\nannotation = (\n    alt.Chart(annot_df)\n    .mark_text(align=\"left\", baseline=\"top\", fontSize=12, fontWeight=\"bold\", color=INK)\n    .encode(x=\"x:Q\", y=\"y:Q\", text=\"label:N\")\n)\n\nchart = (\n    (reference_line + points + regression_line + annotation)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        padding={\"left\": 0, \"right\": 0, \"top\": 0, \"bottom\": 0},\n        title=alt.Title(title_str, fontSize=16, subtitle=subtitle_str, subtitleFontSize=12, subtitleColor=INK_SOFT),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0, continuousWidth=620, continuousHeight=320)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        grid=True,\n        gridOpacity=0.12,\n        gridWidth=0.5,\n        gridColor=INK,\n        domainWidth=0,\n        tickSize=4,\n        tickWidth=0.8,\n        tickColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n    .configure_title(anchor=\"start\", offset=12, color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save PNG + HTML\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n\n# Pad PNG to exact 3200×1800 (altair canvas hard contract)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n"}