Skill

Decompose Time Series Data

An expert skill for decomposing time series into trend, seasonality, and residual components using classical, ML, and signal-processing methods.

Works with pandasnumpystatsmodelsmatplotlibscipy

Maintainer of this project? Claim this page to edit the listing.


79
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Decompose complex time series data into its fundamental components: trend, seasonality, and residuals. This asset provides robust methods for understanding temporal patterns and improving forecasting accuracy.

Outcomes

What it gets done

01

Apply classical decomposition methods like Moving Average and STL.

02

Utilize advanced techniques including X-13ARIMA-SEATS, EMD, and Wavelet Decomposition.

03

Assess decomposition quality using diagnostic metrics and visualizations.

04

Generate Python code for various decomposition algorithms.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-time-series-decomposition | bash

Overview

Time Series Decomposition Expert

Decomposes time series into trend, seasonality, and residual components using classical STL, X-13ARIMA-SEATS, empirical mode decomposition, or wavelet methods. Use it when forecasting or analyzing seasonal data; choose the method based on the series' linearity, stationarity, and noise level.

What it does

This skill acts as an expert in time series decomposition, breaking temporal data into interpretable components - trend, seasonality, cyclical variation, and residual/noise - using classical statistical methods, modern signal processing, and machine learning approaches. It works with both additive (Y(t) = Trend + Seasonal + Residual) and multiplicative (Y(t) = Trend x Seasonal x Residual) models, choosing the right one for the data's structure.

For classical decomposition it applies statsmodels' seasonal_decompose and STL (Seasonal and Trend decomposition using Loess), the latter with tunable seasonal/trend smoother lengths and robustness to outliers:

from statsmodels.tsa.seasonal import STL

def stl_decompose(ts, seasonal=7, trend=None, robust=True):
    """
    STL decomposition with flexible parameters
    """
    stl = STL(
        ts,
        seasonal=seasonal,  # Length of seasonal smoother
        trend=trend,        # Length of trend smoother
        robust=robust       # Robust to outliers
    )
    
    result = stl.fit()
    
    return {
        'original': result.observed,
        'trend': result.trend,
        'seasonal': result.seasonal,
        'residual': result.resid
    }

For advanced cases it applies X-13ARIMA-SEATS for economic/business data needing trading-day adjustment and automatic outlier detection, Empirical Mode Decomposition (EMD/EEMD via PyEMD) for non-linear, non-stationary series, and wavelet decomposition (pywt) for multi-resolution and time-frequency analysis. Decomposition quality is assessed with reconstruction MSE/MAE, a Ljung-Box test for residual white noise, a Shapiro normality test, and explicit seasonal-strength and trend-strength scores - plus a standard four-panel plot (original, trend, seasonal, residual) for visual validation.

When to use - and when NOT to

Use Classical/STL decomposition for regular, stable seasonality with minimal outliers; X-13ARIMA-SEATS for economic or business data requiring trading-day adjustments; EMD/EEMD for non-linear, non-stationary data with complex patterns; and wavelet decomposition for multi-scale analysis, especially financial or engineering data. When seasonal strength scores below 0.3, consider a trend-only model instead of forcing a seasonal component; for high-noise series, prefer robust methods like STL with robust=True or EEMD.

X-13ARIMA-SEATS requires the separate X-13ARIMA-SEATS software to be installed and falls back to STL automatically if it isn't available.

Inputs and outputs

Input is a time series (a pandas Series with a datetime index, or a raw numeric array). Output is a dictionary of components (trend, seasonal, residual/original, or IMFs and wavelet coefficients depending on method) plus diagnostic metrics for validating the decomposition's quality. Suggested parameter defaults: STL seasonal parameter as an odd integer, typically 7-15 for strong seasonality; db4 wavelet for general use, haar for sharp changes, coif2 for smooth data; EMD max_imf starting around data_length/10.

Who it's for

Data scientists and analysts working with temporal data - forecasting, anomaly detection, or seasonal adjustment - who need to separate trend, seasonality, and noise using the method best suited to their data's linearity, stationarity, and noise level.

Source README

Time Series Decomposition Expert

You are an expert in time series decomposition, specializing in breaking down temporal data into interpretable components (trend, seasonality, and residuals) using classical statistical methods, modern signal processing techniques, and machine learning approaches.

Core Decomposition Principles

Fundamental Components

  • Trend: Long-term directional movement in the data
  • Seasonality: Regular, predictable patterns that repeat over fixed periods
  • Cyclical: Irregular fluctuations without fixed periods (often combined with trend)
  • Residual/Noise: Random variation after removing systematic components

Decomposition Models

  • Additive Model: Y(t) = Trend(t) + Seasonal(t) + Residual(t)
  • Multiplicative Model: Y(t) = Trend(t) × Seasonal(t) × Residual(t)
  • Mixed Models: Combinations for complex patterns

Classical Decomposition Methods

Moving Average Decomposition

import pandas as pd
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt

### Classical decomposition
def classical_decompose(ts, model='additive', period=None):
    """
    Perform classical time series decomposition
    """
    decomposition = seasonal_decompose(
        ts, 
        model=model, 
        period=period,
        extrapolate_trend='freq'  # Handle NaN values at boundaries
    )
    
    return {
        'original': decomposition.observed,
        'trend': decomposition.trend,
        'seasonal': decomposition.seasonal,
        'residual': decomposition.resid
    }

### Example usage
ts = pd.read_csv('data.csv', index_col='date', parse_dates=True)['value']
result = classical_decompose(ts, model='additive', period=12)

STL Decomposition (Seasonal and Trend decomposition using Loess)

from statsmodels.tsa.seasonal import STL

def stl_decompose(ts, seasonal=7, trend=None, robust=True):
    """
    STL decomposition with flexible parameters
    """
    stl = STL(
        ts,
        seasonal=seasonal,  # Length of seasonal smoother
        trend=trend,        # Length of trend smoother
        robust=robust       # Robust to outliers
    )
    
    result = stl.fit()
    
    return {
        'original': result.observed,
        'trend': result.trend,
        'seasonal': result.seasonal,
        'residual': result.resid
    }

### Adaptive seasonal parameter
def adaptive_stl(ts, freq=None):
    if freq is None:
        freq = pd.infer_freq(ts.index)
    
    seasonal_length = {
        'D': 7,    # Daily: weekly seasonality
        'H': 24,   # Hourly: daily seasonality
        'M': 12,   # Monthly: yearly seasonality
        'Q': 4     # Quarterly: yearly seasonality
    }.get(freq, 7)
    
    return stl_decompose(ts, seasonal=seasonal_length)

Advanced Decomposition Techniques

X-13ARIMA-SEATS (Seasonal Adjustment)

from statsmodels.tsa.x13 import x13_arima_analysis

def x13_decompose(ts, trading=True, outlier=True):
    """
    X-13ARIMA-SEATS decomposition (requires X-13ARIMA-SEATS software)
    """
    try:
        result = x13_arima_analysis(
            ts,
            trading=trading,    # Trading day adjustment
            outlier=outlier,    # Automatic outlier detection
            forecast_years=2   # Extend series for better seasonal factors
        )
        
        return {
            'original': result.observed,
            'trend': result.trend,
            'seasonal': result.seasadj,  # Seasonally adjusted
            'irregular': result.irregular
        }
    except:
        print("X-13ARIMA-SEATS not available, falling back to STL")
        return stl_decompose(ts)

Empirical Mode Decomposition (EMD)

from PyEMD import EMD, EEMD

def emd_decompose(ts, max_imf=10):
    """
    Empirical Mode Decomposition for non-linear, non-stationary series
    """
    # Convert to numpy array
    values = ts.values if hasattr(ts, 'values') else np.array(ts)
    time = np.arange(len(values))
    
    # Standard EMD
    emd = EMD()
    emd.emd(values, max_imf=max_imf)
    imfs, residue = emd.get_imfs_and_residue()
    
    # Ensemble EMD for noise robustness
    eemd = EEMD()
    eemd.eemd(values, max_imf=max_imf)
    eimfs, eresidue = eemd.get_imfs_and_residue()
    
    return {
        'imfs': imfs,           # Intrinsic Mode Functions
        'residue': residue,     # Final residue (trend)
        'ensemble_imfs': eimfs, # Ensemble IMFs
        'ensemble_residue': eresidue
    }

Wavelet Decomposition

import pywt
from scipy import signal

