Seasonality
Seasonality is any pattern that repeats on a schedule: busier weekends, a summer peak, a spike at the start of each month.
Verified against Python Prophet
Seasonal patterns match Python Prophet 1.4.0 on all three trend types. The seasonality, events and regressors benchmarks show every case side by side.
Built-in patterns
There are three built-in patterns. Each one switches on automatically once you have enough data for it:
| Pattern | Turns on when the history covers |
|---|---|
daily | At least 2 days, with several rows per day |
weekly | At least 2 weeks, with rows less than a week apart |
yearly | At least 2 years |
You can override each one with "off", or with { mode: "on" } plus optional settings.
Adding your own patterns
Add any repeating cycle you know about with seasonalities. Give it a name, a length in days (periodDays) and a fourierOrder.
import { Effect } from "effect";
import { fit, predict, prophetFittingBackendLayer } from "effect-prophet";
const date = (day: number) => new Date(Date.UTC(2024, 0, 1 + day)).toISOString();
// Two years of daily data with a weekly rhythm and a monthly billing cycle.
const history = Array.from({ length: 730 }, (_, day) => ({
timestamp: date(day),
value:
200 +
day * 0.1 +
15 * Math.sin((2 * Math.PI * day) / 7) +
8 * Math.cos((2 * Math.PI * day) / 30.5),
}));
const model = fit(history, {
// Built-in patterns: "auto" (the default), "off", or "on" with your own settings.
builtInSeasonalities: {
weekly: { mode: "on", fourierOrder: 5 },
yearly: "auto",
daily: "off",
},
// Add any repeating pattern you know about.
seasonalities: [{ name: "monthly", periodDays: 30.5, fourierOrder: 5 }],
});
const forecast = await Effect.runPromise(
Effect.gen(function* () {
return yield* predict(yield* model, [date(731)]);
}).pipe(Effect.provide(prophetFittingBackendLayer)),
);
for (const seasonality of forecast[0]?.seasonalities ?? []) {
if (seasonality.mode === "additive") {
console.log(seasonality.name, seasonality.value.toFixed(2));
}
}monthly 7.83
weekly 6.51What is fourierOrder?
It controls how detailed the pattern can be. Low values (3 to 5) give smooth, gentle curves. Higher values (10 or more) can capture sharp shapes, like a spike on one specific day, but are more likely to chase noise. The built-in defaults are 3 for weekly, 10 for yearly and 4 for daily.
Each pattern also accepts a priorScale, default 10. Lower it to keep a pattern's effect small unless the data strongly supports it.
Effects that grow with the trend
By default, a pattern adds a fixed amount: "+20 on Saturdays". If the swings grow as your data grows, use seasonalityMode: "multiplicative" so the pattern acts as a percentage instead: "+15% on Saturdays".
import { Effect } from "effect";
import { fit, predict, prophetFittingBackendLayer } from "effect-prophet";
const date = (day: number) => new Date(Date.UTC(2025, 0, 1 + day)).toISOString();
// The weekly swing grows as the business grows: it is a percentage, not a fixed amount.
const history = Array.from({ length: 120 }, (_, day) => {
const trend = 100 + day * 2;
return {
timestamp: date(day),
value: trend * (1 + 0.2 * Math.sin((2 * Math.PI * day) / 7)),
};
});
const model = fit(history, { seasonalityMode: "multiplicative" });
const forecast = await Effect.runPromise(
Effect.gen(function* () {
return yield* predict(yield* model, [date(121)]);
}).pipe(Effect.provide(prophetFittingBackendLayer)),
);
for (const seasonality of forecast[0]?.seasonalities ?? []) {
if (seasonality.mode === "multiplicative") {
// `factor` is the percentage effect; `contribution` is the same effect in your data's units.
console.log(
seasonality.name,
`${(seasonality.factor * 100).toFixed(1)}%`,
seasonality.contribution.toFixed(1),
);
}
}weekly 19.5% 66.7You can also set mode on a single pattern, event or regressor to mix the two kinds in one model.
Patterns that switch on and off
Sometimes a pattern only exists part of the time. A campus coffee shop has a weekly rhythm during the school year, but not in summer. Give the pattern a conditionName, and put a matching true/false value in conditions on every row, past and future.
import { Effect } from "effect";
import { fit, predict, prophetFittingBackendLayer } from "effect-prophet";
const date = (day: number) => new Date(Date.UTC(2025, 0, 1 + day));
// A campus coffee shop: the weekly pattern only exists while classes are in session.
const inSession = (day: number) => {
const month = date(day).getUTCMonth();
return month < 5 || month > 7;
};
const history = Array.from({ length: 365 }, (_, day) => ({
timestamp: date(day).toISOString(),
value: 80 + (inSession(day) ? 25 * Math.sin((2 * Math.PI * day) / 7) : 0),
// Every row says whether the condition is true on that day.
conditions: { inSession: inSession(day) },
}));
const model = fit(history, {
builtInSeasonalities: { weekly: "off" },
seasonalities: [
{ name: "school-week", periodDays: 7, fourierOrder: 3, conditionName: "inSession" },
],
});
// Future rows need the condition too.
const future = Array.from({ length: 7 }, (_, offset) => ({
timestamp: date(365 + offset).toISOString(),
conditions: { inSession: inSession(365 + offset) },
}));
const forecast = await Effect.runPromise(
Effect.gen(function* () {
return yield* predict(yield* model, future);
}).pipe(Effect.provide(prophetFittingBackendLayer)),
);
console.log(forecast.map((row) => row.value.toFixed(1)).join(", "));