{"spec_id":"acf-pacf","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nacf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\n\n\n# Theme tokens\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — position 1 for significant lags\nBRAND = \"#009E73\"\n\n# Confidence band fill/border (INK_MUTED at low opacity)\nband_fill = \"rgba(107,106,99,0.12)\" if THEME == \"light\" else \"rgba(168,167,159,0.12)\"\nband_border = \"rgba(107,106,99,0.40)\" if THEME == \"light\" else \"rgba(168,167,159,0.40)\"\n\n# Data — monthly retail sales AR(2) process\nnp.random.seed(42)\nn_obs = 200\nar1_coeff, ar2_coeff = 0.7, -0.3\nseries = np.zeros(n_obs)\nnoise = np.random.normal(0, 1, n_obs)\nfor t in range(2, n_obs):\n    series[t] = ar1_coeff * series[t - 1] + ar2_coeff * series[t - 2] + noise[t]\n\n# Compute ACF\nn_lags = 35\nseries_centered = series - np.mean(series)\nvariance = np.sum(series_centered**2)\nacf_values = np.array(\n    [np.sum(series_centered[: n_obs - k] * series_centered[k:]) / variance for k in range(n_lags + 1)]\n)\n\n# Compute PACF via Durbin-Levinson recursion\npacf_values = np.zeros(n_lags + 1)\npacf_values[0] = 1.0\npacf_values[1] = acf_values[1]\nphi = np.zeros((n_lags + 1, n_lags + 1))\nphi[1, 1] = acf_values[1]\nfor k in range(2, n_lags + 1):\n    num = acf_values[k] - np.sum(phi[k - 1, 1:k] * acf_values[k - 1 : 0 : -1])\n    den = 1.0 - np.sum(phi[k - 1, 1:k] * acf_values[1:k])\n    phi[k, k] = num / den if abs(den) > 1e-12 else 0.0\n    for j in range(1, k):\n        phi[k, j] = phi[k - 1, j] - phi[k, k] * phi[k - 1, k - j]\n    pacf_values[k] = phi[k, k]\n\nconf_bound = 1.96 / np.sqrt(n_obs)\nlags_acf = np.arange(0, n_lags + 1)\nlags_pacf = np.arange(1, n_lags + 1)\npacf_plot = pacf_values[1:]\n\nacf_significant = np.abs(acf_values) > conf_bound\npacf_significant = np.abs(pacf_plot) > conf_bound\n\n# Build stem coords using None separators (one trace per class, not per stem)\nacf_sig_x, acf_sig_y, acf_nsig_x, acf_nsig_y = [], [], [], []\nfor lag, val, sig in zip(lags_acf, acf_values, acf_significant, strict=False):\n    (acf_sig_x if sig else acf_nsig_x).extend([int(lag), int(lag), None])\n    (acf_sig_y if sig else acf_nsig_y).extend([0, float(val), None])\n\npacf_sig_x, pacf_sig_y, pacf_nsig_x, pacf_nsig_y = [], [], [], []\nfor lag, val, sig in zip(lags_pacf, pacf_plot, pacf_significant, strict=False):\n    (pacf_sig_x if sig else pacf_nsig_x).extend([int(lag), int(lag), None])\n    (pacf_sig_y if sig else pacf_nsig_y).extend([0, float(val), None])\n\n# Plot\nfig = make_subplots(\n    rows=2,\n    cols=1,\n    shared_xaxes=True,\n    vertical_spacing=0.10,\n    subplot_titles=[\"Autocorrelation (ACF)\", \"Partial Autocorrelation (PACF)\"],\n)\n\nhover_tpl = \"Lag %{x}<br>Correlation: %{y:.3f}<extra></extra>\"\n\n# ACF stems (row 1)\nif acf_sig_x:\n    fig.add_trace(\n        go.Scatter(\n            x=acf_sig_x,\n            y=acf_sig_y,\n            mode=\"lines\",\n            line={\"color\": BRAND, \"width\": 3},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=1,\n        col=1,\n    )\nif acf_nsig_x:\n    fig.add_trace(\n        go.Scatter(\n            x=acf_nsig_x,\n            y=acf_nsig_y,\n            mode=\"lines\",\n            line={\"color\": INK_MUTED, \"width\": 2},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=1,\n        col=1,\n    )\nif np.any(acf_significant):\n    fig.add_trace(\n        go.Scatter(\n            x=lags_acf[acf_significant],\n            y=acf_values[acf_significant],\n            mode=\"markers\",\n            name=\"Significant\",\n            marker={\"size\": 12, \"color\": BRAND, \"line\": {\"color\": PAGE_BG, \"width\": 2}},\n            hovertemplate=hover_tpl,\n        ),\n        row=1,\n        col=1,\n    )\nif np.any(~acf_significant):\n    fig.add_trace(\n        go.Scatter(\n            x=lags_acf[~acf_significant],\n            y=acf_values[~acf_significant],\n            mode=\"markers\",\n            name=\"Non-significant\",\n            marker={\"size\": 9, \"color\": INK_MUTED, \"line\": {\"color\": PAGE_BG, \"width\": 1.5}},\n            hovertemplate=hover_tpl,\n        ),\n        row=1,\n        col=1,\n    )\n\n# PACF stems (row 2)\nif pacf_sig_x:\n    fig.add_trace(\n        go.Scatter(\n            x=pacf_sig_x,\n            y=pacf_sig_y,\n            mode=\"lines\",\n            line={\"color\": BRAND, \"width\": 3},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=2,\n        col=1,\n    )\nif pacf_nsig_x:\n    fig.add_trace(\n        go.Scatter(\n            x=pacf_nsig_x,\n            y=pacf_nsig_y,\n            mode=\"lines\",\n            line={\"color\": INK_MUTED, \"width\": 2},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=2,\n        col=1,\n    )\nif np.any(pacf_significant):\n    fig.add_trace(\n        go.Scatter(\n            x=lags_pacf[pacf_significant],\n            y=pacf_plot[pacf_significant],\n            mode=\"markers\",\n            showlegend=False,\n            marker={\"size\": 12, \"color\": BRAND, \"line\": {\"color\": PAGE_BG, \"width\": 2}},\n            hovertemplate=hover_tpl,\n        ),\n        row=2,\n        col=1,\n    )\nif np.any(~pacf_significant):\n    fig.add_trace(\n        go.Scatter(\n            x=lags_pacf[~pacf_significant],\n            y=pacf_plot[~pacf_significant],\n            mode=\"markers\",\n            showlegend=False,\n            marker={\"size\": 9, \"color\": INK_MUTED, \"line\": {\"color\": PAGE_BG, \"width\": 1.5}},\n            hovertemplate=hover_tpl,\n        ),\n        row=2,\n        col=1,\n    )\n\n# Confidence bands and zero baselines\nfor row in [1, 2]:\n    x_start, x_end = (0, n_lags) if row == 1 else (1, n_lags)\n    fig.add_trace(\n        go.Scatter(\n            x=[x_start, x_end, x_end, x_start],\n            y=[conf_bound, conf_bound, -conf_bound, -conf_bound],\n            fill=\"toself\",\n            fillcolor=band_fill,\n            line={\"color\": band_border, \"width\": 1, \"dash\": \"dash\"},\n            showlegend=(row == 1),\n            name=\"95% Confidence\",\n            hoverinfo=\"skip\",\n        ),\n        row=row,\n        col=1,\n    )\n    fig.add_trace(\n        go.Scatter(\n            x=[x_start, x_end],\n            y=[0, 0],\n            mode=\"lines\",\n            line={\"color\": INK_SOFT, \"width\": 1.5},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=row,\n        col=1,\n    )\n\n# Title (length-adaptive font size)\ntitle_str = \"Monthly Retail Sales · acf-pacf · python · plotly · anyplot.ai\"\nsubtitle = \"AR(2) Process (n=200, φ₁=0.7, φ₂=−0.3)\"\nn = len(title_str)\ntitle_size = round(16 * (67 / n)) if n > 67 else 16\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    width=800,\n    height=450,\n    template=\"plotly_white\",\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    margin={\"l\": 90, \"r\": 50, \"t\": 130, \"b\": 100},\n    title={\n        \"text\": f\"{title_str}<br><sup style='color:{INK_SOFT};font-size:11px'>{subtitle}</sup>\",\n        \"font\": {\"size\": title_size, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    legend={\n        \"orientation\": \"h\",\n        \"yanchor\": \"top\",\n        \"y\": -0.18,\n        \"xanchor\": \"center\",\n        \"x\": 0.5,\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    hoverlabel={\"font_size\": 13},\n)\n\n# Subplot title font\nfor annotation in fig.layout.annotations:\n    annotation.font = {\"size\": 13, \"color\": INK_SOFT}\n\n# Y-axes\nfor row, label in [(1, \"ACF (correlation)\"), (2, \"PACF (correlation)\")]:\n    fig.update_yaxes(\n        title_text=label,\n        title_font={\"size\": 12, \"color\": INK},\n        tickfont={\"size\": 10, \"color\": INK_SOFT},\n        showgrid=True,\n        gridcolor=GRID,\n        zeroline=False,\n        showline=True,\n        linecolor=INK_SOFT,\n        row=row,\n        col=1,\n    )\n\n# X-axes (global then row-specific)\nfig.update_xaxes(\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    linecolor=INK_SOFT,\n    showgrid=False,\n    showspikes=True,\n    spikecolor=INK_MUTED,\n    spikethickness=1,\n    spikedash=\"dot\",\n    spikemode=\"across\",\n)\nfig.update_xaxes(showline=True, title_text=\"Lag (periods)\", title_font={\"size\": 12, \"color\": INK}, row=2, col=1)\nfig.update_xaxes(showline=False, row=1, col=1)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}