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: object

Immutable time series decomposition result.

Parameters:
original

The input series.

Type:

TimeSeries

trend

Smooth trend component. May contain NaN at the edges (classical decomposition only; STL fills the edges with LOESS extrapolation).

Type:

TimeSeries

seasonal

Periodic seasonal component with the same length as original.

Type:

TimeSeries

residual

Remainder after removing trend and seasonal. NaN wherever trend is NaN.

Type:

TimeSeries

period

Number of observations per seasonal cycle (e.g., 12 for monthly data with an annual pattern).

Type:

int

model

"additive" or "multiplicative".

Type:

str

method

"classical" or "stl".

Type:

str

strength_trend

Wang et al. (2006) trend strength: max(0, 1 Var(R) / Var(T + R)). In [0, 1].

Type:

float

strength_seasonal

Wang et al. (2006) seasonality strength: max(0, 1 Var(R) / Var(S + R)). In [0, 1].

Type:

float

n_obs_used

Number of non-NaN residual observations used for strength metrics.

Type:

int

original: TimeSeries
trend: TimeSeries
seasonal: TimeSeries
residual: TimeSeries
period: int
model: str
method: str
strength_trend: float
strength_seasonal: float
n_obs_used: int
to_dataframe()[source]

Return all four components as a pandas.DataFrame.

Returns:

Columns: observed, trend, seasonal, residual.

Return type:

pandas.DataFrame

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']
summary()[source]

Return a plain-text summary of the decomposition.

Return type:

str

__repr__()[source]

Return repr(self).

Return type:

str

__init__(original, trend, seasonal, residual, period, model, method, strength_trend, strength_seasonal, n_obs_used)
Parameters:
Return type:

None

class tseda.decomposition.ClassicalDecomposer[source]

Bases: object

Decompose a TimeSeries using the centered moving-average (classical) method.

The decomposer is stateless — one instance may be reused.

decompose(ts, period, model)[source]

Return a DecompositionResult.

Parameters:
Return type:

DecompositionResult

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:

DecompositionResult

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: object

Decompose a TimeSeries using 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:
Return type:

DecompositionResult

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.freq when 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:

method is "stl" when statsmodels is used, or "stl-fallback" otherwise.

Return type:

DecompositionResult

Raises:

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: object

Immutable time series decomposition result.

Parameters:
original

The input series.

Type:

TimeSeries

trend

Smooth trend component. May contain NaN at the edges (classical decomposition only; STL fills the edges with LOESS extrapolation).

Type:

TimeSeries

seasonal

Periodic seasonal component with the same length as original.

Type:

TimeSeries

residual

Remainder after removing trend and seasonal. NaN wherever trend is NaN.

Type:

TimeSeries

period

Number of observations per seasonal cycle (e.g., 12 for monthly data with an annual pattern).

Type:

int

model

"additive" or "multiplicative".

Type:

str

method

"classical" or "stl".

Type:

str

strength_trend

Wang et al. (2006) trend strength: max(0, 1 Var(R) / Var(T + R)). In [0, 1].

Type:

float

strength_seasonal

Wang et al. (2006) seasonality strength: max(0, 1 Var(R) / Var(S + R)). In [0, 1].

Type:

float

n_obs_used

Number of non-NaN residual observations used for strength metrics.

Type:

int

original: TimeSeries
trend: TimeSeries
seasonal: TimeSeries
residual: TimeSeries
period: int
model: str
method: str
strength_trend: float
strength_seasonal: float
n_obs_used: int
to_dataframe()[source]

Return all four components as a pandas.DataFrame.

Returns:

Columns: observed, trend, seasonal, residual.

Return type:

pandas.DataFrame

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']
summary()[source]

Return a plain-text summary of the decomposition.

Return type:

str

__repr__()[source]

Return repr(self).

Return type:

str

__init__(original, trend, seasonal, residual, period, model, method, strength_trend, strength_seasonal, n_obs_used)
Parameters:
Return type:

None

Classical Decomposition

class tseda.decomposition.classical.ClassicalDecomposer[source]

Bases: object

Decompose a TimeSeries using the centered moving-average (classical) method.

The decomposer is stateless — one instance may be reused.

decompose(ts, period, model)[source]

Return a DecompositionResult.

Parameters:
Return type:

DecompositionResult

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:

DecompositionResult

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: object

Decompose a TimeSeries using 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:
Return type:

DecompositionResult

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.freq when 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:

method is "stl" when statsmodels is used, or "stl-fallback" otherwise.

Return type:

DecompositionResult

Raises:

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