prophet-batch 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. prophet_batch-0.1.0/LICENSE +22 -0
  2. prophet_batch-0.1.0/MANIFEST.in +2 -0
  3. prophet_batch-0.1.0/PKG-INFO +203 -0
  4. prophet_batch-0.1.0/README.md +173 -0
  5. prophet_batch-0.1.0/pyproject.toml +49 -0
  6. prophet_batch-0.1.0/setup.cfg +4 -0
  7. prophet_batch-0.1.0/src/prophet_batch/__init__.py +33 -0
  8. prophet_batch-0.1.0/src/prophet_batch/_predict.py +68 -0
  9. prophet_batch-0.1.0/src/prophet_batch/_seeded.py +741 -0
  10. prophet_batch-0.1.0/src/prophet_batch/batch.py +1898 -0
  11. prophet_batch-0.1.0/src/prophet_batch.egg-info/PKG-INFO +203 -0
  12. prophet_batch-0.1.0/src/prophet_batch.egg-info/SOURCES.txt +39 -0
  13. prophet_batch-0.1.0/src/prophet_batch.egg-info/dependency_links.txt +1 -0
  14. prophet_batch-0.1.0/src/prophet_batch.egg-info/requires.txt +3 -0
  15. prophet_batch-0.1.0/src/prophet_batch.egg-info/top_level.txt +1 -0
  16. prophet_batch-0.1.0/tests/api.py +29 -0
  17. prophet_batch-0.1.0/tests/api_parity.py +262 -0
  18. prophet_batch-0.1.0/tests/conftest.py +24 -0
  19. prophet_batch-0.1.0/tests/fixtures/seeded.npz +0 -0
  20. prophet_batch-0.1.0/tests/make_fixtures.py +44 -0
  21. prophet_batch-0.1.0/tests/predict_scenarios.py +87 -0
  22. prophet_batch-0.1.0/tests/random_cases.py +365 -0
  23. prophet_batch-0.1.0/tests/scenarios.py +118 -0
  24. prophet_batch-0.1.0/tests/synthetic.py +18 -0
  25. prophet_batch-0.1.0/tests/test_api_parity.py +21 -0
  26. prophet_batch-0.1.0/tests/test_batch_solver.py +103 -0
  27. prophet_batch-0.1.0/tests/test_cv_many.py +169 -0
  28. prophet_batch-0.1.0/tests/test_edge_cases.py +92 -0
  29. prophet_batch-0.1.0/tests/test_fit_many.py +762 -0
  30. prophet_batch-0.1.0/tests/test_fit_many_parallel.py +135 -0
  31. prophet_batch-0.1.0/tests/test_history_uncertainty.py +136 -0
  32. prophet_batch-0.1.0/tests/test_multiplicative.py +131 -0
  33. prophet_batch-0.1.0/tests/test_predict.py +108 -0
  34. prophet_batch-0.1.0/tests/test_predict_many.py +266 -0
  35. prophet_batch-0.1.0/tests/test_properties.py +340 -0
  36. prophet_batch-0.1.0/tests/test_random_cases.py +95 -0
  37. prophet_batch-0.1.0/tests/test_rng.py +44 -0
  38. prophet_batch-0.1.0/tests/test_series_rng.py +45 -0
  39. prophet_batch-0.1.0/tools/check_docs.py +73 -0
  40. prophet_batch-0.1.0/tools/smoke.py +41 -0
  41. prophet_batch-0.1.0/tools/sync_seeded.py +175 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) Facebook, Inc. and its affiliates.
