Seasonality captures repeating patterns at fixed intervals. Soothsayer supports yearly and weekly seasonality using Fourier terms.

This is useful for:

  • Holiday shopping peaks (yearly)
  • Summer/winter demand patterns (yearly)
  • Weekend dips in activity (weekly)
  • Monday peaks in emails (weekly)

How It Works

Seasonality is modeled using Fourier series:

seasonality(t) = sum(a_n * cos(2*pi*n*t/P) + b_n * sin(2*pi*n*t/P))

Where:

  • P = period (365.25 days for yearly, 7 days for weekly)
  • n = Fourier term index (1 to N)
  • a_n, b_n = learned coefficients

More Fourier terms allow more complex seasonal shapes.

For more details, see NeuralProphet's Seasonality documentation.

Configuration

model = Soothsayer.new(%{
  seasonality: %{
    yearly: %{
      enabled: true,      # Enable yearly seasonality
      fourier_terms: 6    # Number of Fourier terms
    },
    weekly: %{
      enabled: true,      # Enable weekly seasonality
      fourier_terms: 3    # Number of Fourier terms
    }
  }
})

Parameters

ParameterDefaultDescription
yearly.enabledtrueEnable yearly (annual) patterns
yearly.fourier_terms6Flexibility of yearly pattern
weekly.enabledtrueEnable weekly patterns
weekly.fourier_terms3Flexibility of weekly pattern

Yearly Seasonality

Captures patterns that repeat every year:

model = Soothsayer.new(%{
  seasonality: %{
    yearly: %{enabled: true, fourier_terms: 6},
    weekly: %{enabled: false}
  }
})

Good for:

  • Holiday shopping peaks
  • Summer vacation patterns
  • Agricultural cycles
  • Weather-driven demand

Fourier Terms for Yearly

fourier_termsFlexibilityUse when
3LowSmooth, simple patterns (one peak per year)
6MediumMost cases (default)
10+HighComplex patterns with multiple peaks

Weekly Seasonality

Captures patterns that repeat every week:

model = Soothsayer.new(%{
  seasonality: %{
    yearly: %{enabled: false},
    weekly: %{enabled: true, fourier_terms: 3}
  }
})

Good for:

  • Weekend vs weekday differences
  • Day-of-week patterns
  • Business cycle effects

Fourier Terms for Weekly

fourier_termsFlexibilityUse when
2LowSimple weekday/weekend split
3MediumMost cases (default)
5+HighComplex day-specific patterns

Choosing Fourier Terms

More terms = more flexibility = more risk of overfitting.

Signs you need more terms:

  • Residuals show repeating patterns
  • Known complex seasonal shape (multiple peaks per cycle)

Signs you need fewer terms:

  • Model fits noise in training but not test data
  • Seasonal pattern looks jagged or implausible

Start with defaults and adjust based on results.

Example: Extracting Seasonal Components

alias Explorer.DataFrame
alias Explorer.Series

# Generate data with both yearly and weekly seasonality
start_date = ~D[2020-01-01]
dates = Date.range(start_date, ~D[2022-12-31])

y = Enum.map(dates, fn date ->
  days = Date.diff(date, start_date)
  trend = 1000
  yearly = 50 * :math.sin(2 * :math.pi() * days / 365.25)
  weekly = 20 * :math.cos(2 * :math.pi() * Date.day_of_week(date) / 7)
  noise = :rand.normal(0, 10)
  trend + yearly + weekly + noise
end)

df = DataFrame.new(%{"ds" => dates, "y" => y})

# Fit model
model = Soothsayer.new(%{
  trend: %{changepoints: 0},  # Simple linear trend
  seasonality: %{
    yearly: %{enabled: true, fourier_terms: 6},
    weekly: %{enabled: true, fourier_terms: 3}
  },
  epochs: 50
})

fitted = Soothsayer.fit(model, df)

# Extract components
components = Soothsayer.predict_components(fitted, df["ds"])

# components.yearly_seasonality contains the yearly pattern
# components.weekly_seasonality contains the weekly pattern

Disabling Seasonality

If your data has no seasonal patterns:

model = Soothsayer.new(%{
  seasonality: %{
    yearly: %{enabled: false},
    weekly: %{enabled: false}
  }
})

Or disable just one type:

# Only yearly seasonality
model = Soothsayer.new(%{
  seasonality: %{
    yearly: %{enabled: true},
    weekly: %{enabled: false}
  }
})

Data Requirements

  • Yearly seasonality: Needs at least 2 years of data to learn reliably
  • Weekly seasonality: Needs at least a few weeks of data

With less data, the model may learn noise rather than true patterns. Consider disabling seasonality if you don't have enough data.

Network Architecture

Each seasonality type adds input features to the network:

# Yearly: 2 * fourier_terms features (sin and cos)
yearly_features = 2 * 6  # => 12 features for fourier_terms: 6

# Weekly: 2 * fourier_terms features
weekly_features = 2 * 3  # => 6 features for fourier_terms: 3

Next Steps

  • Auto-Regression - Capture dependencies on recent values
  • Trends - Piecewise linear trends with changepoints