1 Installation

Install Farseer using pip or build from source:

From PyPI (Recommended)

pip install farseer

From Source

# Clone the repository
git clone https://github.com/ryanbieber/seer
cd seer

# For Python 3.13+, set compatibility flag
export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1

# Build and install with maturin
pip install maturin
maturin develop --release

Prerequisites

  • Python 3.11 or higher
  • CmdStan (automatically installed with cmdstanpy)
  • Rust toolchain (only for building from source)

2 Your First Forecast

Create a simple forecast in just a few lines of code:

import polars as pl
from datetime import datetime, timedelta
from farseer import Farseer

# Create sample data
dates = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(100)]
df = pl.DataFrame({
    'ds': dates,
    'y': list(range(100))  # Simple linear trend
})

# Create and fit model
m = Farseer()
m.fit(df)

# Make future predictions
future = m.make_future_dataframe(periods=30)
forecast = m.predict(future)

# View results
print(forecast.select(['ds', 'yhat', 'yhat_lower', 'yhat_upper']).tail())

Output:

shape: (5, 4)
┌─────────────────────┬────────────┬─────────────┬─────────────┐
│ ds                  ┆ yhat       ┆ yhat_lower  ┆ yhat_upper  │
│ ---                 ┆ ---        ┆ ---         ┆ ---         │
│ datetime[μs]        ┆ f64        ┆ f64         ┆ f64         │
╞═════════════════════╪════════════╪═════════════╪═════════════╡
│ 2020-04-06 00:00:00 ┆ 126.234    ┆ 123.891     ┆ 128.577     │
│ 2020-04-07 00:00:00 ┆ 127.234    ┆ 124.891     ┆ 129.577     │
│ 2020-04-08 00:00:00 ┆ 128.234    ┆ 125.891     ┆ 130.577     │
│ 2020-04-09 00:00:00 ┆ 129.234    ┆ 126.891     ┆ 131.577     │
│ 2020-04-10 00:00:00 ┆ 130.234    ┆ 127.891     ┆ 132.577     │
└─────────────────────┴────────────┴─────────────┴─────────────┘

3 Understanding the Model

Farseer's forecast includes several components:

  • yhat: The predicted value
  • yhat_lower, yhat_upper: Uncertainty intervals (default 80%)
  • trend: The overall trend component
  • yearly: Yearly seasonal component (if enabled)
  • weekly: Weekly seasonal component (if enabled)
  • daily: Daily seasonal component (if enabled)
# View all components
print(forecast.select([
    'ds', 'yhat', 'trend', 'yearly', 'weekly'
]).tail())

4 Customizing Your Model

Farseer provides many options to customize your forecast:

Seasonality Settings

m = Farseer(
    yearly_seasonality=True,   # Enable yearly patterns
    weekly_seasonality=True,   # Enable weekly patterns
    daily_seasonality=False,   # Disable daily patterns
    seasonality_mode='additive'  # or 'multiplicative'
)

Growth Settings

# Linear growth (default)
m = Farseer(growth='linear')

# Logistic growth with capacity
df['cap'] = 200  # Maximum capacity
m = Farseer(growth='logistic')
m.fit(df)

# Flat (no trend)
m = Farseer(growth='flat')

Changepoint Detection

m = Farseer(
    n_changepoints=25,           # Number of potential changepoints
    changepoint_range=0.8,       # Use first 80% of data
    changepoint_prior_scale=0.05 # Flexibility (higher = more flexible)
)

Uncertainty Intervals

m = Farseer(interval_width=0.95)  # 95% confidence intervals
m.fit(df)
forecast = m.predict(future)

5 Working with Real Data

Here's a complete example with realistic data including trend and seasonality:

import polars as pl
import numpy as np
from datetime import datetime, timedelta
from farseer import Farseer

# Generate realistic data
np.random.seed(42)
n = 365 * 2  # 2 years of daily data
dates = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(n)]

# Create components
t = np.arange(n)
trend = t * 0.5 + 100  # Linear growth
yearly = 10 * np.sin(2 * np.pi * t / 365.25)  # Yearly seasonality
weekly = 5 * np.sin(2 * np.pi * t / 7)  # Weekly seasonality
noise = np.random.normal(0, 2, n)

