Skip to content

Adapters Reference

mmm_eval.adapters

Adapters for different MMM frameworks.

Classes

MeridianAdapter(config: MeridianConfig)

Bases: BaseAdapter

Adapter for Google Meridian MMM framework.

Initialize the Meridian adapter.

Parameters:

Name Type Description Default
config MeridianConfig

MeridianConfig object

required
Source code in mmm_eval/adapters/meridian.py
def __init__(self, config: MeridianConfig):
    """Initialize the Meridian adapter.

    Args:
        config: MeridianConfig object

    """
    self.config = config
    self.input_data_builder_schema = config.input_data_builder_config

    self.date_column = config.date_column
    self.channel_spend_columns = self.input_data_builder_schema.channel_spend_columns

    # Initialize stateful attributes to None/False
    self._reset_state()
Attributes
media_channels: list[str] property

Return the channel names used by this adapter.

For Meridian, this returns the human-readable channel names from the config.

Returns List of channel names

primary_media_regressor_columns: list[str] property

Return the primary media regressor columns that should be perturbed in tests.

For Meridian, this depends on the configuration: - If channel_reach_columns is provided: returns empty list (not supported in perturbation tests) - If channel_impressions_columns is provided: returns channel_impressions_columns - Otherwise: returns channel_spend_columns

Returns List of column names that are used as primary media regressors in the model

primary_media_regressor_type: PrimaryMediaRegressor property

Return the type of primary media regressors used by the model.

For Meridian, this is determined by the configuration: - If channel_reach_columns is provided: returns PrimaryMediaRegressor.REACH_AND_FREQUENCY - If channel_impressions_columns is provided: returns PrimaryMediaRegressor.IMPRESSIONS - Otherwise: returns PrimaryMediaRegressor.SPEND

Returns PrimaryMediaRegressor enum value

Functions
fit(data: pd.DataFrame, max_train_date: pd.Timestamp | None = None) -> None

Fit the Meridian model to data.

Parameters:

Name Type Description Default
data DataFrame

Training data

required
max_train_date Timestamp | None

Optional maximum training date for holdout validation

None
Source code in mmm_eval/adapters/meridian.py
def fit(self, data: pd.DataFrame, max_train_date: pd.Timestamp | None = None) -> None:
    """Fit the Meridian model to data.

    Args:
        data: Training data
        max_train_date: Optional maximum training date for holdout validation

    """
    # Reset state to ensure clean start when new data is provided
    self._reset_state()

    # build Meridian data object
    self.training_data = construct_meridian_data_object(data, self.config)
    self.max_train_date = max_train_date

    model_spec_kwargs = dict(self.config.model_spec_config)

    # if max train date is provided, construct a mask that is True for all dates before max_train_date
    if self.max_train_date:
        self.holdout_mask = construct_holdout_mask(self.max_train_date, self.training_data.kpi.time)
        # model expects a 2D array of shape (n_geos, n_times) so have to duplicate the values across each geo
        model_spec_kwargs["holdout_id"] = np.repeat(
            self.holdout_mask[None, :], repeats=len(self.training_data.kpi.geo), axis=0
        )

    # Create and fit the Meridian model
    model_spec = ModelSpec(**model_spec_kwargs)
    self.model = Meridian(
        input_data=self.training_data,  # type: ignore
        model_spec=model_spec,
    )
    self.trace = self.model.sample_posterior(**dict(self.config.sample_posterior_config))

    # used to compute channel contributions for ROI calculations
    self.analyzer = Analyzer(self.model)
    self.is_fitted = True