def wavelet_decompose(ts, wavelet='db4', levels=6):
    """
    Wavelet decomposition for multi-resolution analysis
    """
    values = ts.values if hasattr(ts, 'values') else np.array(ts)
    
    # Discrete Wavelet Transform
    coeffs = pywt.wavedec(values, wavelet, level=levels)
    
    # Reconstruct components
    components = []
    for i in range(len(coeffs)):
        # Zero out all coefficients except current level
        temp_coeffs = [np.zeros_like(c) for c in coeffs]
        temp_coeffs[i] = coeffs[i]
        
        # Reconstruct
        component = pywt.waverec(temp_coeffs, wavelet)
        components.append(component[:len(values)])
    
    return {
        'approximation': components[0],  # Low frequency (trend)
        'details': components[1:],       # High frequency components
        'coefficients': coeffs
    }

def continuous_wavelet_transform(ts, wavelet='cmor', scales=None):
    """
    Continuous Wavelet Transform for time-frequency analysis
    """
    values = ts.values if hasattr(ts, 'values') else np.array(ts)
    
    if scales is None:
        scales = np.arange(1, 128)
    
    coefficients, frequencies = pywt.cwt(values, scales, wavelet)
    
    return {
        'coefficients': coefficients,
        'frequencies': frequencies,
        'scales': scales
    }

Decomposition Quality Assessment

Diagnostic Metrics

from scipy import stats
from statsmodels.stats.diagnostic import acorr_ljungbox

def assess_decomposition(original, trend, seasonal, residual):
    """
    Comprehensive decomposition quality assessment
    """
    # Reconstruction accuracy
    reconstructed = trend + seasonal
    mse = np.mean((original - reconstructed) ** 2)
    mae = np.mean(np.abs(original - reconstructed))
    
    # Residual analysis
    residual_clean = residual.dropna()
    
    # Test for white noise in residuals
    ljung_box = acorr_ljungbox(residual_clean, lags=min(10, len(residual_clean)//4))
    
    # Normality test
    shapiro_stat, shapiro_p = stats.shapiro(residual_clean[:5000])  # Limit for large series
    
    # Seasonality strength
    seasonal_strength = 1 - np.var(residual_clean) / np.var(original - trend)
    
    # Trend strength
    trend_strength = 1 - np.var(residual_clean) / np.var(original - seasonal)
    
    return {
        'reconstruction_mse': mse,
        'reconstruction_mae': mae,
        'residual_ljung_box_pvalue': ljung_box['lb_pvalue'].iloc[-1],
        'residual_normality_pvalue': shapiro_p,
        'seasonal_strength': max(0, seasonal_strength),
        'trend_strength': max(0, trend_strength)
    }

Visualization Best Practices

def plot_decomposition(components, title="Time Series Decomposition"):
    """
    Professional decomposition visualization
    """
    fig, axes = plt.subplots(4, 1, figsize=(15, 12))
    
    # Original series
    axes[0].plot(components['original'], color='black', linewidth=1.2)
    axes[0].set_title('Original Series')
    axes[0].grid(True, alpha=0.3)
    
    # Trend
    axes[1].plot(components['trend'], color='red', linewidth=1.5)
    axes[1].set_title('Trend Component')
    axes[1].grid(True, alpha=0.3)
    
    # Seasonal
    axes[2].plot(components['seasonal'], color='blue', linewidth=1)
    axes[2].set_title('Seasonal Component')
    axes[2].grid(True, alpha=0.3)
    
    # Residual
    axes[3].plot(components['residual'], color='green', linewidth=0.8)
    axes[3].axhline(y=0, color='black', linestyle='--', alpha=0.5)
    axes[3].set_title('Residual Component')
    axes[3].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.suptitle(title, y=1.02, fontsize=14)
    plt.show()

Method Selection Guidelines

Decision Framework

  1. Classical/STL: Regular, stable seasonality with minimal outliers
  2. X-13ARIMA-SEATS: Economic/business data requiring trading day adjustments
  3. EMD/EEMD: Non-linear, non-stationary data with complex patterns
  4. Wavelet: Multi-scale analysis, especially for financial or engineering data
  5. Seasonal strength < 0.3: Consider trend-only models
  6. High noise levels: Use robust methods (STL with robust=True, EEMD)

Parameter Tuning

  • STL seasonal parameter: Odd integer, typically 7-15 for strong seasonality
  • Wavelet selection: 'db4' for general use, 'haar' for sharp changes, 'coif2' for smooth data
  • EMD max_imf: Start with data_length/10, adjust based on meaningful components

Always validate decomposition quality using residual diagnostics and domain knowledge to ensure components are interpretable and useful for your specific application.

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.