{"spec_id":"acf-pacf","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nacf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this file's directory from sys.path so `import pygal` resolves to the\n# installed package, not this script (which shares the same name).\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _this_dir]\n\nimport io\nimport xml.etree.ElementTree as ET\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom PIL import Image, ImageDraw, ImageFont\nfrom pygal.style import Style\nfrom statsmodels.tsa.stattools import acf, pacf\n\n\n# Theme-adaptive tokens (Imprint palette)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette — canonical order, theme-independent\nIMPRINT_PALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\nANYPLOT_AMBER = \"#DDCC77\"  # significance threshold lines (caution/warning role)\n\n# Semantic color roles for this chart\nHIGHLIGHT_COLOR = IMPRINT_PALETTE[0]  # #009E73 — significant bars (first series, always)\nMUTED_BAR_COLOR = INK_MUTED  # non-significant bars\nCONF_LINE_COLOR = ANYPLOT_AMBER  # 95% CI threshold lines\nZERO_LINE_COLOR = INK_MUTED  # zero baseline\n\n\ndef hex_to_rgb(hex_str):\n    h = hex_str.lstrip(\"#\")\n    return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))\n\n\n# Data — synthetic monthly airline-style passenger data with trend and seasonality\nnp.random.seed(42)\nn_obs = 200\nt = np.arange(n_obs)\ntrend = 0.05 * t\nseasonal = 10 * np.sin(2 * np.pi * t / 12)\nnoise = np.random.normal(0, 2, n_obs)\npassengers = 100 + trend + seasonal + noise\n\n# ACF/PACF computation\nn_lags = 36\nacf_values = acf(passengers, nlags=n_lags, fft=True)\npacf_values = pacf(passengers, nlags=n_lags, method=\"ywm\")\nconf_bound = 1.96 / np.sqrt(n_obs)\n\n# Style — Imprint palette, theme-adaptive chrome; canvas = 3200×1800 (two 3200×900 panels)\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT_PALETTE,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n    font_family=\"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n    title_font_family=\"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n    label_font_family=\"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n    value_font_family=\"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n)\n\n# Y-axis ranges tailored to data\nacf_min, acf_max = -0.5, 1.1\npacf_min, pacf_max = -0.5, 1.0\n\n# Each panel: 3200×900; two panels stacked → 3200×1800 landscape\ncommon_config = {\n    \"width\": 3200,\n    \"height\": 900,\n    \"style\": custom_style,\n    \"show_legend\": False,\n    \"show_y_guides\": True,\n    \"show_x_guides\": False,\n    \"margin\": 20,\n    \"margin_left\": 130,\n    \"margin_right\": 60,\n    \"spacing\": 80,\n    \"truncate_label\": -1,\n    \"print_values\": False,\n    \"show_minor_x_labels\": False,\n    \"x_labels_major_every\": 4,\n    \"rounded_bars\": 2,\n}\n\n# ACF chart (top panel)\nacf_chart = pygal.Bar(\n    **common_config,\n    x_title=\"\",\n    y_title=\"ACF\",\n    title=\"acf-pacf · python · pygal · anyplot.ai\",\n    margin_bottom=10,\n    margin_top=30,\n    range=(acf_min, acf_max),\n)\nacf_chart.x_labels = [str(i) for i in range(n_lags + 1)]\nacf_chart.y_labels = [-0.4, -0.2, 0.0, 0.5, 1.0]\nacf_chart.add(\n    \"ACF\",\n    [{\"value\": round(v, 4), \"color\": HIGHLIGHT_COLOR if abs(v) > conf_bound else MUTED_BAR_COLOR} for v in acf_values],\n)\n\n# PACF chart (bottom panel)\npacf_chart = pygal.Bar(\n    **common_config, x_title=\"Lag\", y_title=\"PACF\", title=\"\", margin_bottom=70, margin_top=5, range=(pacf_min, pacf_max)\n)\npacf_chart.x_labels = [str(i) for i in range(1, n_lags + 1)]\npacf_chart.y_labels = [-0.4, -0.2, 0.0, 0.5, 1.0]\npacf_chart.add(\n    \"PACF\",\n    [\n        {\"value\": round(v, 4), \"color\": HIGHLIGHT_COLOR if abs(v) > conf_bound else MUTED_BAR_COLOR}\n        for v in pacf_values[1:]\n    ],\n)\n\n# SVG namespace registration\nns = \"http://www.w3.org/2000/svg\"\nET.register_namespace(\"\", ns)\nET.register_namespace(\"xlink\", \"http://www.w3.org/1999/xlink\")\n\n\ndef _inject_ci_lines(chart, y_min, y_max):\n    \"\"\"Inject CI dashed lines and zero baseline into pygal's SVG (no native reference-line API).\"\"\"\n    root = ET.fromstring(chart.render())\n    plot_group = next((g for g in root.iter(f\"{{{ns}}}g\") if g.get(\"class\", \"\") == \"plot\"), None)\n    if plot_group is not None:\n        bg_rect = next((r for r in plot_group.iter(f\"{{{ns}}}rect\") if r.get(\"class\", \"\") == \"background\"), None)\n        if bg_rect is not None:\n            pw, ph = float(bg_rect.get(\"width\")), float(bg_rect.get(\"height\"))\n            y_range = y_max - y_min\n            # 95% CI bounds (dashed amber lines)\n            for cv in [conf_bound, -conf_bound]:\n                ly = (y_max - cv) / y_range * ph\n                line = ET.SubElement(plot_group, f\"{{{ns}}}line\")\n                line.set(\"x1\", \"0\")\n                line.set(\"y1\", f\"{ly:.1f}\")\n                line.set(\"x2\", str(pw))\n                line.set(\"y2\", f\"{ly:.1f}\")\n                line.set(\"stroke\", CONF_LINE_COLOR)\n                line.set(\"stroke-width\", \"3\")\n                line.set(\"stroke-dasharray\", \"18,10\")\n                line.set(\"opacity\", \"0.9\")\n            # Zero baseline (muted solid line)\n            zy = (y_max - 0) / y_range * ph\n            zline = ET.SubElement(plot_group, f\"{{{ns}}}line\")\n            zline.set(\"x1\", \"0\")\n            zline.set(\"y1\", f\"{zy:.1f}\")\n            zline.set(\"x2\", str(pw))\n            zline.set(\"y2\", f\"{zy:.1f}\")\n            zline.set(\"stroke\", ZERO_LINE_COLOR)\n            zline.set(\"stroke-width\", \"2.5\")\n            zline.set(\"opacity\", \"0.6\")\n    return root\n\n\n# Render both panels\npanels = []\nsvg_strings = []\nfor chart, y_min, y_max in [(acf_chart, acf_min, acf_max), (pacf_chart, pacf_min, pacf_max)]:\n    root = _inject_ci_lines(chart, y_min, y_max)\n    svg_bytes = ET.tostring(root)\n    svg_strings.append(ET.tostring(root, encoding=\"unicode\"))\n    png_data = cairosvg.svg2png(bytestring=svg_bytes, output_width=3200, output_height=900)\n    img = Image.open(io.BytesIO(png_data)).convert(\"RGB\")\n    img = img.resize((3200, 900), Image.LANCZOS)\n    panels.append(img)\n\n# Compose 3200×1800 landscape canvas\ncombined = Image.new(\"RGB\", (3200, 1800), hex_to_rgb(PAGE_BG))\ncombined.paste(panels[0], (0, 0))\ncombined.paste(panels[1], (0, 900))\n\n# Subtle divider between panels\ndraw = ImageDraw.Draw(combined)\ndraw.line([(130, 900), (3140, 900)], fill=hex_to_rgb(INK_MUTED), width=1)\n\n# CI annotation in upper-right margin\ntry:\n    font = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\", 30)\nexcept OSError:\n    font = ImageFont.load_default()\ndraw.text((2640, 24), f\"95% CI: ±{conf_bound:.3f}\", fill=CONF_LINE_COLOR, font=font)\n\ncombined.save(f\"plot-{THEME}.png\", dpi=(300, 300))\n\n# Interactive HTML output (pygal renders interactive SVG charts)\nhtml_content = (\n    \"<!DOCTYPE html>\\n<html>\\n<head>\\n\"\n    \"    <title>acf-pacf · python · pygal · anyplot.ai</title>\\n\"\n    \"    <style>\\n\"\n    f\"        body {{ font-family: 'Helvetica Neue', sans-serif; background: {PAGE_BG}; color: {INK}; margin: 0; padding: 20px; }}\\n\"\n    \"        .container { max-width: 1200px; margin: 0 auto; }\\n\"\n    \"        .chart { width: 100%; margin: 10px 0; }\\n\"\n    \"    </style>\\n</head>\\n<body>\\n\"\n    \"    <div class='container'>\\n\"\n    f\"        <div class='chart'>{svg_strings[0]}</div>\\n\"\n    f\"        <div class='chart'>{svg_strings[1]}</div>\\n\"\n    \"    </div>\\n</body>\\n</html>\"\n)\n\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(html_content)\n"}