{"spec_id":"timeseries-decomposition","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ntimeseries-decomposition: Time Series Decomposition Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    facet_wrap,\n    geom_line,\n    ggplot,\n    labs,\n    scale_size_manual,\n    scale_x_datetime,\n    theme,\n    theme_minimal,\n)\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\"\nBRAND = \"#009E73\"\n\n# Data - Monthly airline passengers with trend and seasonality\nnp.random.seed(42)\nn_months = 144  # 12 years of monthly data\n\n# Create date range\ndates = pd.date_range(start=\"2012-01-01\", periods=n_months, freq=\"MS\")\n\n# Generate synthetic airline passenger data with:\n# - Upward trend\n# - Strong yearly seasonality (peak in summer)\n# - Random noise\nt = np.arange(n_months)\ntrend = 200 + t * 2.5  # Growing trend\nseasonal = 40 * np.sin(2 * np.pi * t / 12 - np.pi / 2)  # Peak in summer (month 7)\nresidual = np.random.normal(0, 15, n_months)\nvalue = trend + seasonal + residual\n\n# Create DataFrame\ndf = pd.DataFrame({\"date\": dates, \"value\": value})\n\n# Perform seasonal decomposition using statsmodels\ndecomposition = seasonal_decompose(df[\"value\"], model=\"additive\", period=12)\n\n# Prepare data for plotnine with all components\ndf_plot = pd.DataFrame(\n    {\n        \"date\": np.tile(dates, 4),\n        \"value\": np.concatenate(\n            [df[\"value\"].values, decomposition.trend.values, decomposition.seasonal.values, decomposition.resid.values]\n        ),\n        \"component\": np.repeat([\"Original\", \"Trend\", \"Seasonal\", \"Residual\"], n_months),\n    }\n)\n\n# Remove NaN values (decomposition creates NaNs at edges)\ndf_plot = df_plot.dropna()\n\n# Make component a categorical with correct order\ndf_plot[\"component\"] = pd.Categorical(\n    df_plot[\"component\"], categories=[\"Original\", \"Trend\", \"Seasonal\", \"Residual\"], ordered=True\n)\n\n# Create faceted plot with four components\n# Emphasize original series with thicker line\nplot = (\n    ggplot(df_plot, aes(x=\"date\", y=\"value\", size=\"component\"))\n    + geom_line(color=BRAND)\n    + facet_wrap(\"~component\", ncol=1, scales=\"free_y\", dir=\"v\")\n    + scale_x_datetime(date_labels=\"%Y\", date_breaks=\"2 years\")\n    + scale_size_manual(values={\"Original\": 1.8, \"Trend\": 1.2, \"Seasonal\": 1.2, \"Residual\": 1.2}, guide=None)\n    + labs(title=\"timeseries-decomposition · plotnine · anyplot.ai\", x=\"Date\", y=\"Passengers\")\n    + theme_minimal()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_title=element_text(size=24, ha=\"center\", weight=\"bold\", color=INK),\n        axis_title_x=element_text(size=20, margin={\"t\": 15}, color=INK),\n        axis_title_y=element_text(size=20, margin={\"r\": 15}, color=INK),\n        axis_text_x=element_text(size=14, color=INK_SOFT),\n        axis_text_y=element_text(size=12, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT, size=0.5),\n        strip_text=element_text(size=16, weight=\"bold\", color=INK),\n        strip_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        panel_spacing_y=0.06,\n        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_grid_minor=element_blank(),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, width=16, height=9)\n"}