fit_and_predict(train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray

Fit the Meridian model and make predictions given new input data.

The full dataset must be passed to fit(), since making out-of-sample predictions is only possible by way of specifying a holdout mask when sampling from the posterior.

Parameters:

Name Type Description Default
train DataFrame

Training data

required
test DataFrame

Test data

required

Returns:

Type Description
ndarray

Predicted values for the test period

Source code in mmm_eval/adapters/meridian.py
def fit_and_predict(self, train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray:
    """Fit the Meridian model and make predictions given new input data.

    The full dataset must be passed to `fit()`, since making out-of-sample predictions
    is only possible by way of specifying a holdout mask when sampling from the
    posterior.

    Args:
        train: Training data
        test: Test data

    Returns:
        Predicted values for the test period

    """
    train_and_test = pd.concat([train, test])
    max_train_date = train[self.date_column].squeeze().max()
    self.fit(train_and_test, max_train_date=max_train_date)
    return self.predict()
get_channel_names() -> list[str]

Get the channel names that would be used as the index in get_channel_roi results.

For Meridian, this returns the media_channels which are the human-readable channel names used in the ROI results.

Returns List of channel names

Source code in mmm_eval/adapters/meridian.py
def get_channel_names(self) -> list[str]:
    """Get the channel names that would be used as the index in get_channel_roi results.

    For Meridian, this returns the media_channels which are the human-readable
    channel names used in the ROI results.

    Returns
        List of channel names

    """
    return self.media_channels
get_channel_roi(start_date: pd.Timestamp | None = None, end_date: pd.Timestamp | None = None) -> pd.Series

Get channel ROI estimates.

Parameters:

Name Type Description Default
start_date Timestamp | None

Optional start date for ROI calculation

None
end_date Timestamp | None

Optional end date for ROI calculation

None

Returns:

Type Description
Series

Series containing ROI estimates for each channel

Source code in mmm_eval/adapters/meridian.py
def get_channel_roi(
    self,
    start_date: pd.Timestamp | None = None,
    end_date: pd.Timestamp | None = None,
) -> pd.Series:
    """Get channel ROI estimates.

    Args:
        start_date: Optional start date for ROI calculation
        end_date: Optional end date for ROI calculation

    Returns:
        Series containing ROI estimates for each channel

    """
    if not self.is_fitted or self.training_data is None or self.analyzer is None:
        raise RuntimeError("Model must be fit before computing ROI")

    # restrict ROI calculation to a particular period if start/end date args are
    # passed
    training_date_index = pd.to_datetime(self.training_data.kpi.time)
    roi_date_index = training_date_index.copy()
    if start_date:
        roi_date_index = roi_date_index[roi_date_index >= start_date]
    if end_date:
        roi_date_index = roi_date_index[roi_date_index < end_date]

    selected_times = [bool(date) for date in training_date_index.isin(roi_date_index)]

    # analyzer.roi() returns a tensor of shape (n_chains, n_draws, n_channels)
    rois_per_channel = np.mean(self.analyzer.roi(selected_times=selected_times), axis=(0, 1))

    rois = {}
    for channel, roi in zip(self.media_channels, rois_per_channel, strict=False):
        rois[channel] = float(roi)
    return pd.Series(rois)
predict(data: pd.DataFrame | None = None) -> np.ndarray

Make predictions using the fitted model.

This returns predictions for the entirety of the dataset passed to fit() unless max_train_date is specified when calling fit(); in that case it only returns predictions for the time periods indicated by the holdout mask.

Note: Meridian doesn't require input data for prediction - it uses the fitted model state, so the data argument will be ignored if passed.

Parameters:

Name Type Description Default
data DataFrame | None

Ignored - Meridian uses the fitted model state for predictions.

None

Returns:

Type Description
ndarray

Predicted values

Raises:

Type Description
RuntimeError

If model is not fitted

Source code in mmm_eval/adapters/meridian.py
def predict(self, data: pd.DataFrame | None = None) -> np.ndarray:
    """Make predictions using the fitted model.

    This returns predictions for the entirety of the dataset passed to fit() unless
    `max_train_date` is specified when calling fit(); in that case it only returns
    predictions for the time periods indicated by the holdout mask.

    Note: Meridian doesn't require input data for prediction - it uses the fitted
    model state, so the `data` argument will be ignored if passed.

    Args:
        data: Ignored - Meridian uses the fitted model state for predictions.

    Returns:
        Predicted values

    Raises:
        RuntimeError: If model is not fitted

    """
    if not self.is_fitted or self.analyzer is None:
        raise RuntimeError("Model must be fit before prediction")

    # shape (n_chains, n_draws, n_times)
    preds_tensor = self.analyzer.expected_outcome(aggregate_geos=True, aggregate_times=False)
    posterior_mean = np.mean(preds_tensor, axis=(0, 1))

    # if holdout mask is provided, use it to mask the predictions to restrict only to the
    # holdout period
    if self.holdout_mask is not None:
        posterior_mean = posterior_mean[self.holdout_mask]

    return posterior_mean

PyMCAdapter(config: PyMCConfig)

Bases: BaseAdapter

Initialize the PyMCAdapter.

Parameters:

Name Type Description Default
config PyMCConfig

PyMCConfig object

required
Source code in mmm_eval/adapters/pymc.py
def __init__(self, config: PyMCConfig):
    """Initialize the PyMCAdapter.

    Args:
        config: PyMCConfig object

    """
    self.model_kwargs = config.pymc_model_config_dict
    self.fit_kwargs = config.fit_config_dict
    self.predict_kwargs = config.predict_config_dict
    self.date_column = config.date_column
    self.channel_spend_columns = config.channel_columns
    self.control_columns = config.control_columns
    self.model = None
    self.trace = None
    self._channel_roi_df = None
    self.is_fitted = False

    # Store original values to reset on subsequent fit calls
    self._original_channel_spend_columns = config.channel_columns.copy()
    self._original_model_kwargs = config.pymc_model_config_dict.copy()
Attributes
media_channels: list[str] property

Return the channel names used by this adapter.

For PyMC, this returns the channel_spend_columns which are used as the channel names in ROI results.

Returns List of channel names

primary_media_regressor_columns: list[str] property

Return the primary media regressor columns that should be perturbed in tests.

For PyMC, this is always the channel_spend_columns since the PyMC adapter uses spend as the primary regressor in the model.

Returns List of channel spend column names

primary_media_regressor_type: PrimaryMediaRegressor property

Return the type of primary media regressors used by this adapter.

For PyMC, this is always SPEND since the PyMC adapter uses spend as the primary regressor.

Returns PrimaryMediaRegressor.SPEND

Functions
fit(data: pd.DataFrame) -> None

Fit the model and compute ROIs.

Parameters:

Name Type Description Default
data DataFrame

DataFrame containing the training data adhering to the PyMCInputDataSchema.

required
Source code in mmm_eval/adapters/pymc.py
def fit(self, data: pd.DataFrame) -> None:
    """Fit the model and compute ROIs.

    Args:
        data: DataFrame containing the training data adhering to the PyMCInputDataSchema.

    """
    # Reset to original values at the start of each fit call
    self.channel_spend_columns = self._original_channel_spend_columns.copy()
    self.model_kwargs = self._original_model_kwargs.copy()

    # Identify channel spend columns that sum to zero and remove them from modelling.
    # We cannot reliably make any prediction based on these channels when making
    # predictions on new data.
    channel_spend_data = data[self.channel_spend_columns]
    zero_spend_channels = channel_spend_data.columns[channel_spend_data.sum() == 0].tolist()

    if zero_spend_channels:
        logger.info(f"Dropping channels with zero spend: {zero_spend_channels}")
        # Remove zero-spend channels from the list passed to the MMM constructor
        self.channel_spend_columns = [col for col in self.channel_spend_columns if col not in zero_spend_channels]
        # also update the model config field to reflect the new channel spend columns
        self.model_kwargs["channel_columns"] = self.channel_spend_columns

        # Check for vector priors that might cause shape mismatches
        _check_vector_priors_when_dropping_channels(self.model_kwargs["model_config"], zero_spend_channels)

        data = data.drop(columns=zero_spend_channels)

    X = data.drop(columns=[InputDataframeConstants.RESPONSE_COL, InputDataframeConstants.MEDIA_CHANNEL_REVENUE_COL])
    y = data[InputDataframeConstants.RESPONSE_COL]

    self.model = MMM(**self.model_kwargs)
    self.trace = self.model.fit(X=X, y=y, **self.fit_kwargs)

    self._channel_roi_df = self._compute_channel_contributions(data)
    self.is_fitted = True
fit_and_predict(train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray

Fit on training data and make predictions on test data.

Parameters:

Name Type Description Default
train DataFrame

training dataset

required
test DataFrame

test dataset

required

Returns:

Type Description
ndarray

model predictions.

Source code in mmm_eval/adapters/pymc.py
def fit_and_predict(self, train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray:
    """Fit on training data and make predictions on test data.

    Arguments:
        train: training dataset
        test: test dataset

    Returns:
        model predictions.

    """
    self.fit(train)
    return self.predict(test)
get_channel_names() -> list[str]

Get the channel names that would be used as the index in get_channel_roi results.

For PyMC, this returns the channel_spend_columns which are used as the index in the ROI results.

Returns List of channel names

Source code in mmm_eval/adapters/pymc.py
def get_channel_names(self) -> list[str]:
    """Get the channel names that would be used as the index in get_channel_roi results.

    For PyMC, this returns the `channel_spend_columns` which are used as the index
    in the ROI results.

    Returns
        List of channel names

    """
    return self.channel_spend_columns
get_channel_roi(start_date: pd.Timestamp | None = None, end_date: pd.Timestamp | None = None) -> pd.Series

Return the ROIs for each channel, optionally within a given date range.

Parameters:

Name Type Description Default
start_date Timestamp | None

Optional start date for ROI calculation

None
end_date Timestamp | None

Optional end date for ROI calculation

None

Returns:

Type Description
Series

Series containing ROI estimates for each channel

Source code in mmm_eval/adapters/pymc.py
def get_channel_roi(
    self,
    start_date: pd.Timestamp | None = None,
    end_date: pd.Timestamp | None = None,
) -> pd.Series:
    """Return the ROIs for each channel, optionally within a given date range.

    Args:
        start_date: Optional start date for ROI calculation
        end_date: Optional end date for ROI calculation

    Returns:
        Series containing ROI estimates for each channel

    """
    if not self.is_fitted or self._channel_roi_df is None:
        raise RuntimeError("Model must be fit before computing ROI.")

    _validate_start_end_dates(start_date, end_date, pd.DatetimeIndex(self._channel_roi_df.index))

    # Filter the contribution DataFrame by date range
    date_range_df = self._channel_roi_df.loc[start_date:end_date]

    if date_range_df.empty:
        raise ValueError(f"No data found for date range {start_date} to {end_date}")

    return pd.Series(self._calculate_rois(date_range_df))
predict(data: pd.DataFrame | None = None) -> np.ndarray

Predict the response variable for new data.

Parameters:

Name Type Description Default
data DataFrame | None

Input data for prediction. This parameter is required for PyMC predictions and cannot be None.

None

Returns:

Type Description
ndarray

Predicted values

Raises:

Type Description
RuntimeError

If model is not fitted

ValueError

If data is None (PyMC requires data for prediction)

Source code in mmm_eval/adapters/pymc.py
def predict(self, data: pd.DataFrame | None = None) -> np.ndarray:
    """Predict the response variable for new data.

    Args:
        data: Input data for prediction. This parameter is required for PyMC
            predictions and cannot be None.

    Returns:
        Predicted values

    Raises:
        RuntimeError: If model is not fitted
        ValueError: If data is None (PyMC requires data for prediction)

    """
    if not self.is_fitted or self.model is None:
        raise RuntimeError("Model must be fit before prediction.")

    if data is None:
        raise ValueError("PyMC adapter requires data for prediction")

    if InputDataframeConstants.RESPONSE_COL in data.columns:
        data = data.drop(columns=[InputDataframeConstants.RESPONSE_COL])
    predictions = predictions = self.model.predict(
        data, extend_idata=False, include_last_observations=True, **self.predict_kwargs
    )
    return predictions

Functions

get_adapter(framework: str, config: PyMCConfig | MeridianConfig)

Get an adapter instance for the specified framework.

Parameters:

Name Type Description Default
framework str

Name of the MMM framework

required
config PyMCConfig | MeridianConfig

Framework-specific configuration

required

Returns:

Type Description

Adapter instance

Raises:

Type Description
ValueError

If framework is not supported

Source code in mmm_eval/adapters/__init__.py
def get_adapter(framework: str, config: PyMCConfig | MeridianConfig):
    """Get an adapter instance for the specified framework.

    Args:
        framework: Name of the MMM framework
        config: Framework-specific configuration

    Returns:
        Adapter instance

    Raises:
        ValueError: If framework is not supported

    """
    if framework not in ADAPTER_REGISTRY:
        raise ValueError(f"Unsupported framework: {framework}. Available: {list(ADAPTER_REGISTRY.keys())}")

    adapter_class = ADAPTER_REGISTRY[framework]
    return adapter_class(config)

Modules

base

Base adapter class for MMM frameworks.

Classes
BaseAdapter(config: dict[str, Any] | None = None)

Bases: ABC

Base class for MMM framework adapters.

Initialize the base adapter.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Configuration dictionary

None
Source code in mmm_eval/adapters/base.py
def __init__(self, config: dict[str, Any] | None = None):
    """Initialize the base adapter.

    Args:
        config: Configuration dictionary

    """
    self.config = config or {}
    self.is_fitted = False
    self.channel_spend_columns: list[str] = []
    self.date_column: str
Attributes
media_channels: list[str] abstractmethod property

Return the channel names used by this adapter.

This property provides a consistent way to get channel names across different adapters. For most frameworks, this will be human-readable channel names, but for PyMC it may be the column names themselves.

Returns List of channel names used by this adapter

primary_media_regressor_columns: list[str] abstractmethod property

Return the primary media regressor columns that should be perturbed in tests.

This property returns the columns that are actually used as regressors in the model. For most frameworks, this will be the spend columns, but for e.g. Meridian it could be impressions or reach/frequency columns depending on the configuration.

Returns List of column names that are used as primary media regressors in the model

primary_media_regressor_type: PrimaryMediaRegressor abstractmethod property

Return the type of primary media regressors used by this adapter.

This property indicates what type of regressors are used as primary inputs to the model, which determines what should be perturbed in tests.

Returns PrimaryMediaRegressor enum value indicating the type of primary media regressors

Functions
fit(data: pd.DataFrame) -> None abstractmethod

Fit the model to the data.

Parameters:

Name Type Description Default
data DataFrame

Training data

required
Source code in mmm_eval/adapters/base.py
@abstractmethod
def fit(self, data: pd.DataFrame) -> None:
    """Fit the model to the data.

    Args:
        data: Training data

    """
    pass
fit_and_predict(train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray abstractmethod

Fit on training data and make predictions on test data.

Parameters:

Name Type Description Default
train DataFrame

dataset to train model on

required
test DataFrame

dataset to make predictions using

required

Returns:

Type Description
ndarray

Predicted values.

Source code in mmm_eval/adapters/base.py
@abstractmethod
def fit_and_predict(self, train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray:
    """Fit on training data and make predictions on test data.

    Args:
        train: dataset to train model on
        test: dataset to make predictions using

    Returns:
        Predicted values.

    """
    pass
get_channel_names() -> list[str] abstractmethod

Get the channel names that would be used as the index in channel ROI results.

This method provides a consistent way to get channel names across different adapters without needing to call get_channel_roi() (which requires the model to be fitted).

Returns List of channel names that would be used as the index in get_channel_roi results

Source code in mmm_eval/adapters/base.py
@abstractmethod
def get_channel_names(self) -> list[str]:  # pyright: ignore[reportReturnType]
    """Get the channel names that would be used as the index in channel ROI results.

    This method provides a consistent way to get channel names across different adapters
    without needing to call get_channel_roi() (which requires the model to be fitted).

    Returns
        List of channel names that would be used as the index in get_channel_roi results

    """
    pass
get_channel_roi(start_date: pd.Timestamp | None = None, end_date: pd.Timestamp | None = None) -> pd.Series abstractmethod

Get channel ROI estimates.

Parameters:

Name Type Description Default
start_date Timestamp | None

Optional start date for ROI calculation

None
end_date Timestamp | None

Optional end date for ROI calculation

None

Returns:

Type Description
Series

Series containing ROI estimates for each channel

Source code in mmm_eval/adapters/base.py
@abstractmethod
def get_channel_roi(
    self,
    start_date: pd.Timestamp | None = None,
    end_date: pd.Timestamp | None = None,
) -> pd.Series:
    """Get channel ROI estimates.

    Args:
        start_date: Optional start date for ROI calculation
        end_date: Optional end date for ROI calculation

    Returns:
        Series containing ROI estimates for each channel

    """
    pass
predict(data: pd.DataFrame | None = None) -> np.ndarray abstractmethod

Make predictions on new data.

Parameters:

Name Type Description Default
data DataFrame | None

Input data for prediction. Behavior varies by adapter: - Some adapters (e.g., PyMC) require this parameter and will raise an error if None is passed - Other adapters (e.g., Meridian) ignore this parameter and use the fitted model state instead

None

Returns:

Type Description
ndarray

Predicted values

Source code in mmm_eval/adapters/base.py
@abstractmethod
def predict(self, data: pd.DataFrame | None = None) -> np.ndarray:
    """Make predictions on new data.

    Args:
        data: Input data for prediction. Behavior varies by adapter:
            - Some adapters (e.g., PyMC) require this parameter and will raise
              an error if None is passed
            - Other adapters (e.g., Meridian) ignore this parameter and use
              the fitted model state instead

    Returns:
        Predicted values

    """
    pass
PrimaryMediaRegressor

Bases: StrEnum

Enum for primary media regressor types used in MMM frameworks.

meridian

Google Meridian adapter for MMM evaluation.

Classes
MeridianAdapter(config: MeridianConfig)

Bases: BaseAdapter

Adapter for Google Meridian MMM framework.

Initialize the Meridian adapter.

Parameters:

Name Type Description Default
config MeridianConfig

MeridianConfig object

required
Source code in mmm_eval/adapters/meridian.py
def __init__(self, config: MeridianConfig):
    """Initialize the Meridian adapter.

    Args:
        config: MeridianConfig object

    """
    self.config = config
    self.input_data_builder_schema = config.input_data_builder_config

    self.date_column = config.date_column
    self.channel_spend_columns = self.input_data_builder_schema.channel_spend_columns

    # Initialize stateful attributes to None/False
    self._reset_state()
Attributes
media_channels: list[str] property

Return the channel names used by this adapter.

For Meridian, this returns the human-readable channel names from the config.

Returns List of channel names

primary_media_regressor_columns: list[str] property

Return the primary media regressor columns that should be perturbed in tests.

For Meridian, this depends on the configuration: - If channel_reach_columns is provided: returns empty list (not supported in perturbation tests) - If channel_impressions_columns is provided: returns channel_impressions_columns - Otherwise: returns channel_spend_columns

Returns List of column names that are used as primary media regressors in the model

primary_media_regressor_type: PrimaryMediaRegressor property

Return the type of primary media regressors used by the model.

For Meridian, this is determined by the configuration: - If channel_reach_columns is provided: returns PrimaryMediaRegressor.REACH_AND_FREQUENCY - If channel_impressions_columns is provided: returns PrimaryMediaRegressor.IMPRESSIONS - Otherwise: returns PrimaryMediaRegressor.SPEND

Returns PrimaryMediaRegressor enum value

Functions
fit(data: pd.DataFrame, max_train_date: pd.Timestamp | None = None) -> None

Fit the Meridian model to data.

Parameters:

Name Type Description Default
data DataFrame

Training data

required
max_train_date Timestamp | None

Optional maximum training date for holdout validation

None
Source code in mmm_eval/adapters/meridian.py
def fit(self, data: pd.DataFrame, max_train_date: pd.Timestamp | None = None) -> None:
    """Fit the Meridian model to data.

    Args:
        data: Training data
        max_train_date: Optional maximum training date for holdout validation

    """
    # Reset state to ensure clean start when new data is provided
    self._reset_state()

    # build Meridian data object
    self.training_data = construct_meridian_data_object(data, self.config)
    self.max_train_date = max_train_date

    model_spec_kwargs = dict(self.config.model_spec_config)

    # if max train date is provided, construct a mask that is True for all dates before max_train_date
    if self.max_train_date:
        self.holdout_mask = construct_holdout_mask(self.max_train_date, self.training_data.kpi.time)
        # model expects a 2D array of shape (n_geos, n_times) so have to duplicate the values across each geo
        model_spec_kwargs["holdout_id"] = np.repeat(
            self.holdout_mask[None, :], repeats=len(self.training_data.kpi.geo), axis=0
        )

    # Create and fit the Meridian model
    model_spec = ModelSpec(**model_spec_kwargs)
    self.model = Meridian(
        input_data=self.training_data,  # type: ignore
        model_spec=model_spec,
    )
    self.trace = self.model.sample_posterior(**dict(self.config.sample_posterior_config))

    # used to compute channel contributions for ROI calculations
    self.analyzer = Analyzer(self.model)
    self.is_fitted = True
fit_and_predict(train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray

Fit the Meridian model and make predictions given new input data.

The full dataset must be passed to fit(), since making out-of-sample predictions is only possible by way of specifying a holdout mask when sampling from the posterior.

Parameters:

Name Type Description Default
train DataFrame

Training data

required
test DataFrame

Test data

required

Returns:

Type Description
ndarray

Predicted values for the test period

Source code in mmm_eval/adapters/meridian.py
def fit_and_predict(self, train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray:
    """Fit the Meridian model and make predictions given new input data.

    The full dataset must be passed to `fit()`, since making out-of-sample predictions
    is only possible by way of specifying a holdout mask when sampling from the
    posterior.

    Args:
        train: Training data
        test: Test data

    Returns:
        Predicted values for the test period

    """
    train_and_test = pd.concat([train, test])
    max_train_date = train[self.date_column].squeeze().max()
    self.fit(train_and_test, max_train_date=max_train_date)
    return self.predict()
get_channel_names() -> list[str]

Get the channel names that would be used as the index in get_channel_roi results.

For Meridian, this returns the media_channels which are the human-readable channel names used in the ROI results.

Returns List of channel names

Source code in mmm_eval/adapters/meridian.py
def get_channel_names(self) -> list[str]:
    """Get the channel names that would be used as the index in get_channel_roi results.

    For Meridian, this returns the media_channels which are the human-readable
    channel names used in the ROI results.

    Returns
        List of channel names

    """
    return self.media_channels
get_channel_roi(start_date: pd.Timestamp | None = None, end_date: pd.Timestamp | None = None) -> pd.Series

Get channel ROI estimates.

Parameters:

Name Type Description Default
start_date Timestamp | None

Optional start date for ROI calculation

None
end_date Timestamp | None

Optional end date for ROI calculation

None

Returns:

Type Description
Series

Series containing ROI estimates for each channel

Source code in mmm_eval/adapters/meridian.py
def get_channel_roi(
    self,
    start_date: pd.Timestamp | None = None,
    end_date: pd.Timestamp | None = None,
) -> pd.Series:
    """Get channel ROI estimates.

    Args:
        start_date: Optional start date for ROI calculation
        end_date: Optional end date for ROI calculation

    Returns:
        Series containing ROI estimates for each channel

    """
    if not self.is_fitted or self.training_data is None or self.analyzer is None:
        raise RuntimeError("Model must be fit before computing ROI")

    # restrict ROI calculation to a particular period if start/end date args are
    # passed
    training_date_index = pd.to_datetime(self.training_data.kpi.time)
    roi_date_index = training_date_index.copy()
    if start_date:
        roi_date_index = roi_date_index[roi_date_index >= start_date]
    if end_date:
        roi_date_index = roi_date_index[roi_date_index < end_date]

    selected_times = [bool(date) for date in training_date_index.isin(roi_date_index)]

    # analyzer.roi() returns a tensor of shape (n_chains, n_draws, n_channels)
    rois_per_channel = np.mean(self.analyzer.roi(selected_times=selected_times), axis=(0, 1))

    rois = {}
    for channel, roi in zip(self.media_channels, rois_per_channel, strict=False):
        rois[channel] = float(roi)
    return pd.Series(rois)
predict(data: pd.DataFrame | None = None) -> np.ndarray

Make predictions using the fitted model.

This returns predictions for the entirety of the dataset passed to fit() unless max_train_date is specified when calling fit(); in that case it only returns predictions for the time periods indicated by the holdout mask.

Note: Meridian doesn't require input data for prediction - it uses the fitted model state, so the data argument will be ignored if passed.

Parameters:

Name Type Description Default
data DataFrame | None

Ignored - Meridian uses the fitted model state for predictions.

None

Returns:

Type Description
ndarray

Predicted values

Raises:

Type Description
RuntimeError

If model is not fitted

Source code in mmm_eval/adapters/meridian.py
def predict(self, data: pd.DataFrame | None = None) -> np.ndarray:
    """Make predictions using the fitted model.

    This returns predictions for the entirety of the dataset passed to fit() unless
    `max_train_date` is specified when calling fit(); in that case it only returns
    predictions for the time periods indicated by the holdout mask.

    Note: Meridian doesn't require input data for prediction - it uses the fitted
    model state, so the `data` argument will be ignored if passed.

    Args:
        data: Ignored - Meridian uses the fitted model state for predictions.

    Returns:
        Predicted values

    Raises:
        RuntimeError: If model is not fitted

    """
    if not self.is_fitted or self.analyzer is None:
        raise RuntimeError("Model must be fit before prediction")

    # shape (n_chains, n_draws, n_times)
    preds_tensor = self.analyzer.expected_outcome(aggregate_geos=True, aggregate_times=False)
    posterior_mean = np.mean(preds_tensor, axis=(0, 1))

    # if holdout mask is provided, use it to mask the predictions to restrict only to the
    # holdout period
    if self.holdout_mask is not None:
        posterior_mean = posterior_mean[self.holdout_mask]

    return posterior_mean
Functions
construct_holdout_mask(max_train_date: pd.Timestamp, time_index: np.ndarray) -> np.ndarray

Construct a boolean mask for holdout period identification.

This function creates a boolean mask that identifies which time periods fall into the holdout/test period (after the maximum training date). The mask can be used to separate training and test data or to filter predictions to only the holdout period.

Parameters:

Name Type Description Default
max_train_date Timestamp

The maximum date to be considered part of the training period. All dates after this will be marked as holdout/test data.

required
time_index ndarray

Array-like object containing the time index to be masked. Can be any format that pandas.to_datetime can convert.

required

Returns:

Type Description
ndarray

A boolean array of the same length as time_index, where True indicates

ndarray

the time period is in the holdout/test set (after max_train_date).

Example

time_index = pd.date_range('2023-01-01', '2023-12-31', freq='D') max_train_date = pd.Timestamp('2023-06-30') mask = construct_holdout_mask(max_train_date, time_index)

mask will be True for dates from 2023-07-01 onwards
Source code in mmm_eval/adapters/meridian.py
def construct_holdout_mask(max_train_date: pd.Timestamp, time_index: np.ndarray) -> np.ndarray:
    """Construct a boolean mask for holdout period identification.

    This function creates a boolean mask that identifies which time periods fall into
    the holdout/test period (after the maximum training date). The mask can be used
    to separate training and test data or to filter predictions to only the holdout period.

    Args:
        max_train_date: The maximum date to be considered part of the training period.
            All dates after this will be marked as holdout/test data.
        time_index: Array-like object containing the time index to be masked.
            Can be any format that pandas.to_datetime can convert.

    Returns:
        A boolean array of the same length as time_index, where True indicates
        the time period is in the holdout/test set (after max_train_date).

    Example:
        >>> time_index = pd.date_range('2023-01-01', '2023-12-31', freq='D')
        >>> max_train_date = pd.Timestamp('2023-06-30')
        >>> mask = construct_holdout_mask(max_train_date, time_index)
        >>> # mask will be True for dates from 2023-07-01 onwards

    """
    full_index = pd.to_datetime(time_index)
    test_index = full_index[full_index > max_train_date]

    return full_index.isin(test_index)
construct_meridian_data_object(df: pd.DataFrame, config: MeridianConfig) -> pd.DataFrame

Construct a Meridian data object from a pandas DataFrame.

This function transforms a standard DataFrame into the format required by the Meridian framework. It handles the conversion of revenue to revenue_per_kpi, and configures the data builder with various components including KPI, population, controls, media channels (with different types: reach/frequency, impressions, or spend-only), organic media, and non-media treatments.

Parameters:

Name Type Description Default
df DataFrame

Input DataFrame containing the raw data with columns for dates, response, revenue, media spend, and other variables as specified in the config.

required
config MeridianConfig

MeridianConfig object containing the configuration for data transformation including column mappings and feature specifications.

required

Returns:

Type Description
DataFrame

A Meridian data object built from the input DataFrame according to the config.

Note

The function modifies the input DataFrame by: - Converting revenue to revenue_per_kpi (revenue / response) - Dropping the original revenue column - Adding various media and control components based on config - Validating that media channels have sufficient variation (non-zero spend)

Raises:

Type Description
ValueError

If a media channel has zero spend across all time periods and geos, as this would cause Meridian to fail or produce unreliable results.

Source code in mmm_eval/adapters/meridian.py
def construct_meridian_data_object(df: pd.DataFrame, config: MeridianConfig) -> pd.DataFrame:
    """Construct a Meridian data object from a pandas DataFrame.

    This function transforms a standard DataFrame into the format required by the Meridian
    framework. It handles the conversion of revenue to revenue_per_kpi, and configures
    the data builder with various components including KPI, population, controls,
    media channels (with different types: reach/frequency, impressions, or spend-only),
    organic media, and non-media treatments.

    Args:
        df: Input DataFrame containing the raw data with columns for dates, response,
            revenue, media spend, and other variables as specified in the config.
        config: MeridianConfig object containing the configuration for data transformation
            including column mappings and feature specifications.

    Returns:
        A Meridian data object built from the input DataFrame according to the config.

    Note:
        The function modifies the input DataFrame by:
        - Converting revenue to revenue_per_kpi (revenue / response)
        - Dropping the original revenue column
        - Adding various media and control components based on config
        - Validating that media channels have sufficient variation (non-zero spend)

    Raises:
        ValueError: If a media channel has zero spend across all time periods and geos,
                   as this would cause Meridian to fail or produce unreliable results.

    """
    df = df.copy()

    # Validate media channels have sufficient variation
    _validate_media_channels(df, config)

    # convert from "revenue" to "revenue_per_kpi"
    df[REVENUE_PER_KPI_COL] = (
        df[InputDataframeConstants.MEDIA_CHANNEL_REVENUE_COL] / df[InputDataframeConstants.RESPONSE_COL]
    )
    df = df.drop(columns=InputDataframeConstants.MEDIA_CHANNEL_REVENUE_COL)

    input_data_builder_schema = config.input_data_builder_config

    # KPI, population, and control variables
    builder = (
        data_builder.DataFrameInputDataBuilder(kpi_type="non_revenue")
        .with_kpi(df, time_col=config.date_column, kpi_col=InputDataframeConstants.RESPONSE_COL)
        .with_revenue_per_kpi(df, time_col=config.date_column, revenue_per_kpi_col=REVENUE_PER_KPI_COL)
    )
    # population, if provided, needs to be called "population" in the DF
    if "population" in df.columns:
        builder = builder.with_population(df)

    # controls (non-intervenable, e.g. macroeconomics)
    if input_data_builder_schema.control_columns:
        builder = builder.with_controls(
            df, time_col=config.date_column, control_cols=input_data_builder_schema.control_columns
        )

    # paid media
    builder = _add_media_to_data_builder(df, builder, input_data_builder_schema, config.date_column)

    # organic media
    if input_data_builder_schema.organic_media_columns:
        builder = builder.with_organic_media(
            df,
            organic_media_cols=input_data_builder_schema.organic_media_columns,
            organic_media_channels=input_data_builder_schema.organic_media_channels,
            media_time_col=config.date_column,
        )

    # non-media treatments (anything that is "intervenable", e.g. pricing/promotions)
    if input_data_builder_schema.non_media_treatment_columns:
        builder = builder.with_non_media_treatments(
            df,
            non_media_treatment_cols=input_data_builder_schema.non_media_treatment_columns,
            time_col=config.date_column,
        )

    return builder.build()

pymc

PyMC MMM framework adapter.

N.B. we expect control variables to be scaled to [-1, 1] using maxabs scaling BEFORE being passed to the PyMCAdapter.

Classes
PyMCAdapter(config: PyMCConfig)

Bases: BaseAdapter

Initialize the PyMCAdapter.

Parameters:

Name Type Description Default
config PyMCConfig

PyMCConfig object

required
Source code in mmm_eval/adapters/pymc.py
def __init__(self, config: PyMCConfig):
    """Initialize the PyMCAdapter.

    Args:
        config: PyMCConfig object

    """
    self.model_kwargs = config.pymc_model_config_dict
    self.fit_kwargs = config.fit_config_dict
    self.predict_kwargs = config.predict_config_dict
    self.date_column = config.date_column
    self.channel_spend_columns = config.channel_columns
    self.control_columns = config.control_columns
    self.model = None
    self.trace = None
    self._channel_roi_df = None
    self.is_fitted = False

    # Store original values to reset on subsequent fit calls
    self._original_channel_spend_columns = config.channel_columns.copy()
    self._original_model_kwargs = config.pymc_model_config_dict.copy()
Attributes
media_channels: list[str] property

Return the channel names used by this adapter.

For PyMC, this returns the channel_spend_columns which are used as the channel names in ROI results.

Returns List of channel names

primary_media_regressor_columns: list[str] property

Return the primary media regressor columns that should be perturbed in tests.

For PyMC, this is always the channel_spend_columns since the PyMC adapter uses spend as the primary regressor in the model.

Returns List of channel spend column names

primary_media_regressor_type: PrimaryMediaRegressor property

Return the type of primary media regressors used by this adapter.

For PyMC, this is always SPEND since the PyMC adapter uses spend as the primary regressor.

Returns PrimaryMediaRegressor.SPEND

Functions
fit(data: pd.DataFrame) -> None

Fit the model and compute ROIs.

Parameters:

Name Type Description Default
data DataFrame

DataFrame containing the training data adhering to the PyMCInputDataSchema.

required
Source code in mmm_eval/adapters/pymc.py
def fit(self, data: pd.DataFrame) -> None:
    """Fit the model and compute ROIs.

    Args:
        data: DataFrame containing the training data adhering to the PyMCInputDataSchema.

    """
    # Reset to original values at the start of each fit call
    self.channel_spend_columns = self._original_channel_spend_columns.copy()
    self.model_kwargs = self._original_model_kwargs.copy()

    # Identify channel spend columns that sum to zero and remove them from modelling.
    # We cannot reliably make any prediction based on these channels when making
    # predictions on new data.
    channel_spend_data = data[self.channel_spend_columns]
    zero_spend_channels = channel_spend_data.columns[channel_spend_data.sum() == 0].tolist()

    if zero_spend_channels:
        logger.info(f"Dropping channels with zero spend: {zero_spend_channels}")
        # Remove zero-spend channels from the list passed to the MMM constructor
        self.channel_spend_columns = [col for col in self.channel_spend_columns if col not in zero_spend_channels]
        # also update the model config field to reflect the new channel spend columns
        self.model_kwargs["channel_columns"] = self.channel_spend_columns

        # Check for vector priors that might cause shape mismatches
        _check_vector_priors_when_dropping_channels(self.model_kwargs["model_config"], zero_spend_channels)

        data = data.drop(columns=zero_spend_channels)

    X = data.drop(columns=[InputDataframeConstants.RESPONSE_COL, InputDataframeConstants.MEDIA_CHANNEL_REVENUE_COL])
    y = data[InputDataframeConstants.RESPONSE_COL]

    self.model = MMM(**self.model_kwargs)
    self.trace = self.model.fit(X=X, y=y, **self.fit_kwargs)

    self._channel_roi_df = self._compute_channel_contributions(data)
    self.is_fitted = True
fit_and_predict(train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray

Fit on training data and make predictions on test data.

Parameters:

Name Type Description Default
train DataFrame

training dataset

required
test DataFrame

test dataset

required

Returns:

Type Description
ndarray

model predictions.

Source code in mmm_eval/adapters/pymc.py
def fit_and_predict(self, train: pd.DataFrame, test: pd.DataFrame) -> np.ndarray:
    """Fit on training data and make predictions on test data.

    Arguments:
        train: training dataset
        test: test dataset

    Returns:
        model predictions.

    """
    self.fit(train)
    return self.predict(test)
get_channel_names() -> list[str]

Get the channel names that would be used as the index in get_channel_roi results.

For PyMC, this returns the channel_spend_columns which are used as the index in the ROI results.

Returns List of channel names

Source code in mmm_eval/adapters/pymc.py
def get_channel_names(self) -> list[str]:
    """Get the channel names that would be used as the index in get_channel_roi results.

    For PyMC, this returns the `channel_spend_columns` which are used as the index
    in the ROI results.

    Returns
        List of channel names

    """
    return self.channel_spend_columns
get_channel_roi(start_date: pd.Timestamp | None = None, end_date: pd.Timestamp | None = None) -> pd.Series

Return the ROIs for each channel, optionally within a given date range.

Parameters:

Name Type Description Default
start_date Timestamp | None

Optional start date for ROI calculation

None
end_date Timestamp | None

Optional end date for ROI calculation

None

Returns:

Type Description
Series

Series containing ROI estimates for each channel

Source code in mmm_eval/adapters/pymc.py
def get_channel_roi(
    self,
    start_date: pd.Timestamp | None = None,
    end_date: pd.Timestamp | None = None,
) -> pd.Series:
    """Return the ROIs for each channel, optionally within a given date range.

    Args:
        start_date: Optional start date for ROI calculation
        end_date: Optional end date for ROI calculation

    Returns:
        Series containing ROI estimates for each channel

    """
    if not self.is_fitted or self._channel_roi_df is None:
        raise RuntimeError("Model must be fit before computing ROI.")

    _validate_start_end_dates(start_date, end_date, pd.DatetimeIndex(self._channel_roi_df.index))

    # Filter the contribution DataFrame by date range
    date_range_df = self._channel_roi_df.loc[start_date:end_date]

    if date_range_df.empty:
        raise ValueError(f"No data found for date range {start_date} to {end_date}")

    return pd.Series(self._calculate_rois(date_range_df))
predict(data: pd.DataFrame | None = None) -> np.ndarray

Predict the response variable for new data.

Parameters:

Name Type Description Default
data DataFrame | None

Input data for prediction. This parameter is required for PyMC predictions and cannot be None.

None

Returns:

Type Description
ndarray

Predicted values

Raises:

Type Description
RuntimeError

If model is not fitted

ValueError

If data is None (PyMC requires data for prediction)

Source code in mmm_eval/adapters/pymc.py
def predict(self, data: pd.DataFrame | None = None) -> np.ndarray:
    """Predict the response variable for new data.

    Args:
        data: Input data for prediction. This parameter is required for PyMC
            predictions and cannot be None.

    Returns:
        Predicted values

    Raises:
        RuntimeError: If model is not fitted
        ValueError: If data is None (PyMC requires data for prediction)

    """
    if not self.is_fitted or self.model is None:
        raise RuntimeError("Model must be fit before prediction.")

    if data is None:
        raise ValueError("PyMC adapter requires data for prediction")

    if InputDataframeConstants.RESPONSE_COL in data.columns:
        data = data.drop(columns=[InputDataframeConstants.RESPONSE_COL])
    predictions = predictions = self.model.predict(
        data, extend_idata=False, include_last_observations=True, **self.predict_kwargs
    )
    return predictions

schemas

Classes
MeridianInputDataBuilderSchema

Bases: BaseModel

Schema for Meridian input data builder configuration.

These arguments are passed to the DataFrameInputDataBuilder class for constructing a data object to be fed into the Meridian model.

See here for how to determine whether to consider a particular feature a non-media treatment or a control: https://developers.google.com/meridian/docs/advanced-modeling/organic-and-non-media-variables?hl=en

Functions
validate_media_channels(v)

Validate media columns are not empty.

Parameters:

Name Type Description Default
v

Media columns value

required

Returns:

Type Description

Validated value

Raises:

Type Description
ValueError

If media columns is empty

Source code in mmm_eval/adapters/schemas.py
@field_validator("media_channels")
def validate_media_channels(cls, v):
    """Validate media columns are not empty.

    Args:
        v: Media columns value

    Returns:
        Validated value

    Raises:
        ValueError: If media columns is empty

    """
    if v is not None and not v:
        raise ValueError("media_channels must not be empty")
    return v
validate_organic_media_fields(v, info)

Validate that exactly zero or two of organic_media_columns and organic_media_channels are provided.

Parameters:

Name Type Description Default
v

The value being validated

required
info

Validation info containing the field name

required

Returns:

Type Description

Validated value

Raises:

Type Description
ValueError

If exactly zero or two of the fields are not provided

Source code in mmm_eval/adapters/schemas.py
@field_validator("organic_media_columns", "organic_media_channels", mode="after")
def validate_organic_media_fields(cls, v, info):
    """Validate that exactly zero or two of organic_media_columns and organic_media_channels are provided.

    Args:
        v: The value being validated
        info: Validation info containing the field name

    Returns:
        Validated value

    Raises:
        ValueError: If exactly zero or two of the fields are not provided

    """
    # Get the current values of both fields
    organic_columns = getattr(info.data, "organic_media_columns", None)
    organic_channels = getattr(info.data, "organic_media_channels", None)

    # Count how many are provided (not None and not empty)
    provided_count = sum(1 for field in [organic_columns, organic_channels] if field is not None and len(field) > 0)

    if provided_count not in [0, 2]:
        raise ValueError("Exactly zero or two of organic_media_columns and organic_media_channels must be provided")

    return v
validate_reach_frequency_columns(v, info)

Validate that exactly zero or two of channel_reach_columns and channel_frequency_columns are provided.

Parameters:

Name Type Description Default
v

The value being validated

required
info

Validation info containing the field name

required

Returns:

Type Description

Validated value

Raises:

Type Description
ValueError

If exactly zero or two of the fields are not provided

Source code in mmm_eval/adapters/schemas.py
@field_validator("channel_reach_columns", "channel_frequency_columns", mode="after")
def validate_reach_frequency_columns(cls, v, info):
    """Validate that exactly zero or two of channel_reach_columns and channel_frequency_columns are provided.

    Args:
        v: The value being validated
        info: Validation info containing the field name

    Returns:
        Validated value

    Raises:
        ValueError: If exactly zero or two of the fields are not provided

    """
    # Get the current values of both fields
    reach_columns = getattr(info.data, "channel_reach_columns", None)
    frequency_columns = getattr(info.data, "channel_frequency_columns", None)

    # Count how many are provided (not None and not empty)
    provided_count = sum(1 for field in [reach_columns, frequency_columns] if field is not None and len(field) > 0)

    if provided_count not in [0, 2]:
        raise ValueError(
            "Exactly zero or two of channel_reach_columns and channel_frequency_columns must be provided"
        )

    return v
validate_reach_impressions_mutual_exclusion()

Validate that channel_reach_columns and channel_impressions_columns are not both provided.

Returns Self

Raises ValueError: If both fields are provided

Source code in mmm_eval/adapters/schemas.py
@model_validator(mode="after")
def validate_reach_impressions_mutual_exclusion(self):
    """Validate that channel_reach_columns and channel_impressions_columns are not both provided.

    Returns
        Self

    Raises
        ValueError: If both fields are provided

    """
    reach_columns = self.channel_reach_columns
    impressions_columns = self.channel_impressions_columns

    if (
        reach_columns is not None
        and len(reach_columns) > 0
        and impressions_columns is not None
        and len(impressions_columns) > 0
    ):
        raise ValueError("channel_reach_columns and channel_impressions_columns cannot both be provided")

    return self
MeridianModelSpecSchema

Bases: BaseModel

Schema for Meridian ModelSpec configuration.

MeridianSamplePosteriorSchema

Bases: BaseModel

Schema for Meridian sample_posterior configuration.

These arguments are passed to the Meridian model's sample_posterior() method.

Attributes
fit_config_dict_without_non_provided_fields: dict[str, Any] property

Return only non-None values.

Returns Dictionary of non-None values

MeridianStringConfigSchema

Bases: BaseModel

Schema for Meridian evaluation config dictionary.

PyMCFitSchema

Bases: BaseModel

Schema for PyMC Fit Configuration.

Defaults are all set to None so that the user can provide only the values they want to change. If a user does not provide a value, we will let the latest PYMC defaults be used in model instantiation.

Attributes
fit_config_dict_without_non_provided_fields: dict[str, Any] property

Return only non-None values.

These are the values that are provided by the user. We don't want to include the default values as they should be set by the latest PYMC

Returns Dictionary of non-None values

PyMCModelSchema

Bases: BaseModel

Schema for PyMC Config Dictionary.

Functions
validate_adstock(v)

Validate adstock component.

Parameters:

Name Type Description Default
v

Adstock value

required

Returns:

Type Description

Validated value

Raises:

Type Description
ValueError

If adstock is not a valid type

Source code in mmm_eval/adapters/schemas.py
@field_validator("adstock")
def validate_adstock(cls, v):
    """Validate adstock component.

    Args:
        v: Adstock value

    Returns:
        Validated value

    Raises:
        ValueError: If adstock is not a valid type

    """
    if v is not None:
        assert isinstance(v, AdstockTransformation)
    return v
validate_channel_columns(v)

Validate channel columns are not empty.

Parameters:

Name Type Description Default
v

Channel columns value

required

Returns:

Type Description

Validated value

Raises:

Type Description
ValueError

If channel columns is empty

Source code in mmm_eval/adapters/schemas.py
@field_validator("channel_columns")
def validate_channel_columns(cls, v):
    """Validate channel columns are not empty.

    Args:
        v: Channel columns value

    Returns:
        Validated value

    Raises:
        ValueError: If channel columns is empty

    """
    if v is not None and not v:
        raise ValueError("channel_columns must not be empty")
    return v
validate_saturation(v)

Validate saturation component.

Parameters:

Name Type Description Default
v

Saturation value

required

Returns:

Type Description

Validated value

Raises:

Type Description
ValueError

If saturation is not a valid type

Source code in mmm_eval/adapters/schemas.py
@field_validator("saturation")
def validate_saturation(cls, v):
    """Validate saturation component.

    Args:
        v: Saturation value

    Returns:
        Validated value

    Raises:
        ValueError: If saturation is not a valid type

    """
    if v is not None:
        assert isinstance(v, SaturationTransformation)
    return v
PyMCStringConfigSchema

Bases: BaseModel

Schema for PyMC Evaluation Config Dictionary.