Seasonality Detection

tseda.seasonality

Seasonal period detection for time series.

Public API

SeasonalityReport

Frozen dataclass with dominant period, candidate list, Fisher G-test, and is_seasonal flag.

SeasonalityDetector

Stateless detector supporting periodogram (FFT), ACF-peak, and combined strategies.

class tseda.seasonality.SeasonalityReport(dominant_period, candidate_periods, is_seasonal, method, n_obs, alpha, periodogram_periods, acf_periods, fisher_g_stat, fisher_p_value, strength_scores)[source]

Bases: object

Immutable seasonality detection result.

Parameters:
dominant_period

The single most likely seasonal period (in observations), or None if no seasonality was detected.

Type:

int or None

candidate_periods

All detected candidate periods as (period, score) pairs sorted by score descending. Score is normalised to [0, 1].

Type:

list of (int, float)

is_seasonal

True when the evidence for a dominant seasonal period is statistically significant at alpha.

Type:

bool

method

Detection method used: "periodogram", "acf", or "combined".

Type:

str

n_obs

Number of non-NaN observations used.

Type:

int

alpha

Significance level.

Type:

float

periodogram_periods

Top-k candidate periods from the FFT periodogram, as (period, normalised_power) pairs.

Type:

list of (int, float)

acf_periods

Candidate periods from significant ACF peaks, as (period, acf_value) pairs.

Type:

list of (int, float)

fisher_g_stat

Fisher’s G test statistic for the dominant periodogram peak.

Type:

float

fisher_p_value

P-value of the Fisher G test. Small values (< alpha) indicate that at least one spectral peak is too strong to be explained by white noise.

Type:

float

strength_scores

Normalised combined score for every candidate period.

Type:

dict of int → float

dominant_period: int | None
candidate_periods: List[Tuple[int, float]]
is_seasonal: bool
method: str
n_obs: int
alpha: float
periodogram_periods: List[Tuple[int, float]]
acf_periods: List[Tuple[int, float]]
fisher_g_stat: float
fisher_p_value: float
strength_scores: Dict[int, float]
summary()[source]

Return a plain-text summary.

Return type:

str

__repr__()[source]

Return repr(self).

Return type:

str

__init__(dominant_period, candidate_periods, is_seasonal, method, n_obs, alpha, periodogram_periods, acf_periods, fisher_g_stat, fisher_p_value, strength_scores)
Parameters:
Return type:

None

class tseda.seasonality.SeasonalityDetector[source]

Bases: object

Detect seasonal periods in a TimeSeries.

The detector is stateless — one instance, many series.

detect(ts, method, top_k, alpha, min_period, max_period)[source]

Return a SeasonalityReport.

Parameters:
Return type:

SeasonalityReport

test_period(ts, period, alpha)[source]

Test a specific period for significance.

Parameters:
Return type:

dict

Examples

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector

Weekly pattern in daily data:

>>> rng  = np.random.default_rng(1)
>>> n    = 140
>>> seas = np.tile(np.array([0, 1, 2, 2, 1, -1, -2], dtype=float), 20)
>>> y    = 10 + seas + rng.standard_normal(n) * 0.2
>>> idx  = pd.date_range("2020-01-06", periods=n, freq="D")
>>> ts   = TimeSeries(y, index=idx)
>>> r    = SeasonalityDetector().detect(ts)
>>> r.dominant_period
7
detect(ts, method='combined', *, top_k=5, alpha=0.05, min_period=2, max_period=None)[source]

Detect seasonal periods in ts.

Parameters:
  • ts (TimeSeries) – Input series. NaN values are filled by linear interpolation before spectral analysis.

  • method (str, optional) –

    Detection strategy:

    • "periodogram" — FFT power spectrum only.

    • "acf" — ACF peaks only.

    • "combined" — both methods with agreement bonus (default).

  • top_k (int, optional) – Maximum number of candidate periods to return per method. Default 5.

  • alpha (float, optional) – Significance level for Fisher G-test and ACF confidence interval. Default 0.05.

  • min_period (int, optional) – Minimum period to search for. Default 2.

  • max_period (int, optional) – Maximum period to search for. Defaults to n // 2.

Return type:

SeasonalityReport

