|
| 1 | +""" pyplots.ai |
| 2 | +timeseries-decomposition: Time Series Decomposition Plot |
| 3 | +Library: plotly 6.5.0 | 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 | +import plotly.graph_objects as go |
| 10 | +from plotly.subplots import make_subplots |
| 11 | +from statsmodels.tsa.seasonal import seasonal_decompose |
| 12 | + |
| 13 | + |
| 14 | +# Data: Monthly airline passengers (classic time series with trend and seasonality) |
| 15 | +np.random.seed(42) |
| 16 | + |
| 17 | +# Create 10 years of monthly data (120 points) |
| 18 | +dates = pd.date_range(start="2014-01-01", periods=120, freq="MS") |
| 19 | + |
| 20 | +# Generate realistic airline passenger data with trend, seasonality, and noise |
| 21 | +trend = np.linspace(100, 250, 120) # Growing trend |
| 22 | +seasonal = 30 * np.sin(2 * np.pi * np.arange(120) / 12) # Annual cycle (peak in summer) |
| 23 | +noise = np.random.normal(0, 10, 120) |
| 24 | +passengers = trend + seasonal + noise |
| 25 | + |
| 26 | +# Create time series and decompose |
| 27 | +ts = pd.Series(passengers, index=dates) |
| 28 | +decomposition = seasonal_decompose(ts, model="additive", period=12) |
| 29 | + |
| 30 | +# Create subplots (4 rows, shared x-axis) |
| 31 | +fig = make_subplots( |
| 32 | + rows=4, |
| 33 | + cols=1, |
| 34 | + shared_xaxes=True, |
| 35 | + vertical_spacing=0.08, |
| 36 | + subplot_titles=("Original", "Trend", "Seasonal", "Residual"), |
| 37 | +) |
| 38 | + |
| 39 | +# Color scheme |
| 40 | +primary_color = "#306998" # Python Blue |
| 41 | +secondary_color = "#FFD43B" # Python Yellow |
| 42 | + |
| 43 | +# Original series |
| 44 | +fig.add_trace( |
| 45 | + go.Scatter(x=dates, y=ts.values, mode="lines", line=dict(color=primary_color, width=2.5), name="Original"), |
| 46 | + row=1, |
| 47 | + col=1, |
| 48 | +) |
| 49 | + |
| 50 | +# Trend component |
| 51 | +fig.add_trace( |
| 52 | + go.Scatter( |
| 53 | + x=dates, |
| 54 | + y=decomposition.trend, |
| 55 | + mode="lines", |
| 56 | + line=dict(color="#2E8B57", width=3), # Sea green for trend |
| 57 | + name="Trend", |
| 58 | + ), |
| 59 | + row=2, |
| 60 | + col=1, |
| 61 | +) |
| 62 | + |
| 63 | +# Seasonal component |
| 64 | +fig.add_trace( |
| 65 | + go.Scatter( |
| 66 | + x=dates, |
| 67 | + y=decomposition.seasonal, |
| 68 | + mode="lines", |
| 69 | + line=dict(color="#E07020", width=2.5), # Orange for seasonal |
| 70 | + name="Seasonal", |
| 71 | + ), |
| 72 | + row=3, |
| 73 | + col=1, |
| 74 | +) |
| 75 | + |
| 76 | +# Residual component |
| 77 | +fig.add_trace( |
| 78 | + go.Scatter( |
| 79 | + x=dates, |
| 80 | + y=decomposition.resid, |
| 81 | + mode="lines", |
| 82 | + line=dict(color="#8B4789", width=2), # Purple for residual |
| 83 | + name="Residual", |
| 84 | + ), |
| 85 | + row=4, |
| 86 | + col=1, |
| 87 | +) |
| 88 | + |
| 89 | +# Update layout for large canvas |
| 90 | +fig.update_layout( |
| 91 | + title=dict(text="timeseries-decomposition · plotly · pyplots.ai", font=dict(size=32), x=0.5, xanchor="center"), |
| 92 | + template="plotly_white", |
| 93 | + showlegend=False, |
| 94 | + height=900, |
| 95 | + width=1600, |
| 96 | + margin=dict(l=100, r=60, t=100, b=80), |
| 97 | +) |
| 98 | + |
| 99 | +# Update all y-axes |
| 100 | +y_axis_titles = ["Passengers (thousands)", "Trend", "Seasonal Component", "Residual"] |
| 101 | +for i, title in enumerate(y_axis_titles, 1): |
| 102 | + fig.update_yaxes( |
| 103 | + title=dict(text=title, font=dict(size=20)), |
| 104 | + tickfont=dict(size=16), |
| 105 | + gridcolor="rgba(128, 128, 128, 0.2)", |
| 106 | + gridwidth=1, |
| 107 | + row=i, |
| 108 | + col=1, |
| 109 | + ) |
| 110 | + |
| 111 | +# Update x-axes |
| 112 | +for i in range(1, 5): |
| 113 | + fig.update_xaxes(tickfont=dict(size=16), gridcolor="rgba(128, 128, 128, 0.2)", gridwidth=1, row=i, col=1) |
| 114 | + |
| 115 | +# Bottom x-axis label |
| 116 | +fig.update_xaxes(title=dict(text="Date", font=dict(size=20)), row=4, col=1) |
| 117 | + |
| 118 | +# Update subplot titles font size |
| 119 | +for annotation in fig.layout.annotations: |
| 120 | + annotation.font.size = 22 |
| 121 | + |
| 122 | +# Save as PNG (4800 x 2700 px) |
| 123 | +fig.write_image("plot.png", width=1600, height=900, scale=3) |
| 124 | + |
| 125 | +# Save interactive HTML version |
| 126 | +fig.write_html("plot.html", include_plotlyjs=True, full_html=True) |
0 commit comments