{"spec_id":"acf-pacf","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nacf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot\nLibrary: altair 6.2.1 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove script directory from sys.path to avoid importing local altair.py\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nif _script_dir in sys.path:\n    sys.path.remove(_script_dir)\n# Also strip the '' / '.' empty-string entry added when running from this dir\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != _script_dir]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom statsmodels.tsa.stattools import acf, pacf\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\"\n\n# Imprint palette — semantic assignments for this chart\nSIGNIFICANT_COLOR = \"#009E73\"  # position 1, brand green → notable statistical finding\nSEASONAL_COLOR = \"#DDCC77\"  # amber anchor → special periodic pattern\nNONSIG_COLOR = INK_MUTED  # theme-adaptive muted → neutral/secondary lags\nCI_COLOR = \"#AE3030\"  # matte red → confidence boundary\n\n# Data — airline-style monthly passenger series with trend and seasonality\nnp.random.seed(42)\nn = 200\nt = np.arange(n)\npassengers = 100 + 0.05 * t + 10 * np.sin(2 * np.pi * t / 12) + np.random.normal(0, 2, n)\n\n# Compute ACF and PACF\nn_lags = 35\nacf_values = acf(passengers, nlags=n_lags, fft=True)\npacf_values = pacf(passengers, nlags=n_lags, method=\"ywm\")\nci_bound = 1.96 / np.sqrt(n)\n\n# Build dataframes with semantic category labels\nacf_df = pd.DataFrame({\"lag\": np.arange(len(acf_values)), \"value\": acf_values})\nacf_df[\"category\"] = \"Non-significant\"\nacf_df.loc[acf_df[\"value\"].abs() > ci_bound, \"category\"] = \"Significant\"\nacf_df.loc[(acf_df[\"lag\"] % 12 == 0) & (acf_df[\"lag\"] > 0), \"category\"] = \"Seasonal (12-month)\"\n\npacf_df = pd.DataFrame({\"lag\": np.arange(1, len(pacf_values)), \"value\": pacf_values[1:]})\npacf_df[\"category\"] = \"Non-significant\"\npacf_df.loc[pacf_df[\"value\"].abs() > ci_bound, \"category\"] = \"Significant\"\n\nci_rect_acf = pd.DataFrame({\"upper\": [ci_bound], \"lower\": [-ci_bound], \"x0\": [0], \"x1\": [n_lags]})\nci_rect_pacf = pd.DataFrame({\"upper\": [ci_bound], \"lower\": [-ci_bound], \"x0\": [1], \"x1\": [n_lags]})\nci_line_df = pd.DataFrame({\"y\": [ci_bound, -ci_bound]})\nzero_df = pd.DataFrame({\"y\": [0]})\n\n# Color scale — Imprint palette with semantic ordering\ncat_scale = alt.Scale(\n    domain=[\"Significant\", \"Seasonal (12-month)\", \"Non-significant\"],\n    range=[SIGNIFICANT_COLOR, SEASONAL_COLOR, NONSIG_COLOR],\n)\ncat_color = alt.Color(\"category:N\", scale=cat_scale, legend=alt.Legend(title=\"Lag Type\"))\n\n# Hover selections for interactive HTML (empty=True → all fully opaque in static PNG)\nsel_acf = alt.selection_point(name=\"sel_acf\", fields=[\"lag\"], on=\"pointerover\", nearest=True, empty=True)\nsel_pacf = alt.selection_point(name=\"sel_pacf\", fields=[\"lag\"], on=\"pointerover\", nearest=True, empty=True)\nfade_acf = alt.condition(sel_acf, alt.value(1.0), alt.value(0.3))\nfade_pacf = alt.condition(sel_pacf, alt.value(1.0), alt.value(0.3))\n\n# Shared x encoding\nx_lag = alt.X(\"lag:Q\", title=\"Lag (months)\", axis=alt.Axis(labelFontSize=10, titleFontSize=12))\n\n# Background layers — CI band, CI lines, zero baseline\nci_lines = (\n    alt.Chart(ci_line_df)\n    .mark_rule(strokeDash=[6, 4], strokeWidth=1.5, opacity=0.7)\n    .encode(y=\"y:Q\", color=alt.value(CI_COLOR))\n)\nzero_line = alt.Chart(zero_df).mark_rule(strokeWidth=1, opacity=0.25).encode(y=\"y:Q\", color=alt.value(INK_SOFT))\nci_band_acf = (\n    alt.Chart(ci_rect_acf)\n    .mark_rect(opacity=0.08)\n    .encode(x=\"x0:Q\", x2=\"x1:Q\", y=\"upper:Q\", y2=\"lower:Q\", color=alt.value(CI_COLOR))\n)\nci_band_pacf = (\n    alt.Chart(ci_rect_pacf)\n    .mark_rect(opacity=0.08)\n    .encode(x=\"x0:Q\", x2=\"x1:Q\", y=\"upper:Q\", y2=\"lower:Q\", color=alt.value(CI_COLOR))\n)\n\n# ACF panel — dots carry the nearest-point selection; stems share the condition\nacf_dots = (\n    alt.Chart(acf_df)\n    .mark_circle(size=80)\n    .encode(\n        x=\"lag:Q\",\n        y=\"value:Q\",\n        color=cat_color,\n        opacity=fade_acf,\n        tooltip=[\n            alt.Tooltip(\"lag:Q\", title=\"Lag\"),\n            alt.Tooltip(\"value:Q\", title=\"Correlation\", format=\".3f\"),\n            alt.Tooltip(\"category:N\", title=\"Status\"),\n        ],\n    )\n    .add_params(sel_acf)\n)\nacf_stems = (\n    alt.Chart(acf_df)\n    .mark_rule(strokeWidth=3)\n    .encode(\n        x=x_lag,\n        y=alt.Y(\"value:Q\", title=\"ACF\", axis=alt.Axis(labelFontSize=10, titleFontSize=12)),\n        y2=alt.value(0),\n        color=cat_color,\n        opacity=fade_acf,\n    )\n)\nacf_chart = (ci_band_acf + ci_lines + zero_line + acf_stems + acf_dots).properties(width=580, height=150)\n\n# PACF panel\npacf_dots = (\n    alt.Chart(pacf_df)\n    .mark_circle(size=80)\n    .encode(\n        x=\"lag:Q\",\n        y=\"value:Q\",\n        color=cat_color,\n        opacity=fade_pacf,\n        tooltip=[\n            alt.Tooltip(\"lag:Q\", title=\"Lag\"),\n            alt.Tooltip(\"value:Q\", title=\"Correlation\", format=\".3f\"),\n            alt.Tooltip(\"category:N\", title=\"Status\"),\n        ],\n    )\n    .add_params(sel_pacf)\n)\npacf_stems = (\n    alt.Chart(pacf_df)\n    .mark_rule(strokeWidth=3)\n    .encode(\n        x=x_lag,\n        y=alt.Y(\"value:Q\", title=\"PACF\", axis=alt.Axis(labelFontSize=10, titleFontSize=12)),\n        y2=alt.value(0),\n        color=cat_color,\n        opacity=fade_pacf,\n    )\n)\npacf_chart = (ci_band_pacf + ci_lines + zero_line + pacf_stems + pacf_dots).properties(width=580, height=150)\n\n# Combined chart — theme-adaptive chrome configured globally\nchart = (\n    alt.vconcat(acf_chart, pacf_chart, spacing=15)\n    .properties(\n        background=PAGE_BG,\n        title=alt.Title(\n            text=\"acf-pacf · python · altair · anyplot.ai\",\n            subtitle=\"Seasonal period ≈ 12 months · Amber stems mark seasonal lags\",\n            fontSize=16,\n            subtitleFontSize=10,\n            color=INK,\n            subtitleColor=INK_SOFT,\n        ),\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_axis(\n        grid=False,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_axisY(grid=True, gridOpacity=0.12, gridDash=[4, 4], gridColor=INK)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=10,\n    )\n)\n\n# Save PNG and HTML, then pad PNG to exact 3200×1800 target\nTW, TH = 3200, 1800\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n\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"}