{"spec_id":"timeseries-decomposition","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ntimeseries-decomposition: Time Series Decomposition Plot\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom statsmodels.tsa.seasonal import seasonal_decompose\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\"\n\n# Okabe-Ito palette\nIMPRINT = [\n    \"#009E73\",  # bluish green (brand — first series)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n    \"#BD8233\",  # reddish purple\n]\n\n# Data - Monthly retail sales over 6 years (72 months = 6 full annual cycles)\nnp.random.seed(42)\nn_months = 72\ndates = pd.date_range(start=\"2018-01-01\", periods=n_months, freq=\"MS\")\n\n# Create realistic retail sales data with trend, seasonality, and noise\ntrend = np.linspace(100, 180, n_months) + np.cumsum(np.random.randn(n_months) * 0.5)\nseasonal = 25 * np.sin(2 * np.pi * np.arange(n_months) / 12)  # Annual cycle\n# Add holiday bump in December (month 12)\nholiday_bump = np.array([15 if (i + 1) % 12 == 0 else 0 for i in range(n_months)])\nseasonal = seasonal + holiday_bump\nresidual = np.random.randn(n_months) * 8\nvalues = trend + seasonal + residual\n\n# Create time series\nts = pd.Series(values, index=dates)\n\n# Perform seasonal decomposition (additive model)\ndecomposition = seasonal_decompose(ts, model=\"additive\", period=12)\n\n# Create plot with 4 subplots\nfig, axes = plt.subplots(4, 1, figsize=(16, 12), sharex=True, facecolor=PAGE_BG)\n\n# Original series\naxes[0].plot(dates, ts.values, color=IMPRINT[0], linewidth=2.5)\naxes[0].set_facecolor(PAGE_BG)\naxes[0].set_ylabel(\"Original (Sales USD)\", fontsize=20, color=INK)\naxes[0].tick_params(axis=\"y\", labelsize=16, colors=INK_SOFT)\naxes[0].grid(True, alpha=0.15, linewidth=0.8, color=INK)\naxes[0].set_title(\n    \"timeseries-decomposition · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK, pad=15\n)\n\n# Trend component\naxes[1].plot(dates, decomposition.trend, color=IMPRINT[1], linewidth=2.5)\naxes[1].set_facecolor(PAGE_BG)\naxes[1].set_ylabel(\"Trend (Sales USD)\", fontsize=20, color=INK)\naxes[1].tick_params(axis=\"y\", labelsize=16, colors=INK_SOFT)\naxes[1].grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Seasonal component\naxes[2].plot(dates, decomposition.seasonal, color=IMPRINT[2], linewidth=2.5)\naxes[2].set_facecolor(PAGE_BG)\naxes[2].set_ylabel(\"Seasonal (Sales USD)\", fontsize=20, color=INK)\naxes[2].tick_params(axis=\"y\", labelsize=16, colors=INK_SOFT)\naxes[2].grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Residual component\naxes[3].plot(dates, decomposition.resid, color=IMPRINT[3], linewidth=2.5)\naxes[3].axhline(y=0, color=INK_SOFT, linestyle=\"-\", linewidth=1, alpha=0.3)\naxes[3].set_facecolor(PAGE_BG)\naxes[3].set_ylabel(\"Residual (Sales USD)\", fontsize=20, color=INK)\naxes[3].set_xlabel(\"Date\", fontsize=20, color=INK)\naxes[3].tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT)\naxes[3].grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Remove top and right spines\nfor ax in axes:\n    ax.spines[\"top\"].set_visible(False)\n    ax.spines[\"right\"].set_visible(False)\n    ax.spines[\"left\"].set_color(INK_SOFT)\n    ax.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Adjust x-axis tick formatting\nfig.autofmt_xdate(rotation=45, ha=\"right\")\nfig.axes[-1].tick_params(axis=\"x\", labelsize=16)\n\n# Adjust spacing between subplots\nplt.tight_layout()\nplt.subplots_adjust(hspace=0.15)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}