Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 47 additions & 55 deletions plots/timeseries-decomposition/implementations/python/letsplot.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""" pyplots.ai
""" anyplot.ai
timeseries-decomposition: Time Series Decomposition Plot
Library: letsplot 4.8.2 | Python 3.13.11
Quality: 91/100 | Created: 2025-12-31
Library: letsplot 4.9.0 | Python 3.13.13
Quality: 88/100 | Updated: 2026-05-14
"""

import os
Expand All @@ -13,6 +13,8 @@
LetsPlot,
aes,
element_blank,
element_line,
element_rect,
element_text,
geom_line,
gggrid,
Expand All @@ -22,13 +24,21 @@
ggtitle,
labs,
theme,
theme_minimal,
)
from statsmodels.tsa.seasonal import seasonal_decompose


LetsPlot.setup_html()

# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Okabe-Ito palette for components
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7"]

# Data: Monthly temperature readings over 5 years (60 months)
np.random.seed(42)
n_months = 60
Expand All @@ -49,79 +59,60 @@

# Extract components and create plotting DataFrames
df_original = pd.DataFrame({"date": dates, "value": values, "component": "Original"})

df_trend = pd.DataFrame({"date": dates, "value": decomposition.trend, "component": "Trend"})

df_seasonal = pd.DataFrame({"date": dates, "value": decomposition.seasonal, "component": "Seasonal"})

df_residual = pd.DataFrame({"date": dates, "value": decomposition.resid, "component": "Residual"})

# Combine all components
df_all = pd.concat([df_original, df_trend, df_seasonal, df_residual])

# Convert date to string for plotting
df_all["date_str"] = df_all["date"].dt.strftime("%Y-%m")

# Create individual plots for each component
colors = {"Original": "#306998", "Trend": "#DC2626", "Seasonal": "#059669", "Residual": "#7C3AED"}
component_colors = {"Original": OKABE_ITO[0], "Trend": OKABE_ITO[1], "Seasonal": OKABE_ITO[2], "Residual": OKABE_ITO[3]}

anyplot_theme = theme(
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
panel_background=element_rect(fill=PAGE_BG),
panel_grid_major=element_line(color=INK_SOFT, size=0.5),
axis_title=element_text(color=INK, size=16),
axis_text=element_text(color=INK_SOFT, size=14),
axis_line=element_line(color=INK_SOFT),
plot_title=element_text(color=INK, size=20, face="bold"),
)

# Plot 1: Original Series
p1 = (
ggplot(df_original, aes(x="date", y="value"))
+ geom_line(color="#306998", size=1.2)
+ geom_line(color=component_colors["Original"], size=1.2)
+ labs(x="", y="Temperature (°C)", title="Original Series")
+ theme_minimal()
+ theme(
plot_title=element_text(size=20, face="bold"),
axis_title=element_text(size=16),
axis_text=element_text(size=14),
axis_text_x=element_blank(),
)
+ anyplot_theme
+ theme(axis_text_x=element_blank())
+ ggsize(1600, 200)
)

# Plot 2: Trend Component
p2 = (
ggplot(df_trend.dropna(), aes(x="date", y="value"))
+ geom_line(color="#DC2626", size=1.2)
+ geom_line(color=component_colors["Trend"], size=1.2)
+ labs(x="", y="Temperature (°C)", title="Trend")
+ theme_minimal()
+ theme(
plot_title=element_text(size=20, face="bold"),
axis_title=element_text(size=16),
axis_text=element_text(size=14),
axis_text_x=element_blank(),
)
+ anyplot_theme
+ theme(axis_text_x=element_blank())
+ ggsize(1600, 200)
)

# Plot 3: Seasonal Component
p3 = (
ggplot(df_seasonal, aes(x="date", y="value"))
+ geom_line(color="#059669", size=1.2)
+ geom_line(color=component_colors["Seasonal"], size=1.2)
+ labs(x="", y="Temperature (°C)", title="Seasonal")
+ theme_minimal()
+ theme(
plot_title=element_text(size=20, face="bold"),
axis_title=element_text(size=16),
axis_text=element_text(size=14),
axis_text_x=element_blank(),
)
+ anyplot_theme
+ theme(axis_text_x=element_blank())
+ ggsize(1600, 200)
)

# Plot 4: Residual Component
p4 = (
ggplot(df_residual.dropna(), aes(x="date", y="value"))
+ geom_line(color="#7C3AED", size=1.2)
+ geom_line(color=component_colors["Residual"], size=1.2)
+ labs(x="Date", y="Temperature (°C)", title="Residual")
+ theme_minimal()
+ theme(
plot_title=element_text(size=20, face="bold"),
axis_title=element_text(size=16),
axis_text=element_text(size=14),
axis_text_x=element_text(angle=45),
)
+ anyplot_theme
+ theme(axis_text_x=element_text(angle=45))
+ ggsize(1600, 200)
)

Expand All @@ -132,21 +123,22 @@
final_plot = (
combined
+ ggsize(1600, 900)
+ ggtitle("timeseries-decomposition · letsplot · pyplots.ai")
+ theme(plot_title=element_text(size=24, face="bold"))
+ ggtitle("timeseries-decomposition · letsplot · anyplot.ai")
+ theme(plot_title=element_text(color=INK, size=24, face="bold"))
)

# Save as PNG with scale for 4800x2700 resolution
ggsave(final_plot, "plot.png", scale=3)
ggsave(final_plot, f"plot-{THEME}.png", scale=3)

# Save HTML for interactive version
ggsave(final_plot, "plot.html")
ggsave(final_plot, f"plot-{THEME}.html")

# Move files from lets-plot subdirectory to current directory
# Move files from lets-plot subdirectory to current directory if needed
lp_dir = "lets-plot-images"
if os.path.exists(lp_dir):
for f in ["plot.png", "plot.html"]:
src = os.path.join(lp_dir, f)
for fname in [f"plot-{THEME}.png", f"plot-{THEME}.html"]:
src = os.path.join(lp_dir, fname)
if os.path.exists(src):
shutil.move(src, f)
os.rmdir(lp_dir)
shutil.move(src, fname)
if not os.listdir(lp_dir):
os.rmdir(lp_dir)
Loading
Loading