{"spec_id":"timeseries-decomposition","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ntimeseries-decomposition: Time Series Decomposition Plot\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport shutil\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    gggrid,\n    ggplot,\n    ggsave,\n    ggsize,\n    ggtitle,\n    labs,\n    theme,\n)\nfrom statsmodels.tsa.seasonal import seasonal_decompose\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito palette for components\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Monthly temperature readings over 5 years (60 months)\nnp.random.seed(42)\nn_months = 60\ndates = pd.date_range(\"2019-01-01\", periods=n_months, freq=\"MS\")\n\n# Create realistic temperature data with trend, seasonality, and noise\ntrend = np.linspace(15, 18, n_months)  # Gradual warming trend\nseasonal = 12 * np.sin(2 * np.pi * np.arange(n_months) / 12)  # Annual cycle\nnoise = np.random.normal(0, 1.5, n_months)\nvalues = trend + seasonal + noise\n\n# Create DataFrame for decomposition\ndf_ts = pd.DataFrame({\"date\": dates, \"value\": values})\ndf_ts = df_ts.set_index(\"date\")\n\n# Perform seasonal decomposition (additive model)\ndecomposition = seasonal_decompose(df_ts[\"value\"], model=\"additive\", period=12)\n\n# Extract components and create plotting DataFrames\ndf_original = pd.DataFrame({\"date\": dates, \"value\": values, \"component\": \"Original\"})\ndf_trend = pd.DataFrame({\"date\": dates, \"value\": decomposition.trend, \"component\": \"Trend\"})\ndf_seasonal = pd.DataFrame({\"date\": dates, \"value\": decomposition.seasonal, \"component\": \"Seasonal\"})\ndf_residual = pd.DataFrame({\"date\": dates, \"value\": decomposition.resid, \"component\": \"Residual\"})\n\n# Create individual plots for each component\ncomponent_colors = {\"Original\": IMPRINT[0], \"Trend\": IMPRINT[1], \"Seasonal\": IMPRINT[2], \"Residual\": IMPRINT[3]}\n\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK_SOFT, size=0.5),\n    axis_title=element_text(color=INK, size=16),\n    axis_text=element_text(color=INK_SOFT, size=14),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=20, face=\"bold\"),\n)\n\n# Plot 1: Original Series\np1 = (\n    ggplot(df_original, aes(x=\"date\", y=\"value\"))\n    + geom_line(color=component_colors[\"Original\"], size=1.2)\n    + labs(x=\"\", y=\"Temperature (°C)\", title=\"Original Series\")\n    + anyplot_theme\n    + theme(axis_text_x=element_blank())\n    + ggsize(1600, 200)\n)\n\n# Plot 2: Trend Component\np2 = (\n    ggplot(df_trend.dropna(), aes(x=\"date\", y=\"value\"))\n    + geom_line(color=component_colors[\"Trend\"], size=1.2)\n    + labs(x=\"\", y=\"Temperature (°C)\", title=\"Trend\")\n    + anyplot_theme\n    + theme(axis_text_x=element_blank())\n    + ggsize(1600, 200)\n)\n\n# Plot 3: Seasonal Component\np3 = (\n    ggplot(df_seasonal, aes(x=\"date\", y=\"value\"))\n    + geom_line(color=component_colors[\"Seasonal\"], size=1.2)\n    + labs(x=\"\", y=\"Temperature (°C)\", title=\"Seasonal\")\n    + anyplot_theme\n    + theme(axis_text_x=element_blank())\n    + ggsize(1600, 200)\n)\n\n# Plot 4: Residual Component\np4 = (\n    ggplot(df_residual.dropna(), aes(x=\"date\", y=\"value\"))\n    + geom_line(color=component_colors[\"Residual\"], size=1.2)\n    + labs(x=\"Date\", y=\"Temperature (°C)\", title=\"Residual\")\n    + anyplot_theme\n    + theme(axis_text_x=element_text(angle=45))\n    + ggsize(1600, 200)\n)\n\n# Create combined plot using gggrid\ncombined = gggrid([p1, p2, p3, p4], ncol=1)\n\n# Add overall title\nfinal_plot = (\n    combined\n    + ggsize(1600, 900)\n    + ggtitle(\"timeseries-decomposition · letsplot · anyplot.ai\")\n    + theme(plot_title=element_text(color=INK, size=24, face=\"bold\"))\n)\n\n# Save as PNG with scale for 4800x2700 resolution\nggsave(final_plot, f\"plot-{THEME}.png\", scale=3)\n\n# Save HTML for interactive version\nggsave(final_plot, f\"plot-{THEME}.html\")\n\n# Move files from lets-plot subdirectory to current directory if needed\nlp_dir = \"lets-plot-images\"\nif os.path.exists(lp_dir):\n    for fname in [f\"plot-{THEME}.png\", f\"plot-{THEME}.html\"]:\n        src = os.path.join(lp_dir, fname)\n        if os.path.exists(src):\n            shutil.move(src, fname)\n    if not os.listdir(lp_dir):\n        os.rmdir(lp_dir)\n"}