diff --git a/.github/release.yml b/.github/release.yml index 236ad7afe..e69d87afe 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -11,6 +11,9 @@ changelog: - title: Major Changes 🛠 labels: - major + - title: Deprecations 🚨 + labels: + - deprecation - title: New Features 🎉 labels: - enhancement diff --git a/docs/source/notebooks/mmm/mmm_budget_allocation_example.ipynb b/docs/source/notebooks/mmm/mmm_budget_allocation_example.ipynb index 3ae1fe8d6..4208e1bd3 100644 --- a/docs/source/notebooks/mmm/mmm_budget_allocation_example.ipynb +++ b/docs/source/notebooks/mmm/mmm_budget_allocation_example.ipynb @@ -1236,7 +1236,7 @@ "initial_budget_scenario[\"t\"] = 0\n", "\n", "response_initial_budget = mmm.sample_posterior_predictive(\n", - " X_pred=initial_budget_scenario, extend_idata=False\n", + " initial_budget_scenario, extend_idata=False\n", ")\n", "\n", "response_initial_budget" diff --git a/docs/source/notebooks/mmm/mmm_case_study.ipynb b/docs/source/notebooks/mmm/mmm_case_study.ipynb index 84e1b1cd1..f7cd26c7b 100644 --- a/docs/source/notebooks/mmm/mmm_case_study.ipynb +++ b/docs/source/notebooks/mmm/mmm_case_study.ipynb @@ -6285,7 +6285,7 @@ "outputs": [], "source": [ "y_pred_test = mmm.sample_posterior_predictive(\n", - " X_pred=X_test,\n", + " X_test,\n", " include_last_observations=True,\n", " original_scale=True,\n", " var_names=[\"y\", \"channel_contributions\"],\n", @@ -6942,7 +6942,7 @@ " X_actual_uniform[control] = 0.0\n", "\n", "pred_test_uniform = mmm.sample_posterior_predictive(\n", - " X_pred=X_actual_uniform,\n", + " X_actual_uniform,\n", " include_last_observations=True,\n", " original_scale=True,\n", " var_names=[\"y\", \"channel_contributions\"],\n", diff --git a/docs/source/notebooks/mmm/mmm_counterfactuals.ipynb b/docs/source/notebooks/mmm/mmm_counterfactuals.ipynb index 2ca34b60f..4efe7cddc 100644 --- a/docs/source/notebooks/mmm/mmm_counterfactuals.ipynb +++ b/docs/source/notebooks/mmm/mmm_counterfactuals.ipynb @@ -701,7 +701,7 @@ ], "source": [ "y_forecast = mmm.sample_posterior_predictive(\n", - " X_pred=X_forecast, extend_idata=False, include_last_observations=True\n", + " X_forecast, extend_idata=False, include_last_observations=True\n", ")" ] }, @@ -958,7 +958,7 @@ ], "source": [ "y_intervention = mmm.sample_posterior_predictive(\n", - " X_pred=X_intervention, extend_idata=False, include_last_observations=True\n", + " X_intervention, extend_idata=False, include_last_observations=True\n", ")" ] }, @@ -1289,7 +1289,7 @@ ], "source": [ "y_counterfactual = mmm.sample_posterior_predictive(\n", - " X_pred=X_counterfactual, extend_idata=False\n", + " X_counterfactual, extend_idata=False\n", ");" ] }, diff --git a/docs/source/notebooks/mmm/mmm_example.ipynb b/docs/source/notebooks/mmm/mmm_example.ipynb index 18115b86d..6e0ffa7ce 100644 --- a/docs/source/notebooks/mmm/mmm_example.ipynb +++ b/docs/source/notebooks/mmm/mmm_example.ipynb @@ -9661,7 +9661,7 @@ "- `sample_posterior_predictive` : Get the full posterior predictive distribution\n", "- `predict`: Get the mean of the posterior predictive distribution\n", "\n", - "These methods take new data, `X_pred`, and some additional `kwargs` for new predictions. Namely, \n", + "These methods take new data, `X`, and some additional `kwargs` for new predictions. Namely, \n", "\n", "- `include_last_observations` : boolean flag in order to carry adstock effects from last observations in the training dataset" ] @@ -10313,9 +10313,7 @@ } ], "source": [ - "y_out_of_sample = mmm.sample_posterior_predictive(\n", - " X_pred=X_out_of_sample, extend_idata=False\n", - ")\n", + "y_out_of_sample = mmm.sample_posterior_predictive(X_out_of_sample, extend_idata=False)\n", "\n", "y_out_of_sample" ] @@ -10435,7 +10433,7 @@ ], "source": [ "y_out_of_sample_with_adstock = mmm.sample_posterior_predictive(\n", - " X_pred=X_out_of_sample, extend_idata=False, include_last_observations=True\n", + " X_out_of_sample, extend_idata=False, include_last_observations=True\n", ")" ] }, diff --git a/docs/source/notebooks/mmm/mmm_time_slice_cross_validation.ipynb b/docs/source/notebooks/mmm/mmm_time_slice_cross_validation.ipynb index 7fd73eba9..02bad2932 100644 --- a/docs/source/notebooks/mmm/mmm_time_slice_cross_validation.ipynb +++ b/docs/source/notebooks/mmm/mmm_time_slice_cross_validation.ipynb @@ -260,7 +260,7 @@ " mmm = fit_mmm(mmm, X_train, y_train, random_seed)\n", "\n", " y_pred_test = mmm.sample_posterior_predictive(\n", - " X_pred=X_test,\n", + " X_test,\n", " include_last_observations=True,\n", " original_scale=True,\n", " extend_idata=False,\n", diff --git a/docs/source/notebooks/mmm/mmm_tvp_example.ipynb b/docs/source/notebooks/mmm/mmm_tvp_example.ipynb index f302c6df0..8206ed959 100644 --- a/docs/source/notebooks/mmm/mmm_tvp_example.ipynb +++ b/docs/source/notebooks/mmm/mmm_tvp_example.ipynb @@ -613,7 +613,7 @@ " # Sample posterior predictive in whole data range (train and test)\n", " if \"posterior_predictive\" not in mmm.idata:\n", " mmm.sample_posterior_predictive(\n", - " X_pred=DATA, extend_idata=True, var_names=[\"y\", \"intercept\"]\n", + " DATA, extend_idata=True, var_names=[\"y\", \"intercept\"]\n", " )\n", " mmm.y = target_series.values\n", "\n", diff --git a/pymc_marketing/mlflow.py b/pymc_marketing/mlflow.py index e605e610f..e43055f20 100644 --- a/pymc_marketing/mlflow.py +++ b/pymc_marketing/mlflow.py @@ -673,7 +673,7 @@ class MMMWrapper(mlflow.pyfunc.PythonModel): Combine chain and draw dims into sample. Won't work if a dim named sample already exists. Defaults to True. include_last_observations : bool, default=False Boolean determining whether to include the last observations of the training data in order to carry over - costs with the adstock transformation. Assumes that X_pred are the next predictions following the + costs with the adstock transformation. Assumes that X are the next predictions following the training data. Defaults to False. original_scale : bool, default=True Boolean determining whether to return the predictions in the original scale of the target variable. @@ -800,7 +800,7 @@ def log_mmm( already exists. Used for posterior/prior predictive sampling. Defaults to True. include_last_observations : bool, optional Whether to include the last observations of training data for adstock transformation. - Assumes X_pred are next predictions following training data. Used for all prediction + Assumes X are next predictions following training data. Used for all prediction methods. Defaults to False. original_scale : bool, optional Whether to return predictions in original scale of target variable. Used for all diff --git a/pymc_marketing/mmm/hsgp.py b/pymc_marketing/mmm/hsgp.py index 6aebc89e2..11edd3183 100644 --- a/pymc_marketing/mmm/hsgp.py +++ b/pymc_marketing/mmm/hsgp.py @@ -655,10 +655,10 @@ def parameterize_from_data( if isinstance(X, np.ndarray): numeric_X = np.asarray(X) elif isinstance(X, TensorVariable): - numeric_X = X.get_value(borrow=False) # current numeric data + numeric_X = X.get_value(borrow=False) else: raise ValueError( - "X must be a NumPy array (or list) or a pm.Data/pm.MutableData. " + "X must be a NumPy array (or list) or a TensorVariable. " "If it's a plain symbolic tensor, you must manually specify m, L." ) @@ -666,9 +666,8 @@ def parameterize_from_data( if X_mid is None: X_mid = float(numeric_X.mean()) - # 3. Use the standard approximation m, L = create_m_and_L_recommendations( - numeric_X, # numeric version + numeric_X, X_mid, ls_lower=ls_lower, ls_upper=ls_upper, diff --git a/pymc_marketing/mmm/mmm.py b/pymc_marketing/mmm/mmm.py index a3451b8a2..f92147d85 100644 --- a/pymc_marketing/mmm/mmm.py +++ b/pymc_marketing/mmm/mmm.py @@ -55,6 +55,7 @@ create_new_spend_data, ) from pymc_marketing.mmm.validating import ValidateControlColumns +from pymc_marketing.model_builder import _handle_deprecate_pred_argument from pymc_marketing.model_config import parse_model_config from pymc_marketing.prior import Prior @@ -1902,7 +1903,7 @@ def legend_title_func(channel): def sample_posterior_predictive( self, - X_pred, + X=None, extend_idata: bool = True, combined: bool = True, include_last_observations: bool = False, @@ -1913,7 +1914,7 @@ def sample_posterior_predictive( Parameters ---------- - X_pred : array, shape (n_pred, n_features) + X : array, shape (n_pred, n_features) The input data used for prediction. extend_idata : bool, optional Boolean determining whether the predictions should be added to inference data object. Defaults to True. @@ -1921,7 +1922,7 @@ def sample_posterior_predictive( Combine chain and draw dims into sample. Won't work if a dim named sample already exists. Defaults to True. include_last_observations: bool, optional Boolean determining whether to include the last observations of the training data in order to carry over - costs with the adstock transformation. Assumes that X_pred are the next predictions following the + costs with the adstock transformation. Assumes that X are the next predictions following the training data.Defaults to False. original_scale: bool, optional Boolean determining whether to return the predictions in the original scale of the target variable. @@ -1932,15 +1933,16 @@ def sample_posterior_predictive( Returns ------- posterior_predictive_samples : DataArray, shape (n_pred, samples) - Posterior predictive samples for each input X_pred + Posterior predictive samples for each input X """ + X = _handle_deprecate_pred_argument(X, "X", sample_posterior_predictive_kwargs) if include_last_observations: - X_pred = pd.concat( - [self.X.iloc[-self.adstock.l_max :, :], X_pred], axis=0 + X = pd.concat( + [self.X.iloc[-self.adstock.l_max :, :], X], axis=0 ).sort_values(by=self.date_column) - self._data_setter(X_pred) + self._data_setter(X) with self.model: # sample with new input data post_pred = pm.sample_posterior_predictive( @@ -2251,7 +2253,7 @@ def sample_response_distribution( constant_data = allocation_strategy.to_dataset(name="allocation") return self.sample_posterior_predictive( - X_pred=synth_dataset, + X=synth_dataset, extend_idata=False, include_last_observations=True, original_scale=False, diff --git a/pymc_marketing/model_builder.py b/pymc_marketing/model_builder.py index 7cc224121..6f11c588f 100644 --- a/pymc_marketing/model_builder.py +++ b/pymc_marketing/model_builder.py @@ -46,6 +46,33 @@ def check_array(X, **kwargs): return X +def _handle_deprecate_pred_argument( + value, + name: str, + kwargs: dict, + none_allowed: bool = False, +): + name_pred = f"{name}_pred" + if name_pred in kwargs and value is not None: + raise ValueError(f"Both {name} and {name_pred} cannot be provided.") + + if name_pred not in kwargs and value is None and none_allowed: + return value + + if name_pred not in kwargs and value is None: + raise ValueError(f"Please provide {name}.") + + if name_pred in kwargs: + warnings.warn( + f"{name_pred} is deprecated, use {name} instead", + DeprecationWarning, + stacklevel=2, + ) + return kwargs.pop(name_pred) + + return value + + def create_idata_accessor(value: str, message: str): """Create a property accessor for an InferenceData object. @@ -634,7 +661,6 @@ def fit( X: pd.DataFrame, y: pd.Series | np.ndarray | None = None, progressbar: bool | None = None, - predictor_names: list[str] | None = None, random_seed: RandomState | None = None, **kwargs: Any, ) -> az.InferenceData: @@ -650,10 +676,6 @@ def fit( The target values (real numbers). If scikit-learn is available, array-like, otherwise array. progressbar : bool, optional Specifies whether the fit progress bar should be displayed. Defaults to True. - predictor_names : Optional[List[str]] = None, - Allows for custom naming of predictors when given in a form of a 2D array. - Allows for naming of predictors when given in a form of np.ndarray, if not provided - the predictors will be named like predictor1, predictor2... random_seed : Optional[RandomState] Provides sampler with initial random seed for obtaining reproducible samples. **kwargs : Any @@ -675,8 +697,6 @@ def fit( if isinstance(y, pd.Series) and not X.index.equals(y.index): raise ValueError("Index of X and y must match.") - if predictor_names is None: - predictor_names = [] if y is None: y = np.zeros(X.shape[0]) @@ -763,7 +783,7 @@ def fit_result(self, res: az.InferenceData) -> None: def predict( self, - X_pred: np.ndarray | pd.DataFrame | pd.Series, + X: np.ndarray | pd.DataFrame | pd.Series | None = None, extend_idata: bool = True, **kwargs, ) -> np.ndarray: @@ -773,7 +793,7 @@ def predict( Parameters ---------- - X_pred : array-like | array, shape (n_pred, n_features) + X : array-like | array, shape (n_pred, n_features) The input data used for prediction. If scikit-learn is available, array-like, otherwise array. extend_idata : Boolean Determine whether the predictions should be added to inference data object. @@ -782,8 +802,8 @@ def predict( Returns ------- - y_pred : ndarray, shape (n_pred,) - Predicted output corresponding to input X_pred. + ndarray, shape (n_pred,) + Predicted output corresponding to input X. Examples -------- @@ -795,7 +815,10 @@ def predict( """ posterior_predictive_samples = self.sample_posterior_predictive( - X_pred, extend_idata, combined=False, **kwargs + X, + extend_idata=extend_idata, + combined=False, + **kwargs, ) if self.output_var not in posterior_predictive_samples: @@ -810,8 +833,8 @@ def predict( def sample_prior_predictive( self, - X_pred, - y_pred=None, + X=None, + y=None, samples: int | None = None, extend_idata: bool = True, combined: bool = True, @@ -821,8 +844,11 @@ def sample_prior_predictive( Parameters ---------- - X_pred : array, shape (n_pred, n_features) + X : array, shape (n_pred, n_features) The input data used for prediction using prior distribution. + y : array, shape (n_pred,), optional + The target values (real numbers) used for prediction using prior distribution. + If not set, defaults to an array of zeros. samples : int Number of samples from the prior parameter distributions to generate. If not set, uses sampler_config['draws'] if that is available, otherwise defaults to 500. @@ -837,16 +863,19 @@ def sample_prior_predictive( Returns ------- prior_predictive_samples : DataArray, shape (n_pred, samples) - Prior predictive samples for each input X_pred + Prior predictive samples for each input X """ - if y_pred is None: - y_pred = np.zeros(len(X_pred)) + X = _handle_deprecate_pred_argument(X, "X", kwargs) + y = _handle_deprecate_pred_argument(y, "y", kwargs, none_allowed=True) + + if y is None: + y = np.zeros(len(X)) if samples is None: samples = self.sampler_config.get("draws", 500) if not hasattr(self, "model"): - self.build_model(X_pred, y_pred) + self.build_model(X, y) with self.model: # sample with new input data prior_pred: az.InferenceData = pm.sample_prior_predictive(samples, **kwargs) @@ -866,7 +895,7 @@ def sample_prior_predictive( def sample_posterior_predictive( self, - X_pred, + X=None, extend_idata: bool = True, combined: bool = True, **sample_posterior_predictive_kwargs, @@ -875,7 +904,7 @@ def sample_posterior_predictive( Parameters ---------- - X_pred : array, shape (n_pred, n_features) + X : array, shape (n_pred, n_features) The input data used for prediction using prior distribution.. extend_idata : Boolean Determine whether the predictions should be added to inference data object. @@ -888,10 +917,12 @@ def sample_posterior_predictive( Returns ------- posterior_predictive_samples : DataArray, shape (n_pred, samples) - Posterior predictive samples for each input X_pred + Posterior predictive samples for each input X """ - self._data_setter(X_pred) + X = _handle_deprecate_pred_argument(X, "X", sample_posterior_predictive_kwargs) + + self._data_setter(X) with self.model: post_pred = pm.sample_posterior_predictive( @@ -909,18 +940,6 @@ def sample_posterior_predictive( return az.extract(post_pred, variable_name, combined=combined) - def get_params(self, deep=True): - """Get all the model parameters needed to instantiate a copy of the model, not including training data.""" - return { - "model_config": self.model_config, - "sampler_config": self.sampler_config, - } - - def set_params(self, **params): - """Set all the model parameters needed to instantiate the model, not including training data.""" - self.model_config = params["model_config"] - self.sampler_config = params["sampler_config"] - @property @abstractmethod def _serializable_model_config(self) -> dict[str, int | float | dict]: @@ -937,17 +956,17 @@ def _serializable_model_config(self) -> dict[str, int | float | dict]: def predict_proba( self, - X_pred: np.ndarray | pd.DataFrame | pd.Series, + X: np.ndarray | pd.DataFrame | pd.Series | None = None, extend_idata: bool = True, combined: bool = False, **kwargs, ) -> xr.DataArray: """Alias for `predict_posterior`, for consistency with scikit-learn probabilistic estimators.""" - return self.predict_posterior(X_pred, extend_idata, combined, **kwargs) + return self.predict_posterior(X, extend_idata, combined, **kwargs) def predict_posterior( self, - X_pred: np.ndarray | pd.DataFrame | pd.Series, + X: np.ndarray | pd.DataFrame | pd.Series | None = None, extend_idata: bool = True, combined: bool = True, **kwargs, @@ -956,7 +975,7 @@ def predict_posterior( Parameters ---------- - X_pred : array-like | array, shape (n_pred, n_features) + X : array-like | array, shape (n_pred, n_features) The input data used for prediction. If scikit-learn is available, array-like, otherwise array. extend_idata : Boolean Determine whether the predictions should be added to inference data object. @@ -969,13 +988,14 @@ def predict_posterior( Returns ------- y_pred : DataArray - Posterior predictive samples for each input X_pred. + Posterior predictive samples for each input X. Shape is (n_pred, chains * draws) if combined is True, otherwise (chains, draws, n_pred). """ - X_pred = self._validate_data(X_pred) + X = _handle_deprecate_pred_argument(X, "X", kwargs) + X = self._validate_data(X) posterior_predictive_samples = self.sample_posterior_predictive( - X_pred, extend_idata, combined, **kwargs + X, extend_idata, combined, **kwargs ) if self.output_var not in posterior_predictive_samples: diff --git a/tests/mmm/test_hsgp.py b/tests/mmm/test_hsgp.py index 6ddf43f3b..06577123e 100644 --- a/tests/mmm/test_hsgp.py +++ b/tests/mmm/test_hsgp.py @@ -13,6 +13,8 @@ # limitations under the License. import matplotlib.pyplot as plt import numpy as np +import pymc as pm +import pytensor import pytensor.tensor as pt import pytest import xarray as xr @@ -307,3 +309,41 @@ def test_from_dict_with_non_dictionary_distribution_hspg_periodic() -> None: assert hsgp.scale == 1 assert hsgp.X_mid is None assert hsgp.dims == ("time",) + + +def test_hsgp_with_shared_data(): + """ + Test that HSGP works with a shared variable (pm.MutableData / pm.Data) and that + the computed graph properly includes and depends on the shared data. + """ + n_points = 10 + X = np.arange(n_points, dtype=float) + coords = {"time": X} + + # Create a model and a shared data variable using pm.MutableData + with pm.Model(coords=coords) as model: + # Create a shared data variable with the name "X_shared" + X_shared = pm.Data("X_shared", X, dims="time") + # Parameterize the HSGP using the shared data + hsgp = HSGP.parameterize_from_data(X_shared, dims="time") + # Create the deterministic variable "f" from the HSGP configuration + f = hsgp.create_variable("f") + + # Check that "f" is added to the model variables + assert "f" in model.named_vars + + # Ensure that the stored X is a shared tensor variable + assert isinstance(hsgp.X, pt.TensorVariable) + + # Verify that f depends on X_shared in the computational graph + assert any( + var.name == "X_shared" + for var in pytensor.graph.basic.ancestors([f]) + if hasattr(var, "name") + ), "f is not connected to X_shared in the computational graph" + + # Sample from prior to get initial values + prior = pm.sample_prior_predictive(samples=1) + + # prior should have a "f" variable + assert "f" in prior.prior diff --git a/tests/mmm/test_mmm.py b/tests/mmm/test_mmm.py index 226a861da..fb56e480e 100644 --- a/tests/mmm/test_mmm.py +++ b/tests/mmm/test_mmm.py @@ -1063,10 +1063,10 @@ def test_new_data_sample_posterior_predictive_method( ) -> None: """This is the method that is used in all the other methods that generate predictions.""" mmm = request.getfixturevalue(model_name) - X_pred = generate_data(new_dates) + X = generate_data(new_dates) posterior_predictive = mmm.sample_posterior_predictive( - X_pred=X_pred, + X=X, extend_idata=False, combined=combined, original_scale=original_scale, @@ -1087,10 +1087,10 @@ def test_sample_posterior_predictive_with_prediction_kwarg( predictions: bool, ) -> None: new_dates = pd.date_range("2022-01-01", "2022-03-01", freq="W-MON") - X_pred = generate_data(new_dates) + X = generate_data(new_dates) predictions = mmm_fitted.sample_posterior_predictive( - X_pred=X_pred, + X=X, extend_idata=False, combined=True, predictions=predictions, @@ -1115,14 +1115,14 @@ def test_new_data_include_last_observation_same_dims( request, ) -> None: mmm = request.getfixturevalue(model_name) - X_pred = generate_data(new_dates) + X = generate_data(new_dates) pp_without = mmm.predict_posterior( - X_pred, + X, include_last_observations=False, ) pp_with = mmm.predict_posterior( - X_pred, + X, include_last_observations=True, ) @@ -1148,9 +1148,9 @@ def test_new_data_predict_method( request, ) -> None: mmm = request.getfixturevalue(model_name) - X_pred = generate_data(new_dates) + X = generate_data(new_dates) - posterior_predictive_mean = mmm.predict(X_pred=X_pred) + posterior_predictive_mean = mmm.predict(X=X) assert isinstance(posterior_predictive_mean, np.ndarray) assert posterior_predictive_mean.shape[0] == new_dates.size @@ -1202,7 +1202,7 @@ def test_new_spend_contributions_prior_error() -> None: @pytest.mark.parametrize("original_scale", [True, False]) def test_new_spend_contributions_prior(original_scale, mmm, toy_X) -> None: mmm.sample_prior_predictive( - X_pred=toy_X, + X=toy_X, extend_idata=True, ) diff --git a/tests/test_model_builder.py b/tests/test_model_builder.py index 4a05364e1..3630e3709 100644 --- a/tests/test_model_builder.py +++ b/tests/test_model_builder.py @@ -358,7 +358,7 @@ def test_sample_xxx_predictive_keeps_second( X_pred = toy_X kwargs = { - "X_pred": X_pred, + "X": X_pred, "combined": False, "extend_idata": True, "random_seed": rng, @@ -457,7 +457,7 @@ def test_insufficient_attrs() -> None: match = "__init__ has parameters that are not in the attrs" with pytest.raises(ValueError, match=match): - model.sample_prior_predictive(X_pred=X_pred) + model.sample_prior_predictive(X=X_pred) def test_incorrect_set_idata_attrs_override() -> None: @@ -471,7 +471,7 @@ def create_idata_attrs(self) -> dict: match = "Missing required keys in attrs" with pytest.raises(ValueError, match=match): - model.sample_prior_predictive(X_pred=X_pred) + model.sample_prior_predictive(X=X_pred) @pytest.mark.parametrize( @@ -618,3 +618,41 @@ def test_graphviz(toy_X, toy_y): model.build_model(X=toy_X, y=toy_y) assert isinstance(model.graphviz(), graphviz.graphs.Digraph) + + +@pytest.mark.parametrize( + "method_name", + [ + "sample_posterior_predictive", + "predict", + ], +) +def test_X_pred_posterior_deprecation( + method_name, + fitted_model_instance, + toy_X, +) -> None: + if "posterior_predictive" in fitted_model_instance.idata: + del fitted_model_instance.idata.posterior_predictive + + with pytest.warns(DeprecationWarning, match="X_pred is deprecated"): + method = getattr(fitted_model_instance, method_name) + method(X_pred=toy_X) + + assert isinstance(fitted_model_instance.posterior_predictive, xr.Dataset) + + +def test_X_pred_prior_deprecation(fitted_model_instance, toy_X, toy_y) -> None: + if "prior" in fitted_model_instance.idata: + del fitted_model_instance.idata.prior + if "prior_predictive" in fitted_model_instance.idata: + del fitted_model_instance.idata.prior_predictive + + with pytest.warns(DeprecationWarning, match="X_pred is deprecated"): + fitted_model_instance.sample_prior_predictive(X_pred=toy_X) + + with pytest.warns(DeprecationWarning, match="y_pred is deprecated"): + fitted_model_instance.sample_prior_predictive(toy_X, y_pred=toy_y) + + assert isinstance(fitted_model_instance.prior, xr.Dataset) + assert isinstance(fitted_model_instance.prior_predictive, xr.Dataset)