Raises:
  • TypeError – If ts is not a TimeSeries.

  • ValueError – If method is not recognised, top_k < 1, alpha outside (0, 1), or the series is too short.

Examples

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector

Monthly series — detect 12-month period:

>>> rng  = np.random.default_rng(0)
>>> idx  = pd.date_range("2018-01", periods=60, freq="MS")
>>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 8, 5)
>>> ts   = TimeSeries(seas + rng.standard_normal(60) * 0.5, index=idx)
>>> r    = SeasonalityDetector().detect(ts)
>>> r.dominant_period
12
test_period(ts, period, *, alpha=0.05)[source]

Test whether a specific period is present in ts.

Runs both the periodogram and ACF detectors and checks whether the requested period appears among their significant candidates.

Parameters:
  • ts (TimeSeries) – Input series.

  • period (int) – The period to test (must be >= 2).

  • alpha (float, optional) – Significance level. Default 0.05.

Returns:

Keys:

  • "period" — the period tested.

  • "detected"True if the period was found by at least one method.

  • "periodogram_detected"True if it appears in the FFT peaks.

  • "acf_detected"True if the ACF at this lag is a significant positive peak.

  • "strength" — combined strength score in [0, 1].

  • "fisher_p_value" — p-value of the overall Fisher G-test.

Return type:

dict

Raises:

Examples

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector
>>> rng  = np.random.default_rng(0)
>>> idx  = pd.date_range("2018-01", periods=60, freq="MS")
>>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 8, 5)
>>> ts   = TimeSeries(seas + rng.standard_normal(60) * 0.5, index=idx)
>>> SeasonalityDetector().test_period(ts, 12)["detected"]
True

Seasonality detection for time series.

Three detection strategies are available, all implemented in pure numpy / scipy:

Method

Mechanism

Best for

periodogram

FFT power spectrum peaks

Any length; fast

acf

Autocorrelation peaks above 95 % CI

Short to medium series

combined

Both methods with agreement bonus

General use (default)

A Fisher G-test is always computed on the periodogram and contributes to the is_seasonal verdict.

Classes

SeasonalityReport

Frozen dataclass returned by SeasonalityDetector.detect().

SeasonalityDetector

Stateless detector.

Examples

Monthly data with a clear 12-month season:

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector
>>> rng  = np.random.default_rng(0)
>>> n    = 72
>>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 6, 6)
>>> y    = 50 + np.linspace(0, 4, n) + seas + rng.standard_normal(n) * 0.3
>>> idx  = pd.date_range("2018-01", periods=n, freq="MS")
>>> ts   = TimeSeries(y, index=idx, name="sales")
>>> r    = SeasonalityDetector().detect(ts)
>>> r.dominant_period
12
>>> r.is_seasonal
True
class tseda.seasonality.detector.SeasonalityReport(dominant_period, candidate_periods, is_seasonal, method, n_obs, alpha, periodogram_periods, acf_periods, fisher_g_stat, fisher_p_value, strength_scores)[source]

Bases: object

Immutable seasonality detection result.

Parameters:
dominant_period

The single most likely seasonal period (in observations), or None if no seasonality was detected.

Type:

int or None

candidate_periods

All detected candidate periods as (period, score) pairs sorted by score descending. Score is normalised to [0, 1].

Type:

list of (int, float)

is_seasonal

True when the evidence for a dominant seasonal period is statistically significant at alpha.

Type:

bool

method

Detection method used: "periodogram", "acf", or "combined".

Type:

str

n_obs

Number of non-NaN observations used.

Type:

int

alpha

Significance level.

Type:

float

periodogram_periods

Top-k candidate periods from the FFT periodogram, as (period, normalised_power) pairs.

Type:

list of (int, float)

acf_periods

Candidate periods from significant ACF peaks, as (period, acf_value) pairs.

Type:

list of (int, float)

fisher_g_stat

Fisher’s G test statistic for the dominant periodogram peak.

Type:

float

fisher_p_value

P-value of the Fisher G test. Small values (< alpha) indicate that at least one spectral peak is too strong to be explained by white noise.

Type:

float

strength_scores

Normalised combined score for every candidate period.

Type:

dict of int → float

