Skip to content

Commit 2771c34

Browse files
feat(plotnine): implement timeseries-decomposition (#3033)
## Implementation: `timeseries-decomposition` - plotnine Implements the **plotnine** version of `timeseries-decomposition`. **File:** `plots/timeseries-decomposition/implementations/plotnine.py` **Parent Issue:** #2992 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617482775)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 896cd05 commit 2771c34

2 files changed

Lines changed: 121 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
""" pyplots.ai
2+
timeseries-decomposition: Time Series Decomposition Plot
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from plotnine import (
10+
aes,
11+
element_blank,
12+
element_line,
13+
element_rect,
14+
element_text,
15+
facet_wrap,
16+
geom_line,
17+
ggplot,
18+
labs,
19+
scale_x_datetime,
20+
theme,
21+
theme_minimal,
22+
)
23+
from statsmodels.tsa.seasonal import seasonal_decompose
24+
25+
26+
# Data - Monthly airline passengers with trend and seasonality
27+
np.random.seed(42)
28+
n_months = 144 # 12 years of monthly data
29+
30+
# Create date range
31+
dates = pd.date_range(start="2012-01-01", periods=n_months, freq="MS")
32+
33+
# Generate synthetic airline passenger data with:
34+
# - Upward trend
35+
# - Strong yearly seasonality (peak in summer)
36+
# - Random noise
37+
t = np.arange(n_months)
38+
trend = 200 + t * 2.5 # Growing trend
39+
seasonal = 40 * np.sin(2 * np.pi * t / 12 - np.pi / 2) # Peak in summer (month 7)
40+
residual = np.random.normal(0, 15, n_months)
41+
value = trend + seasonal + residual
42+
43+
# Create DataFrame
44+
df = pd.DataFrame({"date": dates, "value": value})
45+
46+
# Perform seasonal decomposition using statsmodels
47+
decomposition = seasonal_decompose(df["value"], model="additive", period=12)
48+
49+
# Prepare data for plotnine with all components
50+
df_plot = pd.DataFrame(
51+
{
52+
"date": np.tile(dates, 4),
53+
"value": np.concatenate(
54+
[df["value"].values, decomposition.trend.values, decomposition.seasonal.values, decomposition.resid.values]
55+
),
56+
"component": np.repeat(["Original", "Trend", "Seasonal", "Residual"], n_months),
57+
}
58+
)
59+
60+
# Remove NaN values (decomposition creates NaNs at edges)
61+
df_plot = df_plot.dropna()
62+
63+
# Make component a categorical with correct order
64+
df_plot["component"] = pd.Categorical(
65+
df_plot["component"], categories=["Original", "Trend", "Seasonal", "Residual"], ordered=True
66+
)
67+
68+
# Create faceted plot with four components
69+
plot = (
70+
ggplot(df_plot, aes(x="date", y="value"))
71+
+ geom_line(color="#306998", size=1.2)
72+
+ facet_wrap("~component", ncol=1, scales="free_y", dir="v")
73+
+ scale_x_datetime(date_labels="%Y", date_breaks="2 years")
74+
+ labs(title="timeseries-decomposition · plotnine · pyplots.ai", x="Date", y="Value")
75+
+ theme_minimal()
76+
+ theme(
77+
figure_size=(16, 9),
78+
plot_title=element_text(size=24, ha="center", weight="bold"),
79+
axis_title_x=element_text(size=20, margin={"t": 15}),
80+
axis_title_y=element_text(size=20, margin={"r": 15}),
81+
axis_text_x=element_text(size=14),
82+
axis_text_y=element_text(size=12),
83+
strip_text=element_text(size=16, weight="bold"),
84+
strip_background=element_rect(fill="#e8e8e8", color=None),
85+
panel_spacing_y=0.08,
86+
panel_grid_major=element_line(color="#dddddd", size=0.5, alpha=0.5),
87+
panel_grid_minor=element_blank(),
88+
)
89+
)
90+
91+
# Save
92+
plot.save("plot.png", dpi=300, width=16, height=9)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library: plotnine
2+
specification_id: timeseries-decomposition
3+
created: '2025-12-31T10:56:56Z'
4+
updated: '2025-12-31T11:15:24Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617482775
7+
issue: 2992
8+
python_version: 3.13.11
9+
library_version: 0.15.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent use of plotnine facet_wrap for creating the four-panel decomposition
17+
layout
18+
- Clean implementation using statsmodels seasonal_decompose for proper time series
19+
analysis
20+
- Good color choice (#306998) that is accessible and professional
21+
- Proper handling of NaN values from decomposition edges
22+
- Correct categorical ordering of components using pd.Categorical
23+
- Title follows the exact required format
24+
- 12 years of monthly data provides meaningful decomposition
25+
weaknesses:
26+
- Y-axis tick labels in the Original panel appear slightly crowded with multiple
27+
values stacked
28+
- Panel spacing could be reduced to make better use of vertical canvas space
29+
- Grid alpha at 0.5 could be more subtle (0.3 would be better)

0 commit comments

Comments
 (0)