Skip to content

Commit ee5cc09

Browse files
feat(altair): implement timeseries-decomposition (#3018)
## Implementation: `timeseries-decomposition` - altair Implements the **altair** version of `timeseries-decomposition`. **File:** `plots/timeseries-decomposition/implementations/altair.py` **Parent Issue:** #2992 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617482042)* --------- 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 05e116f commit ee5cc09

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
""" pyplots.ai
2+
timeseries-decomposition: Time Series Decomposition Plot
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
from statsmodels.tsa.seasonal import seasonal_decompose
11+
12+
13+
# Data - Monthly airline passengers (classic time series example)
14+
np.random.seed(42)
15+
dates = pd.date_range("2018-01-01", periods=96, freq="MS") # 8 years monthly data
16+
# Create realistic airline passenger data with trend + seasonality
17+
trend = np.linspace(100, 180, 96) # Increasing trend
18+
seasonal = 30 * np.sin(2 * np.pi * np.arange(96) / 12) # Annual cycle
19+
noise = np.random.normal(0, 8, 96)
20+
values = trend + seasonal + noise
21+
22+
df = pd.DataFrame({"date": dates, "value": values})
23+
24+
# Decompose the time series
25+
series = pd.Series(values, index=dates)
26+
decomposition = seasonal_decompose(series, model="additive", period=12)
27+
28+
# Create dataframe with all components
29+
df_decomp = pd.DataFrame(
30+
{
31+
"date": dates,
32+
"Original": values,
33+
"Trend": decomposition.trend,
34+
"Seasonal": decomposition.seasonal,
35+
"Residual": decomposition.resid,
36+
}
37+
)
38+
39+
# Melt for faceted plotting
40+
df_long = df_decomp.melt(id_vars=["date"], var_name="component", value_name="value")
41+
42+
# Define component order
43+
component_order = ["Original", "Trend", "Seasonal", "Residual"]
44+
45+
# Color mapping for each component
46+
color_map = {
47+
"Original": "#306998", # Python Blue
48+
"Trend": "#E63946", # Red
49+
"Seasonal": "#2A9D8F", # Teal
50+
"Residual": "#9B59B6", # Purple
51+
}
52+
53+
# Create the chart
54+
chart = (
55+
alt.Chart(df_long)
56+
.mark_line(strokeWidth=2.5)
57+
.encode(
58+
x=alt.X(
59+
"date:T", title="Date", axis=alt.Axis(labelFontSize=16, titleFontSize=20, labelAngle=-45, tickCount=12)
60+
),
61+
y=alt.Y("value:Q", title="Value", axis=alt.Axis(labelFontSize=16, titleFontSize=20)),
62+
color=alt.Color(
63+
"component:N", scale=alt.Scale(domain=component_order, range=list(color_map.values())), legend=None
64+
),
65+
)
66+
.properties(width=1500, height=180)
67+
.facet(
68+
row=alt.Row(
69+
"component:N",
70+
sort=component_order,
71+
title=None,
72+
header=alt.Header(
73+
labelFontSize=22, labelFontWeight="bold", labelOrient="left", labelAlign="left", labelPadding=10
74+
),
75+
)
76+
)
77+
.properties(title=alt.Title("timeseries-decomposition · altair · pyplots.ai", fontSize=28, anchor="middle", dy=-10))
78+
.configure_view(strokeWidth=0)
79+
.configure_facet(spacing=25)
80+
.resolve_scale(y="independent")
81+
)
82+
83+
# Save as PNG (1600 × 900 base → 4800 × 2700 with scale_factor=3)
84+
chart.save("plot.png", scale_factor=3.0)
85+
86+
# Save as HTML for interactivity
87+
chart.save("plot.html")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: altair
2+
specification_id: timeseries-decomposition
3+
created: '2025-12-31T10:55:23Z'
4+
updated: '2025-12-31T11:05:44Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617482042
7+
issue: 2992
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/timeseries-decomposition/altair/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent use of Altair declarative faceting to create vertically stacked subplots
17+
with independent y-scales
18+
- Clean professional color scheme with distinct colorblind-accessible colors for
19+
each component
20+
- Proper use of statsmodels seasonal_decompose for authentic decomposition
21+
- Component labels prominently displayed using facet headers with good font styling
22+
- Appropriate data coverage with 96 monthly points over 8 years for meaningful decomposition
23+
- Code follows KISS principles with clear linear flow
24+
weaknesses:
25+
- Missing light grid lines that specification explicitly requests
26+
- Y-axis labels all say generic Value rather than being component-specific
27+
- Could leverage Altair interactivity features like .interactive() or tooltips

0 commit comments

Comments
 (0)