Linear growth benchmarks
53 of 55 cases match Python Prophet. 2 are intentional differences, explained below.
Each case runs the same input through Effect Prophet and Python Prophet 1.4.0, then compares every forecast row, the trend, each component and the fitted noise. A case matches when all of them agree within its tolerances, and only matching cases are timed.
Each case's dot shows its result: matches, matches with internal differences flagged, intentional difference, doesn't match. Hover over a dot for details.
How to read the tables:
- Effect output and Python output are each library's final forecast value. Open a case to see every row side by side.
- Largest difference is the biggest gap between the two forecasts, as a share of the input data's range.
- Effect time and Python time are the median time for the timed step, usually fitting and predicting. Open a case for every step, including first-run and save/load times.
- A timed step marked different work calls public operations that produce different outputs in the two libraries, so its times aren't a speed comparison. The case page explains the difference.
- Flagged cases match, but the two libraries stopped their fitting search at slightly different points. The case page shows the internal numbers.
About this run
- Run
2026-11-07T174133-142Z-3a5a6cecon 2026-11-07, commit3a5a6ce - Both libraries ran in
linux/arm64containers on Apple M4 Pro - effect-prophet 0.0.0
- effect 4.0.6
- prophet 1.4.0
- Full benchmark report
Timings come from one machine and describe this workload only. They are not a general speed ranking. Where Python is faster, it is often because Python runs Stan's optimizer as compiled C++, while Effect Prophet runs the same algorithm as WebAssembly with narrower vector instructions. The gap is widest for Newton's method on short histories with many parameters.
Country holiday calendars
Pinned country calendars, matching explicit-event controls, dynamic future years and portable saved models. Known-gap opt-in is partial provider output, not a completeness guarantee.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| US holidays, additive | 96 rows | 21.5688 | 21.5688 | < 0.0001% | Fit and predict | 4.49 ms | 26.6 ms |
| Explicit US control, additive | 96 rows | 21.5688 | 21.5688 | < 0.0001% | Fit and predict | 3.94 ms | 23.1 ms |
| US holidays, multiplicative | 96 rows | 21.5778 | 21.5778 | < 0.0001% | Fit and predict | 4.76 ms | 27.4 ms |
| Explicit US control, multiplicative | 96 rows | 21.5778 | 21.5778 | < 0.0001% | Fit and predict | 4.48 ms | 23.6 ms |
| Observed spillovers and unseen future names | 96 rows | 33.9385 | 33.9385 | < 0.0001% | Fit and predict | 5.28 ms | 27.8 ms |
| Global prior scales | 96 rows | 20.8674 | 20.8674 | < 0.0001% | Fit and predict | 5.09 ms | 33.8 ms |
| Global prior scales with explicit overrides | 96 rows | 20.8727 | 20.8727 | < 0.0001% | Fit and predict | 5.11 ms | 33.9 ms |
Default settings
fit(history) against Prophet().fit(df) with no options on either side. Each library decides on its own which seasonal patterns to use. Warm-start cases fit the first rows, then refit the whole history from the parameters of that fit, as when new data arrives.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Defaults, one month of daily data | 32 rows | 30.7933 | 30.7933 | < 0.0001% | Fit and predict | 18.4 ms | 64.1 ms |
| Refit after a month of new data | 256 rows | 139.9296 | 139.9272 | 0.0025% | Refit from an earlier fit | 27.3 ms | 73.2 ms |
| Refit after one new day | 256 rows | 139.9300 | 139.9288 | 0.0012% | Refit from an earlier fit | 8.14 ms | 21.5 ms |
| Defaults, eight months of daily data | 256 rows | 139.9296 | 139.9278 | 0.0018% | Fit and predict | 26.5 ms | 89.9 ms |
| Defaults, irregular dates | 192 rows | 108.5108 | 108.4320 | 0.11% | Fit and predict | 6.80 ms | 17.1 ms |
Trends and changepoints
Straight-line and bending trends with automatic or hand-picked changepoints, plus unsorted rows, repeated timestamps and both target scalings.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Predicting from a known straight line | 256 rows | 43.7804 | 43.7804 | < 0.0001% | Predict | 532 µs | 2.64 ms |
| One hand-picked changepoint | 32 rows | 28.5156 | 28.5156 | < 0.0001% | Fit and predict | 2.11 ms | 13.8 ms |
| Sorted rows, hand-picked changepoint | 96 rows | 34.4145 | 34.4145 | < 0.0001% | Fit | 4.34 ms | 20.8 ms |
| Sorted rows, automatic changepoints | 96 rows | 34.4258 | 34.4258 | < 0.0001% | Fit | 30.9 ms | 108 ms |
| Shuffled rows, hand-picked changepoint | 96 rows | 34.4145 | 34.4145 | < 0.0001% | Fit | 4.31 ms | 20.8 ms |
| Shuffled rows, automatic changepoints | 96 rows | 34.4258 | 34.4258 | < 0.0001% | Fit | 32.0 ms | 109 ms |
| Repeated timestamps, hand-picked changepoint | 96 rows | 17.8737 | 17.8737 | < 0.0001% | Fit | 4.05 ms | 19.8 ms |
| Repeated timestamps, automatic changepoints | 96 rows | 17.9039 | 17.9039 | 0.0013% | Fit | 26.4 ms | 84.9 ms |
| Values far from zero, absmax scaling | 96 rows | 113.0935 | 113.0935 | < 0.0001% | Fit and predict | 4.91 ms | 26.7 ms |
| Values far from zero, minmax scaling | 96 rows | 113.0928 | 113.0928 | < 0.0001% | Fit and predict | 5.03 ms | 26.9 ms |
Seasonality, events and regressors
Custom and switchable seasonal patterns, events with windows, extra regressors, and additive and multiplicative effects mixed together.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Weekly pattern on irregular dates | 192 rows | 108.5119 | 108.5118 | < 0.0001% | Fit and predict | 1.99 ms | 11.1 ms |
| Two switchable weekly patterns | 256 rows | 139.4380 | 139.4353 | 0.0028% | Fit and predict | 2.77 ms | 16.8 ms |
| Events with windows | 96 rows | 54.2821 | 54.2821 | < 0.0001% | Fit and predict | 6.75 ms | 34.0 ms |
| Four regressors | 256 rows | 138.7384 | 138.7426 | 0.0043% | Fit and predict | 2.65 ms | 14.1 ms |
| A regressor forecast by its own model | 256 rows | 139.2608 | 139.2648 | 0.0040% | Fit and predict | 28.6 ms | 119 ms |
| Everything at once, small | 24 rows | 21.2025 | 21.2025 | < 0.0001% | Fit and predict | 1.79 ms | 16.0 ms |
| Everything at once, two years | 768 rows | 54.2716 | 54.2657 | 0.038% | Fit and predict | 11.8 ms | 44.3 ms |
| Additive and multiplicative together | 96 rows | 44.5183 | 44.5183 | < 0.0001% | Fit and predict | 4.92 ms | 29.5 ms |
| Repeated timestamps with features | 96 rows | 18.2191 | 18.2200 | 0.017% | Fit | 31.9 ms | 113 ms |
Uncertainty ranges
Both libraries simulate possible futures with random draws, so individual simulated values never match. Each range must still match Python's within the variation expected from that many draws. Anomaly cases also check that both libraries flag the outliers in a history.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Ranges with a forecast regressor | 256 rows | 139.2608 | 139.2648 | 0.0040% | Simulate uncertainty (different work) | 9.28 ms | 246 ms |
| Defaults, three years of ranges | 1095 rows | 611.2687 | 611.3156 | 0.011% | Simulate uncertainty (different work) | 69.7 ms | 375 ms |
| Defaults, ranges on irregular dates | 192 rows | 108.5108 | 108.4320 | 0.15% | Simulate uncertainty (different work) | 11.4 ms | 158 ms |
| Samples with repeated timestamps | 96 rows | 17.9039 | 17.9039 | 0.0013% | Simulate uncertainty (different work) | 471 µs | 16.8 ms |
| Ranges over past dates | 96 rows | 110.0950 | 110.0950 | < 0.0001% | Simulate uncertainty (different work) | 331 µs | 19.8 ms |
| Samples for one future date | 96 rows | 110.0819 | 110.0819 | < 0.0001% | Simulate uncertainty (different work) | 204 µs | 16.8 ms |
| Ranges with mixed effects | 96 rows | 45.1353 | 45.1353 | < 0.0001% | Simulate uncertainty (different work) | 487 µs | 26.4 ms |
| Samples with mixed effects | 96 rows | 45.1353 | 45.1353 | < 0.0001% | Simulate uncertainty (different work) | 798 µs | 68.6 ms |
| Anomalies in a history | 365 rows | 149.0161 | 148.9947 | 0.11% | Fit and find anomalies | 25.2 ms | 199 ms |
Full Bayesian fitting
fit(history, { mcmc: { samples, seed } }) against Prophet(mcmc_samples=...) and fit(df, seed=...): both run Stan's NUTS sampler with the same settings. Their chains can't match draw for draw, so each forecast, component and coefficient, a posterior mean, must agree within 0.15 of the narrowest 80% range, and each range bound within the variation expected from that many posterior draws. Divergences and R-hat are reported, and flagged when Effect's chains mix clearly worse than Python's.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Mixed features, 600 draws | 96 rows | 44.5139 | 44.5194 | 0.019% | Simulate uncertainty (different work) | 2.95 ms | 164 ms |
| 25 automatic changepoints | 256 rows | 139.9330 | 139.9339 | 0.00091% | Simulate uncertainty (different work) | 7.58 ms | 153 ms |
| A regressor forecast by its own model | 256 rows | 139.2825 | 139.2647 | 0.019% | Simulate uncertainty (different work) | 16.1 ms | 306 ms |
| A regressor forecast by its own sampled model | 256 rows | 139.2723 | 139.2547 | 0.019% | Simulate uncertainty (different work) | 16.3 ms | 500 ms |
Cross-validation and choosing settings
Rolling-origin cross-validation, metrics, baselines, settings search and a final holdout check.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Cross-validation with a forecast regressor | 256 rows | 106.8125 | 106.8124 | 0.0025% | Cross-validate (different work) | 51.6 ms | 185 ms |
| Cross-validation with a forecast regressor and ranges | 256 rows | 106.8125 | 106.8124 | 0.0025% | Cross-validate with ranges (different work) | 51.9 ms | 255 ms |
| Cross-validation and metrics | 96 rows | 108.5823 | 108.5823 | < 0.0001% | Cross-validate (different work) | 10.1 ms | 72.0 ms |
| Cross-validation with ranges | 96 rows | 108.5823 | 108.5823 | < 0.0001% | Cross-validate with ranges (different work) | 10.5 ms | 78.2 ms |
| Choosing settings and a final holdout | 96 rows | 108.5823 | 108.5823 | < 0.0001% | Search settings (different work) | 20.2 ms | 151 ms |
| Search with a failing candidate | 96 rows | 108.5823 | 108.5823 | < 0.0001% | Search settings (different work) | 21.4 ms | 153 ms |
| Choosing settings by a custom metric | 96 rows | 108.5823 | 108.5823 | < 0.0001% | Search settings (different work) | 20.4 ms | 143 ms |
| Twelve folds, three years, sequential and parallel | 1095 rows | 420.8894 | 420.8323 | 0.052% | Cross-validate (different work) | 953 ms | 2.61 s |
| Cross-validation with repeated timestamps | 96 rows | 15.5221 | 15.5221 | < 0.0001% | Cross-validate (different work) | 6.85 ms | 53.4 ms |
Intentional differences
Inputs where we deliberately behave differently from Python Prophet. These cases are expected to fail the comparison.
| Case | Input | Effect output | Python output | Largest difference | Timed step | Effect time | Python time |
|---|---|---|---|---|---|---|---|
| Every row on the same date, different values | 3 rows | FittingError | Optimizer crash | — | — | — | — |
| Every row on the same date, same value | 3 rows | FittingError | NaN forecast | — | — | — | — |