4
+ Copyright (c) 2026 Sumedhe Karunarathne.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ recursive-include tests *.py *.npz
2
+ recursive-include tools *.py
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: prophet-batch
3
+ Version: 0.1.0
4
+ Summary: Fit and forecast many Prophet models at once: batched exact MAP fits, seeded parallel forecasts.
5
+ Author: Sumedhe Karunarathne
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sumedhekaru/prophet-batch
8
+ Project-URL: Documentation, https://github.com/sumedhekaru/prophet-batch/blob/main/docs/guarantees.md
9
+ Project-URL: Examples, https://github.com/sumedhekaru/prophet-batch-examples
10
+ Project-URL: Issues, https://github.com/sumedhekaru/prophet-batch/issues
11
+ Keywords: prophet,forecasting,time series,batch,parallel
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: prophet<2,>=1.5
27
+ Requires-Dist: numpy>=1.21.6
28
+ Requires-Dist: pandas>=1.3.5
29
+ Dynamic: license-file
30
+
31
+ # prophet-batch
32
+
33
+ Fit and forecast many [Prophet](https://github.com/facebook/prophet) models at once, on top of the stock
34
+ `prophet` package (≥ 1.5):
35
+
36
+ - **`fit_many`** fits thousands of series together with batched MAP solvers instead of one Stan run per series.
37
+ The result for each series is an ordinary `Prophet` model whose log posterior is at least as high as Stan's.
38
+ - **`predict_many`** forecasts them in parallel with per-series seeds: a forecast depends only on
39
+ (seed, series id), not on worker count, order, or which other series are predicted.
40
+ - **`cross_validation_many`** runs stock-format cross-validation for many models at once.
41
+ - **`predict`** is `Prophet.predict` with a per-call random generator and faster history-interval modes.
42
+
43
+ Measured against stock Prophet 1.5.0 on the same 10 workers (fit + forecast, six public datasets, 38,905 series):
44
+ the same holdout accuracy, and 1.5× (short series) to 26.5× (M4 Daily) faster with full history intervals.
45
+ Guarantees and how they are tested: [docs/guarantees.md](https://github.com/sumedhekaru/prophet-batch/blob/main/docs/guarantees.md). Runnable use cases with accuracy
46
+ and speed against stock Prophet on macOS, Windows, Linux (Docker), Cloud Run and Spark (Dataproc):
47
+ [prophet-batch-examples](https://github.com/sumedhekaru/prophet-batch-examples).
48
+
49
+ Status: alpha (0.1.0): the public API (below) is stable in intent but may still change before 1.0.
50
+
51
+ ## Install
52
+
53
+ ```sh
54
+ pip install prophet-batch
55
+ ```
56
+
57
+ Requires Python ≥ 3.10 and `prophet>=1.5` (the stock PyPI wheel; no Stan build needed).
58
+
59
+ ## Quick start
60
+
61
+ Data is in long format: one row per (series, date), with a series-id column, `ds` and `y`.
62
+
63
+ ```python
64
+ import numpy as np
65
+ import pandas as pd
66
+
67
+ def make_series(n, seed):
68
+ rng = np.random.default_rng(seed)
69
+ t = np.arange(n)
70
+ y = 10 + 0.02 * t + 2 * np.sin(2 * np.pi * t / 7) + rng.normal(0, 0.5, n)
71
+ return pd.DataFrame({"ds": pd.date_range("2022-01-01", periods=n), "y": y})
72
+
73
+ df = pd.concat([make_series(400, i).assign(unique_id=f"store_{i}") for i in range(8)], ignore_index=True)
74
+ ```
75
+
76
+ ```python
77
+ from prophet_batch import fit_many, predict_many
78
+
79
+ models = fit_many(df, n_jobs=-1) # {series id: fitted Prophet}
80
+ forecasts = predict_many(models, periods=28, seed=42, n_jobs=-1)
81
+ forecasts["store_0"][["ds", "yhat", "yhat_lower", "yhat_upper"]].tail()
82
+ ```
83
+
84
+ `n_jobs=-1` uses every CPU available to the process (in a container: its CPU limit, which `os.cpu_count()`
85
+ ignores); results are bit-identical for any `n_jobs`. Each model is a normal `Prophet` object,
86
+ so `plot`, `plot_components`, `model_to_json`, `cross_validation` and the rest of the stock API work on it.
87
+
88
+ ### Configuring the models
89
+
90
+ Keyword arguments go to `Prophet()` for every series. For per-series configuration (holidays, extra regressors,
91
+ custom seasonalities), pass a function that returns a fresh, unfitted model for a series id:
92
+
93
+ ```python
94
+ from prophet import Prophet
95
+
96
+ def make_model(series_id):
97
+ m = Prophet(seasonality_mode="multiplicative" if series_id.endswith("7") else "additive")
98
+ m.add_country_holidays("US")
99
+ return m
100
+
101
+ models = fit_many(df, make_model=make_model)
102
+ ```
103
+
104
+ With `n_jobs != 1`, `make_model` must be picklable (a module-level function, not a lambda). Series whose models
105
+ come out identical and that have the same dates and regressor values (e.g. one holiday calendar and a regressor
106
+ shared by all series) are preprocessed once, as without `make_model`; the results are the same either way.
107
+
108
+ Other options: `fit_kwargs` (arguments for `Prophet.fit`, as a dict or a per-series function, used by series that
109
+ are fit by Stan), and `on_error="skip"` (leave out series that Prophet can't fit, e.g. fewer than 2 non-NaN rows,
110
+ with a warning, instead of raising).
111
+
112
+ ### What is batched
113
+
114
+ Linear and flat growth are fit by the batched solvers, with any mix of additive and multiplicative components
115
+ (seasonalities, holidays, extra regressors, conditional seasonalities). Additive models are solved exactly (the MAP
116
+ is a convex problem); multiplicative ones to a verified stationary point. Everything else (logistic growth,
117
+ `mcmc_samples > 0`, regressors with `regressor_predictor`) and any series the solver can't handle is fit by
118
+ `Prophet.fit` per series, with exactly the result of calling it directly.
119
+
120
+ ### Forecasts and history intervals
121
+
122
+ `predict_many(models, periods=..., ...)` builds each future with `make_future_dataframe`; pass
123
+ `futures={series id: DataFrame}` instead for regressors or logistic caps. `history_uncertainty` controls the
124
+ intervals on history rows, which are most of the cost when the history is included:
125
+
126
+ | `history_uncertainty` | history rows | future rows | speed |
127
+ |---|---|---|---|
128
+ | `True` (default) | sampled, as stock Prophet | sampled | like stock |
129
+ | `"analytic"` (MAP fits) | exact closed form: the limit of the sampled intervals | sampled, as with `False` | ≈ `False` |
130
+ | `False` | NaN | sampled (identical to predicting the future rows alone) | fastest |
131
+
132
+ `yhat` is the same in every mode. The future rows' intervals are equally valid draws in each mode, but not the same
133
+ numbers between `True` and the other two: with `True` the series' random stream is first used for the history rows.
134
+
135
+ ```python
136
+ forecasts = predict_many(models, periods=28, seed=42, history_uncertainty="analytic")
137
+ ```
138
+
139
+ Prophet's forecast frame has three columns per component (each holiday and regressor included), so with many
140
+ holidays and series it gets large. `columns=` keeps only what you need; the workers drop the rest before sending
141
+ results back, which saves memory and time (M5, 1,437 series with 30 holidays: 2.3 GB → 0.1 GB):
142
+
143
+ ```python
144
+ forecasts = predict_many(models, periods=28, seed=42, columns=["ds", "yhat", "yhat_lower", "yhat_upper"])
145
+ ```
146
+
147
+ ### Single models
148
+
149
+ `prophet_batch.predict` adds the same options to one model, with a numpy `Generator` for the draws. With the
150
+ default arguments it is bit-identical to `model.predict(df)`.
151
+
152
+ ```python
153
+ from prophet_batch import predict
154
+
155
+ model = models["store_0"]
156
+ fc = predict(model, model.make_future_dataframe(28), rng=np.random.default_rng(0),
157
+ history_uncertainty="analytic")
158
+ ```
159
+
160
+ ### Cross-validation
161
+
162
+ Same arguments and output columns as `prophet.diagnostics.cross_validation`, for many models:
163
+
164
+ ```python
165
+ from prophet.diagnostics import performance_metrics
166
+ from prophet_batch import cross_validation_many
167
+
168
+ cvs = cross_validation_many(models, horizon="14 days", period="60 days", initial="250 days", seed=1)
169
+ metrics = {sid: performance_metrics(cv) for sid, cv in cvs.items()}
170
+ ```
171
+
172
+ `as_frame=True` returns one DataFrame with a `unique_id` column.
173
+
174
+ ## Reproducibility
175
+
176
+ `predict_many(seed=...)` and `cross_validation_many(seed=...)` give every series its own generator, derived from
177
+ (seed, series id) with `numpy.random.SeedSequence`, so results do not depend on `n_jobs`, order or batch
178
+ composition. Without a seed, a fresh one is drawn, logged, and stored in each forecast's `attrs["seed"]`.
179
+
180
+ ## API stability
181
+
182
+ The public API is what `prophet_batch` exports at the top level: `fit_many`, `predict_many`,
183
+ `cross_validation_many`, `predict` and `series_rng`. Everything else (the solvers and helpers in
184
+ `prophet_batch.batch`, such as `solve_map` or `log_posterior`, and the private modules) is internal and may change
185
+ between releases.
186
+
187
+ ## Limitations
188
+
189
+ - Logistic growth, MCMC and regressor predictors are fit by Stan (correct, but not faster than stock).
190
+ - Prediction intervals are about 0.04–0.6 percentage points narrower than stock's on public data: the exact MAP
191
+ fits a little tighter than Stan's L-BFGS, which stops short of the optimum.
192
+ - With few, short series, process start-up dominates: use `n_jobs=1` there.
193
+ - **Windows speed (known issue in 0.1.0):** results are correct on Windows (CI runs the full suite there), but on a
194
+ first Windows laptop (Intel Core i7-1260P, 16 logical CPUs) `fit_many` scaled poorly: 3–4× faster than a 16-process
195
+ stock Prophet pool on 1,437 M5 items, against 9–10× on macOS, and slower with 16 workers than with 1 for a model
196
+ with regressors. The cause is being investigated (BLAS threading is the main suspect); a fix is planned for 0.1.1.
197
+
198
+ More: [docs/guarantees.md](https://github.com/sumedhekaru/prophet-batch/blob/main/docs/guarantees.md) (what is guaranteed and how it is tested) and
199
+ [docs/migration.md](https://github.com/sumedhekaru/prophet-batch/blob/main/docs/migration.md) (from a stock loop or process pool).
200
+
201
+ ## License
202
+
203
+ MIT. Contains code derived from Prophet (Copyright (c) Facebook, Inc. and its affiliates).
@@ -0,0 +1,173 @@
1
+ # prophet-batch
2
+
3
+ Fit and forecast many [Prophet](https://github.com/facebook/prophet) models at once, on top of the stock
4
+ `prophet` package (≥ 1.5):
5
+
6
+ - **`fit_many`** fits thousands of series together with batched MAP solvers instead of one Stan run per series.
7
+ The result for each series is an ordinary `Prophet` model whose log posterior is at least as high as Stan's.
8
+ - **`predict_many`** forecasts them in parallel with per-series seeds: a forecast depends only on
9
+ (seed, series id), not on worker count, order, or which other series are predicted.
10
+ - **`cross_validation_many`** runs stock-format cross-validation for many models at once.
11
+ - **`predict`** is `Prophet.predict` with a per-call random generator and faster history-interval modes.
12
+
13
+ Measured against stock Prophet 1.5.0 on the same 10 workers (fit + forecast, six public datasets, 38,905 series):
14
+ the same holdout accuracy, and 1.5× (short series) to 26.5× (M4 Daily) faster with full history intervals.
15
+ Guarantees and how they are tested: [docs/guarantees.md](https://github.com/sumedhekaru/prophet-batch/blob/main/docs/guarantees.md). Runnable use cases with accuracy
16
+ and speed against stock Prophet on macOS, Windows, Linux (Docker), Cloud Run and Spark (Dataproc):
17
+ [prophet-batch-examples](https://github.com/sumedhekaru/prophet-batch-examples).
18
+
19
+ Status: alpha (0.1.0): the public API (below) is stable in intent but may still change before 1.0.
20
+
21
+ ## Install
22
+
23
+ ```sh
24
+ pip install prophet-batch
25
+ ```
26
+
27
+ Requires Python ≥ 3.10 and `prophet>=1.5` (the stock PyPI wheel; no Stan build needed).
28
+
29
+ ## Quick start
30
+
31
+ Data is in long format: one row per (series, date), with a series-id column, `ds` and `y`.
32
+
33
+ ```python
34
+ import numpy as np
35
+ import pandas as pd
36
+
37
+ def make_series(n, seed):
38
+ rng = np.random.default_rng(seed)
39
+ t = np.arange(n)
40
+ y = 10 + 0.02 * t + 2 * np.sin(2 * np.pi * t / 7) + rng.normal(0, 0.5, n)
41
+ return pd.DataFrame({"ds": pd.date_range("2022-01-01", periods=n), "y": y})
42
+
43
+ df = pd.concat([make_series(400, i).assign(unique_id=f"store_{i}") for i in range(8)], ignore_index=True)
44
+ ```
45
+
46
+ ```python
47
+ from prophet_batch import fit_many, predict_many
48
+
49
+ models = fit_many(df, n_jobs=-1) # {series id: fitted Prophet}
50
+ forecasts = predict_many(models, periods=28, seed=42, n_jobs=-1)
51
+ forecasts["store_0"][["ds", "yhat", "yhat_lower", "yhat_upper"]].tail()
52
+ ```
53
+
54
+ `n_jobs=-1` uses every CPU available to the process (in a container: its CPU limit, which `os.cpu_count()`
55
+ ignores); results are bit-identical for any `n_jobs`. Each model is a normal `Prophet` object,
56
+ so `plot`, `plot_components`, `model_to_json`, `cross_validation` and the rest of the stock API work on it.
57
+
58
+ ### Configuring the models
59
+
60
+ Keyword arguments go to `Prophet()` for every series. For per-series configuration (holidays, extra regressors,
61
+ custom seasonalities), pass a function that returns a fresh, unfitted model for a series id:
62
+
63
+ ```python
64
+ from prophet import Prophet
65
+
66
+ def make_model(series_id):
67
+ m = Prophet(seasonality_mode="multiplicative" if series_id.endswith("7") else "additive")
68
+ m.add_country_holidays("US")
69
+ return m
70
+
71
+ models = fit_many(df, make_model=make_model)
72
+ ```
73
+
74
+ With `n_jobs != 1`, `make_model` must be picklable (a module-level function, not a lambda). Series whose models
75
+ come out identical and that have the same dates and regressor values (e.g. one holiday calendar and a regressor
76
+ shared by all series) are preprocessed once, as without `make_model`; the results are the same either way.
77
+
78
+ Other options: `fit_kwargs` (arguments for `Prophet.fit`, as a dict or a per-series function, used by series that
79
+ are fit by Stan), and `on_error="skip"` (leave out series that Prophet can't fit, e.g. fewer than 2 non-NaN rows,
80
+ with a warning, instead of raising).
81
+
82
+ ### What is batched
83
+
84
+ Linear and flat growth are fit by the batched solvers, with any mix of additive and multiplicative components
85
+ (seasonalities, holidays, extra regressors, conditional seasonalities). Additive models are solved exactly (the MAP
86
+ is a convex problem); multiplicative ones to a verified stationary point. Everything else (logistic growth,
87
+ `mcmc_samples > 0`, regressors with `regressor_predictor`) and any series the solver can't handle is fit by
88
+ `Prophet.fit` per series, with exactly the result of calling it directly.
89
+
90
+ ### Forecasts and history intervals
91
+
92
+ `predict_many(models, periods=..., ...)` builds each future with `make_future_dataframe`; pass
93
+ `futures={series id: DataFrame}` instead for regressors or logistic caps. `history_uncertainty` controls the
94
+ intervals on history rows, which are most of the cost when the history is included:
95
+
96
+ | `history_uncertainty` | history rows | future rows | speed |
97
+ |---|---|---|---|
98
+ | `True` (default) | sampled, as stock Prophet | sampled | like stock |
99
+ | `"analytic"` (MAP fits) | exact closed form: the limit of the sampled intervals | sampled, as with `False` | ≈ `False` |
100
+ | `False` | NaN | sampled (identical to predicting the future rows alone) | fastest |
101
+
102
+ `yhat` is the same in every mode. The future rows' intervals are equally valid draws in each mode, but not the same
103
+ numbers between `True` and the other two: with `True` the series' random stream is first used for the history rows.
104
+
105
+ ```python
106
+ forecasts = predict_many(models, periods=28, seed=42, history_uncertainty="analytic")
107
+ ```
108
+
109
+ Prophet's forecast frame has three columns per component (each holiday and regressor included), so with many
110
+ holidays and series it gets large. `columns=` keeps only what you need; the workers drop the rest before sending
111
+ results back, which saves memory and time (M5, 1,437 series with 30 holidays: 2.3 GB → 0.1 GB):
112
+
113
+ ```python
114
+ forecasts = predict_many(models, periods=28, seed=42, columns=["ds", "yhat", "yhat_lower", "yhat_upper"])
115
+ ```
116
+
117
+ ### Single models
118
+
119
+ `prophet_batch.predict` adds the same options to one model, with a numpy `Generator` for the draws. With the
120
+ default arguments it is bit-identical to `model.predict(df)`.
121
+
122
+ ```python
123
+ from prophet_batch import predict
124
+
125
+ model = models["store_0"]
126
+ fc = predict(model, model.make_future_dataframe(28), rng=np.random.default_rng(0),
127
+ history_uncertainty="analytic")
128
+ ```
129
+
130
+ ### Cross-validation
131
+
132
+ Same arguments and output columns as `prophet.diagnostics.cross_validation`, for many models:
133
+
134
+ ```python
135
+ from prophet.diagnostics import performance_metrics
136
+ from prophet_batch import cross_validation_many
137
+
138
+ cvs = cross_validation_many(models, horizon="14 days", period="60 days", initial="250 days", seed=1)
139
+ metrics = {sid: performance_metrics(cv) for sid, cv in cvs.items()}
140
+ ```
141
+
142
+ `as_frame=True` returns one DataFrame with a `unique_id` column.
143
+
144
+ ## Reproducibility
145
+
146
+ `predict_many(seed=...)` and `cross_validation_many(seed=...)` give every series its own generator, derived from
147
+ (seed, series id) with `numpy.random.SeedSequence`, so results do not depend on `n_jobs`, order or batch
148
+ composition. Without a seed, a fresh one is drawn, logged, and stored in each forecast's `attrs["seed"]`.
149
+
150
+ ## API stability
151
+
152
+ The public API is what `prophet_batch` exports at the top level: `fit_many`, `predict_many`,
153
+ `cross_validation_many`, `predict` and `series_rng`. Everything else (the solvers and helpers in
154
+ `prophet_batch.batch`, such as `solve_map` or `log_posterior`, and the private modules) is internal and may change
155
+ between releases.
156
+
157
+ ## Limitations
158
+
159
+ - Logistic growth, MCMC and regressor predictors are fit by Stan (correct, but not faster than stock).
160
+ - Prediction intervals are about 0.04–0.6 percentage points narrower than stock's on public data: the exact MAP
161
+ fits a little tighter than Stan's L-BFGS, which stops short of the optimum.
162
+ - With few, short series, process start-up dominates: use `n_jobs=1` there.
163
+ - **Windows speed (known issue in 0.1.0):** results are correct on Windows (CI runs the full suite there), but on a
164
+ first Windows laptop (Intel Core i7-1260P, 16 logical CPUs) `fit_many` scaled poorly: 3–4× faster than a 16-process
165
+ stock Prophet pool on 1,437 M5 items, against 9–10× on macOS, and slower with 16 workers than with 1 for a model
166
+ with regressors. The cause is being investigated (BLAS threading is the main suspect); a fix is planned for 0.1.1.
167
+
168
+ More: [docs/guarantees.md](https://github.com/sumedhekaru/prophet-batch/blob/main/docs/guarantees.md) (what is guaranteed and how it is tested) and
169
+ [docs/migration.md](https://github.com/sumedhekaru/prophet-batch/blob/main/docs/migration.md) (from a stock loop or process pool).
170
+
171
+ ## License
172
+
173
+ MIT. Contains code derived from Prophet (Copyright (c) Facebook, Inc. and its affiliates).
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "prophet-batch"
7
+ version = "0.1.0"
8
+ description = "Fit and forecast many Prophet models at once: batched exact MAP fits, seeded parallel forecasts."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Sumedhe Karunarathne" }]
14
+ dependencies = [
15
+ "prophet>=1.5,<2", # the seeded predict path mirrors 1.5's sampling code (tools/sync_seeded.py)
16
+ "numpy>=1.21.6",
17
+ "pandas>=1.3.5",
18
+ ]
19
+ keywords = ["prophet", "forecasting", "time series", "batch", "parallel"]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Science/Research",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Topic :: Scientific/Engineering",
31
+ "Topic :: Scientific/Engineering :: Mathematics",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/sumedhekaru/prophet-batch"
36
+ Documentation = "https://github.com/sumedhekaru/prophet-batch/blob/main/docs/guarantees.md"
37
+ Examples = "https://github.com/sumedhekaru/prophet-batch-examples"
38
+ Issues = "https://github.com/sumedhekaru/prophet-batch/issues"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.pyrefly]
44
+ project-includes = ["src/prophet_batch"]
45
+ python-version = "3.10"
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ timeout = 600 # pytest-timeout; the property tests fit dozens of random models
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,33 @@
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ """Fit and forecast many Prophet models at once, on top of stock Prophet."""
7
+
8
+ from __future__ import annotations
9
+
10
+ __version__ = '0.1.0'
11
+
12
+ import logging # noqa: E402
13
+ import multiprocessing # noqa: E402
14
+
15
+
16
+ def _drop_repeated_plotly_error(record: logging.LogRecord) -> bool:
17
+ return not str(record.msg).startswith('Importing plotly failed')
18
+
19
+
20
+ # Prophet logs a missing plotly when it is imported. Our worker processes import it
21
+ # again, so without this every worker repeats the parent's message. A spawned child
22
+ # imports us before parent_process() is set (re-running the main module, unpickling
23
+ # its work), while multiprocessing marks it as inheriting.
24
+ if (multiprocessing.parent_process() is not None
25
+ or getattr(multiprocessing.current_process(), '_inheriting', False)):
26
+ logging.getLogger('prophet.plot').addFilter(_drop_repeated_plotly_error)
27
+
28
+ from prophet_batch.batch import ( # noqa: E402
29
+ cross_validation_many, fit_many, predict_many, series_rng,
30
+ )
31
+ from prophet_batch._predict import predict # noqa: E402
32
+
33
+ __all__ = ['cross_validation_many', 'fit_many', 'predict', 'predict_many', 'series_rng']
@@ -0,0 +1,68 @@
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ """Seeded predict for any fitted Prophet model (stock or with the upstream hooks)."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import inspect
11
+ import warnings
12
+ from typing import TYPE_CHECKING, Any, Literal
13
+
14
+ from prophet_batch._seeded import seeded
15
+
16
+ if TYPE_CHECKING:
17
+ import numpy as np
18
+ import pandas as pd
19
+ from prophet import Prophet
20
+
21
+
22
+ def has_native_hooks(model: Prophet) -> bool:
23
+ """Whether the model's own predict accepts rng= and history_uncertainty=."""
24
+ params = inspect.signature(type(model).predict).parameters
25
+ return 'rng' in params and 'history_uncertainty' in params
26
+
27
+
28
+ def predict(
29
+ model: Prophet,
30
+ df: pd.DataFrame | None = None,
31
+ vectorized: bool = True,
32
+ history_uncertainty: bool | Literal['analytic'] = True,
33
+ rng: np.random.Generator | None = None,
34
+ ) -> pd.DataFrame:
35
+ """Prophet.predict with a per-call random generator and history interval modes.
36
+
37
+ Parameters
38
+ ----------
39
+ model: A fitted Prophet model.
40
+ df: As in Prophet.predict (None predicts on the history).
41
+ vectorized: As in Prophet.predict.
42
+ history_uncertainty: True (the default) samples intervals for every row, as stock
43
+ Prophet does. False leaves history rows' yhat/trend intervals NaN and samples
44
+ only future rows (exactly the intervals of predicting the future rows alone).
45
+ 'analytic' (MAP fits only) samples future rows as False does and gives history
46
+ rows the exact limit of the sampled intervals, yhat +/- z * sigma_obs * y_scale.
47
+ rng: Optional numpy Generator for all random draws. None uses NumPy's global RNG;
48
+ with None and history_uncertainty=True the result is bit-identical to
49
+ model.predict(df, vectorized).
50
+
51
+ A Prophet subclass that overrides predict (without the rng and history_uncertainty
52
+ arguments) gets Prophet's own predict logic here, not its override; a warning says so.
53
+
54
+ Returns
55
+ -------
56
+ A pd.DataFrame with the forecast components, as Prophet.predict returns.
57
+ """
58
+ if has_native_hooks(model):
59
+ native: Any = model # stock Prophet's type has no rng / history_uncertainty
60
+ return native.predict(df, vectorized, history_uncertainty=history_uncertainty, rng=rng)
61
+ from prophet import Prophet
62
+
63
+ if type(model).predict is not Prophet.predict:
64
+ warnings.warn(
65
+ f'{type(model).__name__} overrides predict; prophet_batch uses Prophet\'s predict '
66
+ 'logic (with seeded sampling) instead of the override.', stacklevel=2)
67
+ target: Any = seeded(model)
68
+ return target.predict(df, vectorized, history_uncertainty=history_uncertainty, rng=rng)