tseda.decomposition
tseda.decomposition
Time series decomposition into trend, seasonal, and residual components.
Public API
- DecompositionResult
Frozen dataclass holding all four components and quality metrics.
- ClassicalDecomposer
Centered moving-average decomposition (additive or multiplicative). Pure numpy — no extra dependencies.
- STLDecomposer
LOESS-based STL decomposition (statsmodels primary, scipy fallback).
- class tseda.decomposition.DecompositionResult(original, trend, seasonal, residual, period, model, method, strength_trend, strength_seasonal, n_obs_used)[source]
Bases:
objectImmutable time series decomposition result.
- Parameters:
original (TimeSeries)
trend (TimeSeries)
seasonal (TimeSeries)
residual (TimeSeries)
period (int)
model (str)
method (str)
strength_trend (float)
strength_seasonal (float)
n_obs_used (int)
- original
The input series.
- Type:
- trend
Smooth trend component. May contain NaN at the edges (classical decomposition only; STL fills the edges with LOESS extrapolation).
- Type:
- seasonal
Periodic seasonal component with the same length as original.
- Type:
- residual
Remainder after removing trend and seasonal. NaN wherever trend is NaN.
- Type:
- period
Number of observations per seasonal cycle (e.g., 12 for monthly data with an annual pattern).
- Type:
- strength_trend
Wang et al. (2006) trend strength:
max(0, 1 − Var(R) / Var(T + R)). In [0, 1].- Type:
- strength_seasonal
Wang et al. (2006) seasonality strength:
max(0, 1 − Var(R) / Var(S + R)). In [0, 1].- Type:
- original: TimeSeries
- trend: TimeSeries
- seasonal: TimeSeries
- residual: TimeSeries
- to_dataframe()[source]
Return all four components as a
pandas.DataFrame.- Returns:
Columns:
observed,trend,seasonal,residual.- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.classical import ClassicalDecomposer >>> idx = pd.date_range("2020", periods=36, freq="MS") >>> ts = TimeSeries(np.ones(36) + np.tile(np.arange(12), 3), index=idx) >>> df = ClassicalDecomposer().decompose(ts, period=12).to_dataframe() >>> list(df.columns) ['observed', 'trend', 'seasonal', 'residual']
- __init__(original, trend, seasonal, residual, period, model, method, strength_trend, strength_seasonal, n_obs_used)
- Parameters:
original (TimeSeries)
trend (TimeSeries)
seasonal (TimeSeries)
residual (TimeSeries)
period (int)
model (str)
method (str)
strength_trend (float)
strength_seasonal (float)
n_obs_used (int)
- Return type:
None
- class tseda.decomposition.ClassicalDecomposer[source]
Bases:
objectDecompose a
TimeSeriesusing the centered moving-average (classical) method.The decomposer is stateless — one instance may be reused.
- decompose(ts, period, model)[source]
Return a
DecompositionResult.- Parameters:
ts (TimeSeries)
period (int | None)
model (str)
- Return type:
Notes
The classical method has well-known limitations:
The trend component has NaN at both ends (half the period width).
It assumes a fixed seasonal pattern throughout the series.
It can be sensitive to outliers.
For more robust results, use
STLDecomposer.Examples
Additive decomposition:
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.classical import ClassicalDecomposer
>>> rng = np.random.default_rng(1) >>> n = 48 >>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 5, 4) >>> y = np.arange(n, dtype=float) * 0.3 + seas + rng.standard_normal(n) >>> idx = pd.date_range("2020-01", periods=n, freq="MS") >>> ts = TimeSeries(y, index=idx) >>> r = ClassicalDecomposer().decompose(ts, period=12) >>> r.model 'additive' >>> r.strength_seasonal > 0.5 True
- decompose(ts, period=None, *, model='additive')[source]
Decompose ts into trend, seasonal, and residual components.
- Parameters:
ts (TimeSeries) – Input series. Should be regularly spaced and have length
>= 2 × period.period (int, optional) – Seasonal period (number of observations per cycle). When omitted the period is inferred from
ts.freq: daily → 7, monthly → 12, quarterly → 4, etc.model (str, optional) –
"additive"(default) or"multiplicative".
- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If period cannot be inferred, is < 2, or the series is too short; also if model is not recognised.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.classical import ClassicalDecomposer
>>> idx = pd.date_range("2020", periods=36, freq="MS") >>> y = np.tile(np.arange(12, dtype=float), 3) + np.linspace(0, 6, 36) >>> ts = TimeSeries(y, index=idx) >>> r = ClassicalDecomposer().decompose(ts, period=12) >>> r.seasonal.n 36 >>> r.trend.n 36
- class tseda.decomposition.STLDecomposer[source]
Bases:
objectDecompose a
TimeSeriesusing the STL algorithm.The decomposer is stateless — one instance may be reused.
- decompose(ts, period, robust, seasonal_deg, trend_deg)[source]
Return a
DecompositionResult.- Parameters:
ts (TimeSeries)
period (int | None)
robust (bool)
seasonal_deg (int)
trend_deg (int)
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.stl import STLDecomposer
>>> rng = np.random.default_rng(2) >>> n = 48 >>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 8, 4) >>> y = np.linspace(0, 10, n) + seas + rng.standard_normal(n) * 0.3 >>> idx = pd.date_range("2020-01", periods=n, freq="MS") >>> ts = TimeSeries(y, index=idx) >>> r = STLDecomposer().decompose(ts, period=12) >>> r.strength_seasonal > 0.7 True
- decompose(ts, period=None, *, robust=True, seasonal_deg=1, trend_deg=1)[source]
Decompose ts using STL.
- Parameters:
ts (TimeSeries) – Input series. Must be regularly spaced and length
>= 2 × period.period (int, optional) – Seasonal period. Inferred from
ts.freqwhen omitted.robust (bool, optional) – When
True(default), use robust LOESS fitting to down-weight outliers. Has no effect on the fallback path.seasonal_deg (int, optional) – Polynomial degree for seasonal LOESS smoother (0 or 1). Passed to
statsmodels.tsa.seasonal.STL. Default 1.trend_deg (int, optional) – Polynomial degree for trend LOESS smoother (0 or 1). Default 1.
- Returns:
methodis"stl"when statsmodels is used, or"stl-fallback"otherwise.- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If period cannot be inferred, is < 2, or the series is too short.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.stl import STLDecomposer >>> rng = np.random.default_rng(3) >>> idx = pd.date_range("2020-01", periods=36, freq="MS") >>> seas = np.tile(np.arange(12, dtype=float), 3) >>> ts = TimeSeries(seas + rng.standard_normal(36) * 0.2, index=idx) >>> r = STLDecomposer().decompose(ts, period=12) >>> r.residual.n 36
Result
- class tseda.decomposition.classical.DecompositionResult(original, trend, seasonal, residual, period, model, method, strength_trend, strength_seasonal, n_obs_used)[source]
Bases:
objectImmutable time series decomposition result.
- Parameters:
original (TimeSeries)
trend (TimeSeries)
seasonal (TimeSeries)
residual (TimeSeries)
period (int)
model (str)
method (str)
strength_trend (float)
strength_seasonal (float)
n_obs_used (int)
- original
The input series.
- Type:
- trend
Smooth trend component. May contain NaN at the edges (classical decomposition only; STL fills the edges with LOESS extrapolation).
- Type:
- seasonal
Periodic seasonal component with the same length as original.
- Type:
- residual
Remainder after removing trend and seasonal. NaN wherever trend is NaN.
- Type:
- period
Number of observations per seasonal cycle (e.g., 12 for monthly data with an annual pattern).
- Type:
- strength_trend
Wang et al. (2006) trend strength:
max(0, 1 − Var(R) / Var(T + R)). In [0, 1].- Type:
- strength_seasonal
Wang et al. (2006) seasonality strength:
max(0, 1 − Var(R) / Var(S + R)). In [0, 1].- Type:
- original: TimeSeries
- trend: TimeSeries
- seasonal: TimeSeries
- residual: TimeSeries
- to_dataframe()[source]
Return all four components as a
pandas.DataFrame.- Returns:
Columns:
observed,trend,seasonal,residual.- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.classical import ClassicalDecomposer >>> idx = pd.date_range("2020", periods=36, freq="MS") >>> ts = TimeSeries(np.ones(36) + np.tile(np.arange(12), 3), index=idx) >>> df = ClassicalDecomposer().decompose(ts, period=12).to_dataframe() >>> list(df.columns) ['observed', 'trend', 'seasonal', 'residual']
- __init__(original, trend, seasonal, residual, period, model, method, strength_trend, strength_seasonal, n_obs_used)
- Parameters:
original (TimeSeries)
trend (TimeSeries)
seasonal (TimeSeries)
residual (TimeSeries)
period (int)
model (str)
method (str)
strength_trend (float)
strength_seasonal (float)
n_obs_used (int)
- Return type:
None
Classical Decomposition
- class tseda.decomposition.classical.ClassicalDecomposer[source]
Bases:
objectDecompose a
TimeSeriesusing the centered moving-average (classical) method.The decomposer is stateless — one instance may be reused.
- decompose(ts, period, model)[source]
Return a
DecompositionResult.- Parameters:
ts (TimeSeries)
period (int | None)
model (str)
- Return type:
Notes
The classical method has well-known limitations:
The trend component has NaN at both ends (half the period width).
It assumes a fixed seasonal pattern throughout the series.
It can be sensitive to outliers.
For more robust results, use
STLDecomposer.Examples
Additive decomposition:
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.classical import ClassicalDecomposer
>>> rng = np.random.default_rng(1) >>> n = 48 >>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 5, 4) >>> y = np.arange(n, dtype=float) * 0.3 + seas + rng.standard_normal(n) >>> idx = pd.date_range("2020-01", periods=n, freq="MS") >>> ts = TimeSeries(y, index=idx) >>> r = ClassicalDecomposer().decompose(ts, period=12) >>> r.model 'additive' >>> r.strength_seasonal > 0.5 True
- decompose(ts, period=None, *, model='additive')[source]
Decompose ts into trend, seasonal, and residual components.
- Parameters:
ts (TimeSeries) – Input series. Should be regularly spaced and have length
>= 2 × period.period (int, optional) – Seasonal period (number of observations per cycle). When omitted the period is inferred from
ts.freq: daily → 7, monthly → 12, quarterly → 4, etc.model (str, optional) –
"additive"(default) or"multiplicative".
- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If period cannot be inferred, is < 2, or the series is too short; also if model is not recognised.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.classical import ClassicalDecomposer
>>> idx = pd.date_range("2020", periods=36, freq="MS") >>> y = np.tile(np.arange(12, dtype=float), 3) + np.linspace(0, 6, 36) >>> ts = TimeSeries(y, index=idx) >>> r = ClassicalDecomposer().decompose(ts, period=12) >>> r.seasonal.n 36 >>> r.trend.n 36
STL Decomposition
- class tseda.decomposition.stl.STLDecomposer[source]
Bases:
objectDecompose a
TimeSeriesusing the STL algorithm.The decomposer is stateless — one instance may be reused.
- decompose(ts, period, robust, seasonal_deg, trend_deg)[source]
Return a
DecompositionResult.- Parameters:
ts (TimeSeries)
period (int | None)
robust (bool)
seasonal_deg (int)
trend_deg (int)
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.stl import STLDecomposer
>>> rng = np.random.default_rng(2) >>> n = 48 >>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 8, 4) >>> y = np.linspace(0, 10, n) + seas + rng.standard_normal(n) * 0.3 >>> idx = pd.date_range("2020-01", periods=n, freq="MS") >>> ts = TimeSeries(y, index=idx) >>> r = STLDecomposer().decompose(ts, period=12) >>> r.strength_seasonal > 0.7 True
- decompose(ts, period=None, *, robust=True, seasonal_deg=1, trend_deg=1)[source]
Decompose ts using STL.
- Parameters:
ts (TimeSeries) – Input series. Must be regularly spaced and length
>= 2 × period.period (int, optional) – Seasonal period. Inferred from
ts.freqwhen omitted.robust (bool, optional) – When
True(default), use robust LOESS fitting to down-weight outliers. Has no effect on the fallback path.seasonal_deg (int, optional) – Polynomial degree for seasonal LOESS smoother (0 or 1). Passed to
statsmodels.tsa.seasonal.STL. Default 1.trend_deg (int, optional) – Polynomial degree for trend LOESS smoother (0 or 1). Default 1.
- Returns:
methodis"stl"when statsmodels is used, or"stl-fallback"otherwise.- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If period cannot be inferred, is < 2, or the series is too short.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.decomposition.stl import STLDecomposer >>> rng = np.random.default_rng(3) >>> idx = pd.date_range("2020-01", periods=36, freq="MS") >>> seas = np.tile(np.arange(12, dtype=float), 3) >>> ts = TimeSeries(seas + rng.standard_normal(36) * 0.2, index=idx) >>> r = STLDecomposer().decompose(ts, period=12) >>> r.residual.n 36