dominant_period: int | None
candidate_periods: List[Tuple[int, float]]
is_seasonal: bool
method: str
n_obs: int
alpha: float
periodogram_periods: List[Tuple[int, float]]
acf_periods: List[Tuple[int, float]]
fisher_g_stat: float
fisher_p_value: float
strength_scores: Dict[int, float]
summary()[source]

Return a plain-text summary.

Return type:

str

__repr__()[source]

Return repr(self).

Return type:

str

__init__(dominant_period, candidate_periods, is_seasonal, method, n_obs, alpha, periodogram_periods, acf_periods, fisher_g_stat, fisher_p_value, strength_scores)
Parameters:
Return type:

None

class tseda.seasonality.detector.SeasonalityDetector[source]

Bases: object

Detect seasonal periods in a TimeSeries.

The detector is stateless — one instance, many series.

detect(ts, method, top_k, alpha, min_period, max_period)[source]

Return a SeasonalityReport.

Parameters:
Return type:

SeasonalityReport

test_period(ts, period, alpha)[source]

Test a specific period for significance.

Parameters:
Return type:

dict

Examples

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector

Weekly pattern in daily data:

>>> rng  = np.random.default_rng(1)
>>> n    = 140
>>> seas = np.tile(np.array([0, 1, 2, 2, 1, -1, -2], dtype=float), 20)
>>> y    = 10 + seas + rng.standard_normal(n) * 0.2
>>> idx  = pd.date_range("2020-01-06", periods=n, freq="D")
>>> ts   = TimeSeries(y, index=idx)
>>> r    = SeasonalityDetector().detect(ts)
>>> r.dominant_period
7
detect(ts, method='combined', *, top_k=5, alpha=0.05, min_period=2, max_period=None)[source]

Detect seasonal periods in ts.

Parameters:
  • ts (TimeSeries) – Input series. NaN values are filled by linear interpolation before spectral analysis.

  • method (str, optional) –

    Detection strategy:

    • "periodogram" — FFT power spectrum only.

    • "acf" — ACF peaks only.

    • "combined" — both methods with agreement bonus (default).

  • top_k (int, optional) – Maximum number of candidate periods to return per method. Default 5.

  • alpha (float, optional) – Significance level for Fisher G-test and ACF confidence interval. Default 0.05.

  • min_period (int, optional) – Minimum period to search for. Default 2.

  • max_period (int, optional) – Maximum period to search for. Defaults to n // 2.

Return type:

SeasonalityReport

Raises:
  • TypeError – If ts is not a TimeSeries.

  • ValueError – If method is not recognised, top_k < 1, alpha outside (0, 1), or the series is too short.

Examples

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector

Monthly series — detect 12-month period:

>>> rng  = np.random.default_rng(0)
>>> idx  = pd.date_range("2018-01", periods=60, freq="MS")
>>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 8, 5)
>>> ts   = TimeSeries(seas + rng.standard_normal(60) * 0.5, index=idx)
>>> r    = SeasonalityDetector().detect(ts)
>>> r.dominant_period
12
test_period(ts, period, *, alpha=0.05)[source]

Test whether a specific period is present in ts.

Runs both the periodogram and ACF detectors and checks whether the requested period appears among their significant candidates.

Parameters:
  • ts (TimeSeries) – Input series.

  • period (int) – The period to test (must be >= 2).

  • alpha (float, optional) – Significance level. Default 0.05.

Returns:

Keys:

  • "period" — the period tested.

  • "detected"True if the period was found by at least one method.

  • "periodogram_detected"True if it appears in the FFT peaks.

  • "acf_detected"True if the ACF at this lag is a significant positive peak.

  • "strength" — combined strength score in [0, 1].

  • "fisher_p_value" — p-value of the overall Fisher G-test.

Return type:

dict

Raises:

Examples

>>> import numpy as np, pandas as pd
>>> from tseda import TimeSeries
>>> from tseda.seasonality.detector import SeasonalityDetector
>>> rng  = np.random.default_rng(0)
>>> idx  = pd.date_range("2018-01", periods=60, freq="MS")
>>> seas = np.tile(np.sin(2 * np.pi * np.arange(12) / 12) * 8, 5)
>>> ts   = TimeSeries(seas + rng.standard_normal(60) * 0.5, index=idx)
>>> SeasonalityDetector().test_period(ts, 12)["detected"]
True