Skip to content

Commit 532a7f9

Browse files
feat(matplotlib): implement timeseries-decomposition (#3023)
## Implementation: `timeseries-decomposition` - matplotlib Implements the **matplotlib** version of `timeseries-decomposition`. **File:** `plots/timeseries-decomposition/implementations/matplotlib.py` **Parent Issue:** #2992 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617479398)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent ee5cc09 commit 532a7f9

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: matplotlib
2+
specification_id: timeseries-decomposition
3+
created: '2025-12-31T10:56:01Z'
4+
updated: '2025-12-31T11:06:08Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617479398
7+
issue: 2992
8+
python_version: 3.13.11
9+
library_version: 3.10.8
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/matplotlib/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/matplotlib/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent four-panel layout with shared x-axis showing all decomposition components
17+
clearly
18+
- Realistic retail sales scenario with trend, annual seasonality, and December holiday
19+
bumps
20+
- Clean code structure using statsmodels seasonal_decompose for proper decomposition
21+
- Appropriate line widths and grid styling for readability
22+
weaknesses:
23+
- Y-axis labels lack units (e.g., "Original (Sales USD)" would be more informative)
24+
- Uses two similar yellow shades for Trend and Residual which could be slightly
25+
confusing
26+
- Tick labels at 14pt are slightly below the 16pt guideline
27+
- Could leverage more matplotlib features like annotations or fill_between for visual
28+
enhancement

0 commit comments

Comments
 (0)