{"spec_id":"timeseries-decomposition","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ntimeseries-decomposition: Time Series Decomposition Plot\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport sys\n\nsys.path.pop(0)\n\nimport numpy as np\nimport pandas as pd\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nfrom statsmodels.tsa.seasonal import seasonal_decompose\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Monthly airline passengers (classic time series)\nnp.random.seed(42)\ndates = pd.date_range(start=\"2014-01-01\", periods=120, freq=\"MS\")\ntrend = np.linspace(100, 250, 120)\nseasonal = 30 * np.sin(2 * np.pi * np.arange(120) / 12)\nnoise = np.random.normal(0, 10, 120)\npassengers = trend + seasonal + noise\n\n# Time series and decomposition\nts = pd.Series(passengers, index=dates)\ndecomposition = seasonal_decompose(ts, model=\"additive\", period=12)\n\n# Create subplots (4 rows, shared x-axis)\nfig = make_subplots(\n    rows=4,\n    cols=1,\n    shared_xaxes=True,\n    vertical_spacing=0.08,\n    subplot_titles=(\"Original\", \"Trend\", \"Seasonal\", \"Residual\"),\n)\n\n# Add traces for each component\nfig.add_trace(\n    go.Scatter(\n        x=dates,\n        y=ts.values,\n        mode=\"lines\",\n        line=dict(color=IMPRINT[0], width=2.5),\n        name=\"Original\",\n        hovertemplate=\"<b>Original</b><br>Date: %{x|%Y-%m}<br>Value: %{y:.1f}<extra></extra>\",\n    ),\n    row=1,\n    col=1,\n)\n\nfig.add_trace(\n    go.Scatter(\n        x=dates,\n        y=decomposition.trend,\n        mode=\"lines\",\n        line=dict(color=IMPRINT[1], width=3),\n        name=\"Trend\",\n        hovertemplate=\"<b>Trend</b><br>Date: %{x|%Y-%m}<br>Value: %{y:.1f}<extra></extra>\",\n    ),\n    row=2,\n    col=1,\n)\n\nfig.add_trace(\n    go.Scatter(\n        x=dates,\n        y=decomposition.seasonal,\n        mode=\"lines\",\n        line=dict(color=IMPRINT[2], width=2.5),\n        name=\"Seasonal\",\n        hovertemplate=\"<b>Seasonal</b><br>Date: %{x|%Y-%m}<br>Value: %{y:.1f}<extra></extra>\",\n    ),\n    row=3,\n    col=1,\n)\n\nfig.add_trace(\n    go.Scatter(\n        x=dates,\n        y=decomposition.resid,\n        mode=\"lines\",\n        line=dict(color=IMPRINT[3], width=2),\n        name=\"Residual\",\n        hovertemplate=\"<b>Residual</b><br>Date: %{x|%Y-%m}<br>Value: %{y:.1f}<extra></extra>\",\n    ),\n    row=4,\n    col=1,\n)\n\n# Update layout\nfig.update_layout(\n    title=dict(text=\"timeseries-decomposition · plotly · anyplot.ai\", font=dict(size=28, color=INK), x=0.5, xanchor=\"center\"),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font=dict(color=INK),\n    showlegend=False,\n    height=900,\n    width=1600,\n    margin=dict(l=100, r=60, t=100, b=80),\n)\n\n# Update all y-axes with theme-adaptive colors\ny_axis_titles = [\"Passengers\", \"Trend\", \"Seasonal\", \"Residual\"]\nfor i, title in enumerate(y_axis_titles, 1):\n    fig.update_yaxes(\n        title=dict(text=title, font=dict(size=22, color=INK)),\n        tickfont=dict(size=18, color=INK_SOFT),\n        gridcolor=GRID,\n        gridwidth=1,\n        linecolor=INK_SOFT,\n        row=i,\n        col=1,\n    )\n\n# Update x-axes\nfor i in range(1, 5):\n    fig.update_xaxes(\n        tickfont=dict(size=18, color=INK_SOFT),\n        gridcolor=GRID,\n        gridwidth=1,\n        linecolor=INK_SOFT,\n        row=i,\n        col=1,\n    )\n\n# Bottom x-axis label\nfig.update_xaxes(title=dict(text=\"Date\", font=dict(size=22, color=INK)), row=4, col=1)\n\n# Update subplot titles font size and color\nfor annotation in fig.layout.annotations:\n    annotation.font.size = 22\n    annotation.font.color = INK\n\n# Save as PNG and HTML\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}