y = trend + yearly + weekly + noise

df = pl.DataFrame({'ds': dates, 'y': y})

# Split into train/test
train_size = int(n * 0.8)
train = df[:train_size]
test = df[train_size:]

# Fit model
m = Farseer(
    yearly_seasonality=True,
    weekly_seasonality=True,
    daily_seasonality=False
)
m.fit(train)

# Forecast on test set
forecast = m.predict(test)

# Evaluate performance
from sklearn.metrics import mean_absolute_error, mean_squared_error

mae = mean_absolute_error(test['y'], forecast['yhat'][:len(test)])
rmse = np.sqrt(mean_squared_error(test['y'], forecast['yhat'][:len(test)]))

print(f"Test MAE: {mae:.2f}")
print(f"Test RMSE: {rmse:.2f}")

6 Advanced Features

Weighted Observations

Give more importance to recent or reliable data by adding a 'weight' column:

import polars as pl
import numpy as np
from datetime import datetime, timedelta
from farseer import Farseer

dates = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(100)]
df = pl.DataFrame({
    'ds': dates,
    'y': np.random.randn(100).cumsum() + 50,
    'weight': [2.0 if i >= 80 else 1.0 for i in range(100)]  # Emphasize recent data
})

m = Farseer()
m.fit(df)  # Weights automatically detected and used

Conditional Seasonality

Apply seasonal patterns only when certain conditions are met:

from farseer import Farseer

m = Farseer()

# Add seasonality that only applies on weekdays
m.add_seasonality(
    name='weekly_on_weekday',
    period=7,
    fourier_order=3,
    condition_name='is_weekday'
)

# Add condition column to your data
df = df.with_columns((pl.col('ds').dt.weekday() < 5).alias('is_weekday'))
m.fit(df)

# Remember to add condition to future dataframe too
future = m.make_future_dataframe(periods=30)
future = future.with_columns((pl.col('ds').dt.weekday() < 5).alias('is_weekday'))
forecast = m.predict(future)

Floor & Cap (Logistic Growth)

Model data with both upper and lower bounds:

import polars as pl
from datetime import datetime, timedelta
from farseer import Farseer

# Logistic growth with floor (minimum) and cap (maximum)
dates = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(100)]
df = pl.DataFrame({
    'ds': dates,
    'y': [...],  # Your data
    'floor': 1.5,  # Minimum value
    'cap': 10.0    # Maximum value
})

m = Farseer(growth='logistic')
m.fit(df)

# Add floor and cap to future dataframe
future = m.make_future_dataframe(periods=30)
future = future.with_columns([
    pl.lit(1.5).alias('floor'),
    pl.lit(10.0).alias('cap')
])
forecast = m.predict(future)

Smart Regressor Standardization

Farseer automatically detects binary vs continuous regressors:

from farseer import Farseer

m = Farseer()

# Binary regressor (0/1) - won't be standardized in 'auto' mode
m.add_regressor('is_weekend', standardize='auto')

# Continuous regressor - will be standardized in 'auto' mode
m.add_regressor('temperature', standardize='auto')

# Force standardization
m.add_regressor('custom_feature', standardize='true')

# Force no standardization
m.add_regressor('manual_feature', standardize='false')

m.fit(df)

Holiday Priors

Different prior scales for different holidays:

from farseer import Farseer

m = Farseer()

# Major holiday with strong prior
m.add_holidays(
    'christmas',
    dates=['2020-12-25', '2021-12-25', '2022-12-25'],
    prior_scale=20.0,  # Strong effect
    lower_window=-1,   # Day before
    upper_window=1     # Day after
)

# Minor event with weak prior
m.add_holidays(
    'minor_event',
    dates=['2020-03-17', '2021-03-17'],
    prior_scale=5.0    # Weak effect
)

m.fit(df)

7 Next Steps

Now that you've got the basics and advanced features, explore more:

💡 Pro Tips

  • Use Polars DataFrames instead of Pandas for 5-10x better performance
  • Farseer automatically uses all CPU cores for parallel optimization
  • The 'auto' standardize mode intelligently detects binary regressors
  • Conditional seasonality is powerful for modeling weekday/weekend differences
  • Save trained models with m.save('model.json') for deployment