Skip to content

Coming from Python ​

Effect Prophet fits the same model as Python Prophet, so the ideas carry over: changepoints, seasonalities, holidays, regressors, and the same defaults. What changes is the shape of the API. You pass plain data and options to functions instead of calling methods on a mutable Prophet object.

A side-by-side example ​

The Python version of a small forecast:

python
from prophet import Prophet

model = Prophet(changepoint_prior_scale=0.5, weekly_seasonality=False)
model.fit(df)  # df has `ds` and `y` columns

future = model.make_future_dataframe(periods=7)
forecast = model.predict(future)
print(forecast[["ds", "yhat"]])

And the same program here:

ts
import { Effect } from "effect";
import { fit, predict, prophetFittingBackendLayer } from "effect-prophet";

const date = (day: number) => new Date(Date.UTC(2025, 0, 1 + day)).toISOString();

// Python builds a dataframe with `ds` and `y` columns. Here each row is a plain object.
const history = Array.from({ length: 90 }, (_, day) => ({
  timestamp: date(day),
  value: 100 + day * 0.5 + 10 * Math.sin((2 * Math.PI * day) / 7),
}));

// Python's `make_future_dataframe(periods=7)`: you list the dates yourself.
const future = Array.from({ length: 7 }, (_, offset) => date(90 + offset));

const forecast = await Effect.runPromise(
  Effect.gen(function* () {
    // `Prophet(changepoint_prior_scale=0.5, weekly_seasonality=False).fit(df)`
    const model = yield* fit(history, {
      map: { changepointPriorScale: 0.5 },
      builtInSeasonalities: { weekly: "off" },
    });

    // `model.predict(future)`
    return yield* predict(model, future);
  }).pipe(Effect.provide(prophetFittingBackendLayer)),
);

for (const row of forecast) {
  console.log(new Date(row.timestamp).toISOString().slice(0, 10), row.value.toFixed(1));
}

Three differences to notice. fit and predict return Effects, so you yield* them inside Effect.gen and run the result once at the edge. The forecasting engine comes from prophetFittingBackendLayer. And you build the future dates yourself. New to Effect? Start with the five-minute introduction.

Translation table ​

Data ​

PythonEffect Prophet
ds columntimestamp: an exact UTC ISO string
y columnvalue
cap and floor columnscapacity and floor fields on each row
extra regressor columnsa regressors: { name: number } object on each row
condition columns for seasonalitiesa conditions: { name: boolean } object on each row
make_future_dataframe(periods=n)build the timestamps yourself, as in the example above
dropping NaN rowsleave the row out; see Your data

Model settings ​

PythonEffect Prophet
growth="linear", "flat", "logistic"growth: "linear" | "flat" | "logistic" (linear is the default)
n_changepoints, changepoint_rangemap.changepoints: { mode: "auto", count, range }
changepoints=[...]map.changepoints: { mode: "explicit", timestamps }
changepoint_prior_scalemap.changepointPriorScale
fit(algorithm="Newton", iter=n)map.optimizer: { algorithm: "newton", maxIterations: n }
yearly_seasonality, weekly_seasonality, daily_seasonalitybuiltInSeasonalities: { yearly, weekly, daily }: "auto", "off", or { mode: "on", fourierOrder, priorScale }
seasonality_mode="multiplicative"seasonalityMode: "multiplicative"
holidays_modeholidaysMode
scaling="minmax"scaling: "minmax"
add_seasonality(name, period, fourier_order, prior_scale, mode, condition_name)an entry in seasonalities: { name, periodDays, fourierOrder, priorScale, mode, conditionName }
add_regressor(name, prior_scale, standardize, mode)an entry in regressors: { name, priorScale, standardization, mode }
holidays dataframeevents: one { name, date, lowerWindowDays, upperWindowDays, priorScale } per occurrence
add_country_holidays(country_name="US")countryHolidays: { country: "US", coverage, priorScale }

The defaults are Prophet's, so a fit with no options produces the same model as Python. See How close are the results? for the evidence.

Output and evaluation ​

PythonEffect Prophet
predict(future) columns yhat, trendpredict(model, timestamps): value, trend, and every component broken out
yhat_lower, yhat_uppera separate predictUncertainty(model, timestamps, { seed, samples, intervalWidth }) call
interval_width, uncertainty_samplesintervalWidth and samples on predictUncertainty
predictive_samplespredictUncertainty(..., { output: "samples" })
regressor_coefficients(model)getRegressorCoefficients(model)
cross_validationcrossValidate
performance_metricsperformanceMetrics
model_to_json, model_from_jsonencodeFittedModel, decodeFittedModel; the format is not Python's
a loop over parameter gridssearchModels and evaluateHoldout

Gotchas for Python users ​

  • Timestamps must be exact UTC ISO strings, such as "2025-01-31T00:00:00.000Z". There is no format guessing and no time-zone conversion.
  • Missing values: leave the row out instead of using NaN. A row without a value is rejected.
  • Future rows carry their own inputs. Every future row needs its capacity (and floor), regressor values and condition flags, because there is no dataframe to look them up in.
  • The seed is required for uncertainty ranges, so results are repeatable.
  • Intervals are a separate call. predict returns point forecasts only, so you never pay for simulations you didn't ask for.
  • Models are immutable. Fitting returns a new model. There is nothing to mutate, and no warm_start_params.
  • Country coverage is explicit. The offline snapshot covers request years 1995–2060; reviewed gaps fail by default. Opting into "allow-known-gaps" accepts actual partial provider data, not complete coverage. Fitted models embed their selected snapshot; updates require a refit. See Country holiday calendars.
  • No plotting. Forecasts are plain data for your own charts.

See What's different for what isn't available yet and where behavior differs on purpose.

Inspired by Prophet from Meta. Not affiliated with Meta.