|
| 1 | +""" pyplots.ai |
| 2 | +timeseries-decomposition: Time Series Decomposition Plot |
| 3 | +Library: matplotlib 3.10.8 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import matplotlib.pyplot as plt |
| 8 | +import numpy as np |
| 9 | +import pandas as pd |
| 10 | +from statsmodels.tsa.seasonal import seasonal_decompose |
| 11 | + |
| 12 | + |
| 13 | +# Data - Monthly retail sales over 6 years (72 months = 6 full annual cycles) |
| 14 | +np.random.seed(42) |
| 15 | +n_months = 72 |
| 16 | +dates = pd.date_range(start="2018-01-01", periods=n_months, freq="MS") |
| 17 | + |
| 18 | +# Create realistic retail sales data with trend, seasonality, and noise |
| 19 | +trend = np.linspace(100, 180, n_months) + np.cumsum(np.random.randn(n_months) * 0.5) |
| 20 | +seasonal = 25 * np.sin(2 * np.pi * np.arange(n_months) / 12) # Annual cycle |
| 21 | +# Add holiday bump in December (month 12) |
| 22 | +holiday_bump = np.array([15 if (i + 1) % 12 == 0 else 0 for i in range(n_months)]) |
| 23 | +seasonal = seasonal + holiday_bump |
| 24 | +residual = np.random.randn(n_months) * 8 |
| 25 | +values = trend + seasonal + residual |
| 26 | + |
| 27 | +# Create time series |
| 28 | +ts = pd.Series(values, index=dates) |
| 29 | + |
| 30 | +# Perform seasonal decomposition (additive model) |
| 31 | +decomposition = seasonal_decompose(ts, model="additive", period=12) |
| 32 | + |
| 33 | +# Create plot with 4 subplots |
| 34 | +fig, axes = plt.subplots(4, 1, figsize=(16, 12), sharex=True) |
| 35 | + |
| 36 | +# Color palette |
| 37 | +color_original = "#306998" |
| 38 | +color_trend = "#FFD43B" |
| 39 | +color_seasonal = "#4B8BBE" |
| 40 | +color_residual = "#FFE873" |
| 41 | + |
| 42 | +# Original series |
| 43 | +axes[0].plot(dates, ts.values, color=color_original, linewidth=2.5) |
| 44 | +axes[0].set_ylabel("Original", fontsize=18) |
| 45 | +axes[0].tick_params(axis="y", labelsize=14) |
| 46 | +axes[0].grid(True, alpha=0.3, linestyle="--") |
| 47 | +axes[0].set_title("timeseries-decomposition · matplotlib · pyplots.ai", fontsize=24, pad=15) |
| 48 | + |
| 49 | +# Trend component |
| 50 | +axes[1].plot(dates, decomposition.trend, color=color_trend, linewidth=2.5) |
| 51 | +axes[1].set_ylabel("Trend", fontsize=18) |
| 52 | +axes[1].tick_params(axis="y", labelsize=14) |
| 53 | +axes[1].grid(True, alpha=0.3, linestyle="--") |
| 54 | + |
| 55 | +# Seasonal component |
| 56 | +axes[2].plot(dates, decomposition.seasonal, color=color_seasonal, linewidth=2.5) |
| 57 | +axes[2].set_ylabel("Seasonal", fontsize=18) |
| 58 | +axes[2].tick_params(axis="y", labelsize=14) |
| 59 | +axes[2].grid(True, alpha=0.3, linestyle="--") |
| 60 | + |
| 61 | +# Residual component |
| 62 | +axes[3].plot(dates, decomposition.resid, color=color_residual, linewidth=2.5, alpha=0.8) |
| 63 | +axes[3].axhline(y=0, color="#666666", linestyle="-", linewidth=1, alpha=0.5) |
| 64 | +axes[3].set_ylabel("Residual", fontsize=18) |
| 65 | +axes[3].set_xlabel("Date", fontsize=20) |
| 66 | +axes[3].tick_params(axis="both", labelsize=14) |
| 67 | +axes[3].grid(True, alpha=0.3, linestyle="--") |
| 68 | + |
| 69 | +# Adjust x-axis tick formatting |
| 70 | +plt.gcf().autofmt_xdate() |
| 71 | + |
| 72 | +# Adjust spacing between subplots |
| 73 | +plt.tight_layout() |
| 74 | +plt.subplots_adjust(hspace=0.15) |
| 75 | + |
| 76 | +plt.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments