diff --git a/python/prophet/forecaster.py b/python/prophet/forecaster.py index 3bc6afc40..c71e2290c 100644 --- a/python/prophet/forecaster.py +++ b/python/prophet/forecaster.py @@ -818,6 +818,14 @@ def _prepare_regressors_for_predict( df.loc[missing_hist, name] = hist_lookup.reindex( df.loc[missing_hist, 'ds'] ).to_numpy() + # Training rows with a NaN y are not in self.history, but the + # regressor model was fit on every date where the regressor is known. + missing_hist = history_mask & df[name].isna() + if missing_hist.any() and predictor.history is not None: + known = predictor.history.set_index('ds')['y'] + df.loc[missing_hist, name] = known.reindex( + df.loc[missing_hist, 'ds'] + ).to_numpy() if future_mask.any(): future_dates = df.loc[future_mask, 'ds'] reg_future = pd.DataFrame({'ds': future_dates}) diff --git a/python/prophet/tests/test_prophet.py b/python/prophet/tests/test_prophet.py index dcce27d0c..7a65c49f7 100644 --- a/python/prophet/tests/test_prophet.py +++ b/python/prophet/tests/test_prophet.py @@ -1118,6 +1118,22 @@ def spy(self, df=None, vectorized=True): assert fcst.shape[0] == 5 assert any(call == "extra" for call in calls) + def test_regressor_predictor_with_missing_y_in_history(self, backend): + df = self._make_regressor_df()[["ds", "y", "reg"]] + df.loc[[10, 11, 40], "y"] = np.nan # the regressor is known on these dates + m = Prophet(uncertainty_samples=50, stan_backend=backend) + m.add_regressor("reg", regressor_predictor=True) + m.fit(df) + future = m.make_future_dataframe(periods=5) # no regressor column + fcst = m.predict(future) + assert fcst["yhat"].notnull().all() + # History rows use the known regressor values, including the rows with a NaN y. + prepared, _ = m._prepare_regressors_for_predict(future.copy()) + known = df.set_index("ds")["reg"] + np.testing.assert_allclose( + prepared["reg"].iloc[:len(df)].to_numpy(), known.to_numpy(), rtol=1e-12 + ) + class TestProphetWarmStart: def test_fit_warm_start(self, daily_univariate_ts, backend):