You can customize and configure the Meridian base model specification for your
specific needs, such as customizing ROI priors, adjusting for trend and
seasonality, setting the maximum carry over duration, using reach and
frequency, and more. For more information about the Meridian base model and
options, see [The Meridian model](https://developers.google.com/meridian/docs/advanced-modeling/input-data)
section.

## Default model specification

You can use the following default model specification to start creating your
model:

    model_spec = spec.ModelSpec(
        prior=prior_distribution.PriorDistribution(),
        media_effects_dist='log_normal',
        hill_before_adstock=False,
        max_lag=8,
        unique_sigma_for_each_geo=False,
        media_prior_type='roi',
        roi_calibration=None,
        rf_prior_type='roi',
        rf_roi_calibration=None,
        organic_media_prior_type='contribution',
        organic_rf_prior_type='contribution',
        non_media_treatments_prior_type='contribution',
        knots=None,
        baseline_geo=None,
        holdout=None,
        population_scaled_controls=None,
        adstock_decay_spec='geometric',
        enable_aks=False,
        allows_negative_aggregate_baseline=True,
    )

## Set the priors

You can customize the priors in the [default model
specification](https://developers.google.com/meridian/docs/user-guide/configure-model#default-spec). Every parameter gets its own independent prior
that can be set by the `prior` argument in Meridian
[`ModelSpec`](https://developers.google.com/meridian/reference/api/meridian/model/spec/ModelSpec).

> [!NOTE]
> **What are the defaults?:** For
> information about the priors and exceptions, see [Default prior
> distributions](https://developers.google.com/meridian/docs/advanced-modeling/default-prior-distributions).
>
> **How do I incorporate my domain knowledge into my MMM?:** For detailed guidance
> on how to incorporate your business domain knowledge and configure your priors
> in your MMM, check out [Choose and configure treatment prior types](https://developers.google.com/meridian/docs/advanced-modeling/how-to-choose-treatment-prior-types).

The following example customizes the ROI prior distributions for each media
channel. In this particular example, the ROI priors for the media channels
differ.

- Channel 1: `LogNormal(0.2, 0.7)`
- Channel 2: `LogNormal(0.3, 0.9)`
- Channel 3: `LogNormal(0.4, 0.6)`
- Channel 4: `LogNormal(0.3, 0.7)`
- Channel 5: `LogNormal(0.3, 0.6)`
- Channel 6: `LogNormal(0.4, 0.5)`

    import numpy as np
    import tensorflow_probability.substrates.jax as tfp_jax

    my_input_data = input_data.InputData( ... )
    build_media_channel_args = my_input_data.get_paid_media_channels_argument_builder()

    # Assuming Channel1,...,Channel6 are all media channels.
    roi_m = build_media_channel_args(
      Channel1=(0.2, 0.7),
      Channel2=(0.3, 0.9),
      Channel3=(0.4, 0.6),
      Channel4=(0.3, 0.7),
      Channel5=(0.3, 0.6),
      Channel6=(0.4, 0.5),
    ) # This creates a list of channel-ordered (mu, sigma) tuples.
    roi_m_mu, roi_m_sigma = zip(*roi_m)

    prior = prior_distribution.PriorDistribution(
        roi_m=tfp_jax.distributions.LogNormal(
            np.array(roi_m_mu, dtype=np.float64),
            np.array(roi_m_sigma, dtype=np.float64),
            name=constants.ROI_M,
        )
    )
    model_spec = spec.ModelSpec(prior=prior)

Where `prior` in `ModelSpec` is a
[`PriorDistribution`](https://developers.google.com/meridian/reference/api/meridian/model/prior_distribution/PriorDistribution)
object specifying the prior distribution of each set of model parameters. Every
parameter gets its own independent prior that can be set by the
`prior_distribution.PriorDistribution()` constructor.

Model parameters with an `m` subscript (for example, `roi_m`) can either have
dimensionality equal to the number of media channels or can be one-dimensional.
When the dimensionality is equal to the number of media channels, the order of
parameter values in the custom prior corresponds to the order in
`data.media_channel`, representing a custom prior being set for each respective
media channel. If you can't determine a custom prior for some of the media
channels, consider manually using the default `LogNormal(0.2, 0.9)`. When a
one-dimensional prior is passed, then the single dimension is used for all of
the media channels.

The logic for setting priors for model parameters with a `c` subscript
(for example, `gamma_c`) is the same as it is for the `m` subscript. For `c`
subscripts, the dimensionality can either be equal to the number of control
variables or can be one-dimensional. When the dimensionality is equal to the
number of control variables, the order of parameter values in the custom prior
corresponds to the order in `data.control_variable`, representing a custom prior
being set for each respective control variable.

The following example shows that a single number is used to set the same ROI
prior for each channel. In this particular example, the ROI priors for the two
media channels are identical, with both represented as `LogNormal(0.2, 0.9)`.

    import numpy as np
    import tensorflow_probability.substrates.jax as tfp_jax
    from meridian.model import constants
    from meridian.model import prior_distribution
    from meridian.model import spec

    roi_mu = np.float64(0.2)
    roi_sigma = np.float64(0.9)
    prior = prior_distribution.PriorDistribution(
        roi_m=tfp_jax.distributions.LogNormal(roi_mu, roi_sigma, name=constants.ROI_M)
    )
    model_spec = spec.ModelSpec(prior=prior)

It is important to note that Meridian has a distinct ROI parameter
(`roi_rf`) and beta parameter (`beta_rf`) for the channels that possess reach
and frequency. Consequently, some modifications are required to the previously
mentioned code snippets in instances where specific channels have reach and
frequency data. In this example, channel 5 and channel 6 have reach and
frequency data.

- To customize the ROI prior distributions for each media channel:

      # ROI prior for channels without R&F data
      build_media_channel_args = my_input_data.get_paid_media_channels_argument_builder()
      roi_m = build_media_channel_args(
        Channel1=(0.2, 0.7),
        Channel2=(0.3, 0.9),
        Channel3=(0.4, 0.6),
        Channel4=(0.3, 0.7),
      )
      roi_m_mu, roi_m_sigma = zip(*roi_m)

      # ROI prior for channels with R&F data
      build_rf_channel_args = my_input_data.get_paid_rf_channels_argument_builder()
      roi_rf = build_rf_channel_args(
        Channel5=(0.3, 0.6),
        Channel6=(0.4, 0.5),
      )
      roi_rf_mu, roi_rf_sigma = zip(*roi_rf)

      prior = prior_distribution.PriorDistribution(
          roi_m=tfp_jax.distributions.LogNormal(
              np.array(roi_m_mu, dtype=np.float64),
              np.array(roi_m_sigma, dtype=np.float64),
              name=constants.ROI_M,
          ),
          roi_rf=tfp_jax.distributions.LogNormal(
              np.array(roi_rf_mu, dtype=np.float64),
              np.array(roi_rf_sigma, dtype=np.float64),
              name=constants.ROI_RF,
          ),
      )
      model_spec = spec.ModelSpec(prior=prior)

Note that the order of parameter values in `roi_rf_mu` and `roi_rf_sigma` must
match `data.rf_channel`.

- To set the same ROI priors for all media channels:

      roi_mu = np.float64(0.2)
      roi_sigma = np.float64(0.9)
      prior = prior_distribution.PriorDistribution(
          roi_m=tfp_jax.distributions.LogNormal(
              roi_mu, roi_sigma, name=constants.ROI_M
          ),
          roi_rf=tfp_jax.distributions.LogNormal(
              roi_mu, roi_sigma, name=constants.ROI_RF
          ),
      )
      model_spec = spec.ModelSpec(prior=prior)

## Use a training and test data split (Optional)

We recommend using a training and test data split to help avoid overfitting and
to help ensure that the model generalizes well with new data. This can be done
by using `holdout`. This is an optional step.

The following example randomly sets 20% of the time periods in each geo as the
test group. The same specification works for geo-level and national-level
models.

    model_spec = spec.ModelSpec(
        holdout=spec.HoldoutSpec(
            spec=spec.RandomHoldoutSpec(ratio=0.2, seed=1),
        ),
    )

Where `holdout` is an optional
[`HoldoutSpec`](https://developers.google.com/meridian/reference/api/meridian/model/spec/HoldoutSpec) that
indicates which observations are excluded from the training sample. Only the
response variable is excluded from the training sample, and the media variables
are still included as they can affect the Adstock of subsequent weeks.
**Default:** `None` (meaning there aren't any holdout geo and times).

With a
[`RandomHoldoutSpec`](https://developers.google.com/meridian/reference/api/meridian/model/spec/RandomHoldoutSpec),
Meridian makes the random draw. It holds out `round(ratio * n_times)` time
periods in each geo, sampled without replacement, and reuses the same draw for
the life of the model. `ratio` must be strictly between 0 and 1, and `seed` is
optional. When the model is [saved and
loaded](https://developers.google.com/meridian/docs/user-guide/saving-model-object), the draw is saved with
it and restored rather than drawn again.

To use a holdout that you drew yourself, pass `HoldoutSpec` a sequence of
[`DateRange`](https://developers.google.com/meridian/reference/api/meridian/model/spec/DateRange) to hold out
the same dates in every geo, or a sequence of
[`GeoHoldoutSpec`](https://developers.google.com/meridian/reference/api/meridian/model/spec/GeoHoldoutSpec)
to hold out the given dates in the given geos. Meridian holds out exactly those
observations. For guidance on choosing a holdout sample, see [Holdout
observations](https://developers.google.com/meridian/docs/advanced-modeling/holdout-observations).

## Tune the time-varying intercept for modeling time effects (Optional)

Meridian uses a time-varying intercept approach for modeling time effects. The
effects can be tuned by adjusting the value of `knots`. For more information,
see [How the `knots` argument
works](https://developers.google.com/meridian/docs/advanced-modeling/setting-knots#how-knots-argument-works).

`knots` is an optional integer or list of integers indicating the knots used to
estimate time effects. When `knots` is a list of integers, the knot locations
are provided by that list, where zero corresponds to a knot at the first time
period, one corresponds to a knot at the second time period, etc., with
(`n_times - 1`) corresponding to a knot at the last time period.

When `knots` is an integer, there are a corresponding number of knots with
locations equally spaced across the time periods, including knots at zero and
(`n_times - 1`). When `knots` is `1`, there is a single common regression
coefficient used for all time periods.

If `knots` is set to `None`, the number of knots used is equal to the number of
time periods in the case of a geo model. This is equivalent to each time period
having its own regression coefficient. For national models, if `knots` is set to
`None`, then the number of knots used is `1`. By default, its value is set to
`None`.

For guidance on how to choose the number of knots for time effects in the model,
see
[Manually choosing knots](https://developers.google.com/meridian/docs/advanced-modeling/setting-knots#choosing-number-of-knots-time-effects).

### Automatic Knot Selection

Meridian's Automatic Knot Selection (AKS) feature automatically selects the
number and placement of knots to explain trend and seasonality in the outcome
variable, see [Automatic Knot Selection (AKS)](https://developers.google.com/meridian/docs/advanced-modeling/setting-knots#automatic-knot-selection)
to learn more.

To enable AKS, set the `enable_aks` parameter to `True` in your `ModelSpec`.

    from meridian.model import model
    from meridian.model import spec

    model_spec = spec.ModelSpec(enable_aks=True)
    mmm = model.Meridian(model_spec=model_spec)

### Hybrid Knot Selection

You can combine Automatic Knot Selection (AKS) with your own domain knowledge by
specifying required knot locations and excluded knot locations. See
[Automatic Knot Selection in Meridian](https://developers.google.com/meridian/docs/advanced-modeling/setting-knots#automatic-knot-selection)
to learn more.

To enable hybrid knot selection, call the `automatic_knot_selection()` method
and provide an iterable of either "required knots" or "excluded knots".

    import numpy as np
    from meridian.model import knots
    from meridian.model import spec

    # Assume 'data' is the input data defined in the InputData step
    aks = knots.AKS(data)
    # Require knots at specific time periods and automatically select the rest
    required_knots = np.array([10, 20, 30])
    # Excluded knots at specific time periods and automatically pick from the rest
    excluded_knots = np.array([40, 50])
    knot_locations = aks.automatic_knot_selection(
        required_knots=required_knots,
        excluded_knots=excluded_knots,
    ).knots

    # You must use the 'knots' argument and NOT set 'enable_aks=True'.
    model_spec = spec.ModelSpec(knots=knot_locations)

You can also use the `restrict_to_right_of_required_knots` parameter to force
AKS to only evaluate and select knots that occur after the maximum
`required_knots` location. This is especially useful for **Model Refreshes** :
when appending new time series data to an existing model, you can pass the
previous model's knots as `required_knots` and set
`restrict_to_right_of_required_knots=True`. This ensures that your
historical baseline trend remains consistent while allowing AKS to automatically
find knots for the new time period.

    import numpy as np
    from meridian.model import knots
    from meridian.model import spec

    # Assume 'data' is the newly appended input data
    aks = knots.AKS(data)

    # Require knots from the previous model and only allow new knots after them
    previous_knots = np.array([10, 20, 30, 40])
    knot_locations = aks.automatic_knot_selection(
        required_knots=previous_knots,
        restrict_to_right_of_required_knots=True
    ).knots

    model_spec = spec.ModelSpec(knots=knot_locations)

### Manual Knot Specification

To manually place knots for modeling time-based effects, use the `knots`
parameter in the `ModelSpec`. The knots parameter accepts either an integer (for
a specific number of equally spaced knots) or a list of integers (for specific
knot locations).

- Setting `knots` to `1` uses a single parameter for all time periods, which
  effectively removes any automatic time-based effects. In this case, you
  should include control variables, such as event dummies, to capture these
  time-based effects.

      model_spec = spec.ModelSpec(knots=1)

- Setting `knots` to a relatively large number:

      knots = round(0.8 * n_times)
      model_spec = spec.ModelSpec(knots=knots)

- Setting `knots` for every 4 time points:

      knots = np.arange(0, n_times, 4).tolist()
      model_spec = spec.ModelSpec(knots=knots)

- Setting `knots` so that there is a knot in November and December, but is
  relatively sparse elsewhere. To make this example tractable, assume that
  there are only 12 data points and that the data is monthly (this assumption
  is not realistic or recommended). This example is summarized in the
  following table:

  ![Setting knots by month](https://developers.google.com/static/meridian/images/knots-table-monthly.png)
- To set `knots` at indexes `0`, `3`, `6`, `10`, and `11`, do the following:

      knots = [0, 3, 6, 10, 11]
      model_spec = spec.ModelSpec(knots=knots)

- To add custom knots to the ones selected by AKS, first retrieve the knots
  chosen by the algorithm, and then append your custom knots to that list:

      # Enable AKS in Meridian
      model_spec = spec.ModelSpec(enable_aks=True)
      mmm = model.Meridian(model_spec=model_spec)
      # Retrieve the list of knots that AKS selects
      knot_info = mmm.knot_info
      selected_knots = knot_info.knot_locations
      # Add a new knot at time point 52
      # Create a new list and use it to define a new ModelSpec
      modified_knots = np.append(selected_knots, 52)
      model_spec = spec.ModelSpec(knots=modified_knots)

## Tune the ROI calibration (Optional)

Meridian introduces an ROI calibration method that reparameterizes ROI
as a model parameter. For more information, see
[ROI priors for calibration](https://developers.google.com/meridian/docs/advanced-modeling/roi-priors-and-calibration).

By default, the same non-informative prior on ROI is applied to all media
channels. You can tune this feature by doing one of the following:

- Turning off the ROI calibration
- Setting the ROI calibration period

### Turn off the ROI calibration

You can turn off the ROI calibration feature using
`media_prior_type='coefficient'` and `rf_prior_type='coefficient'`:

    model_spec = spec.ModelSpec(
        media_prior_type='coefficient',
        rf_prior_type='coefficient',
    )

The `media_prior_type` argument indicates whether to use a prior on `roi_m`,
`mroi_m`, or `beta_m` in the `PriorDistribution`. **Default:** `'roi'`
(recommended)

### Set the ROI calibration period

Although the media effect regression coefficients don't have time varying
effects, there is a calibration window argument for setting the ROI (or mROI)
prior. This is because ROI (or mROI) at a given time is dependent on additional
factors that might vary across time:

- The Hill curves model the non-linear, diminishing returns of media execution. Therefore, the amount of media execution at a given time can impact the ROI.
- Media allocation across geos with different effectiveness.
- Cost of media execution.

You can calibrate the MMM using a subset of data when the experimental results
don't reflect the modeling return on ad spend (ROAS) that the MMM aims to
measure. For example, the experiment timeframe is not aligned with the MMM data
timeframe.

> [!NOTE]
> **Note:** The use of `roi_calibration` is not generally recommended because calibrating the ROI of a specific time period does not necessarily improve estimation of the overall ROI, which is typically the main focal point of the analysis. The `roi_calibration` argument is for users who are specifically looking for this functionality.

For more information, see [Media Mix Model Calibration with Bayesian
Priors](https://research.google/pubs/media-mix-model-calibration-with-bayesian-priors/)
and [Calibrate treatment priors](https://developers.google.com/meridian/docs/advanced-modeling/roi-priors-and-calibration).

The following example shows how to specify the ROI calibration period from
`'2021-11-01'` to `'2021-12-20'` for channel 1. Any media channels not named
will utilize all available periods for ROI calibration.

    model_spec = spec.ModelSpec(
        roi_calibration=spec.CalibrationSpec(
            spec=[
                spec.ChannelCalibrationSpec(
                    channels=['Channel1'],
                    date_ranges=[
                        spec.DateRange('2021-11-01', '2021-12-20'),
                    ],
                ),
            ],
        ),
    )

Where `roi_calibration` is an optional
[`CalibrationSpec`](https://developers.google.com/meridian/reference/api/meridian/model/spec/CalibrationSpec)
naming the media channels to calibrate and the date ranges to calibrate them
over. A [`DateRange`](https://developers.google.com/meridian/reference/api/meridian/model/spec/DateRange)
includes both of its bounds, and each bound must match one of the input data's
time coordinates exactly. Omit a bound to leave that side open. If set to
`None`, all times are used for media ROI calibration. **Default:** `None`.

To apply the same date ranges to every channel, pass a sequence of `DateRange`
instead of a sequence of `ChannelCalibrationSpec`:

    model_spec = spec.ModelSpec(
        roi_calibration=spec.CalibrationSpec(
            spec=[spec.DateRange('2021-11-01', '2021-12-20')],
        ),
    )

Note that Meridian has a different parameter, `rf_roi_calibration`, for the
channels that possess reach and frequency. The following example shows how to
specify the ROI calibration period from `'2021-11-01'` to `'2021-12-20'` for
channel 5, which uses reach and frequency as inputs.

    model_spec = spec.ModelSpec(
        rf_roi_calibration=spec.CalibrationSpec(
            spec=[
                spec.ChannelCalibrationSpec(
                    channels=['Channel5'],
                    date_ranges=[
                        spec.DateRange('2021-11-01', '2021-12-20'),
                    ],
                ),
            ],
        ),
    )

Where `rf_roi_calibration` is an optional
[`CalibrationSpec`](https://developers.google.com/meridian/reference/api/meridian/model/spec/CalibrationSpec)
over reach and frequency channels. If set to `None`, all times are used for
reach and frequency ROI calibration. **Default:** `None`.

## Set additional attributes (Optional)

You can change any remaining attributes in the default model specification as
needed. This section describes the remaining attributes and examples of how to
change the values.

### `baseline_geo`

An optional integer or a string for the baseline geo. The baseline geo is
treated as the reference geo in the dummy encoding of geos. Non-baseline geos
have a corresponding `tau_g` indicator variable, which means that they have a
higher prior variance than the baseline geo. When set to `None`, the geo with
the biggest population is used as the baseline. **Default:** `None`

The following example shows the baseline geo set to `'Geo10'`:

    model_spec = spec.ModelSpec(baseline_geo='Geo10')

### `hill_before_adstock`

A boolean indicating whether to apply the Hill function before the Adstock
function, in contrast to the default order of Adstock before Hill. **Default:**
`False`

The following example shows that the Hill function will be applied first, by
setting the value to `True`:

    model_spec = spec.ModelSpec(hill_before_adstock=True)

### `max_lag`

An integer indicating the maximum number of lag periods (\>= 0) to include in the
Adstock calculation. For additional details on how to set `max_lag`, see
[Set the max_lag parameter](https://developers.google.com/meridian/docs/advanced-modeling/set-max-lag-parameter).
**Default:** `8`

The following example changes the value to `4`:

    model_spec = spec.ModelSpec(max_lag=4)

### `media_effects_dist`

A string to specify the distribution of media random effects across geos.
Allowed values: `'normal'` or `'log_normal'`. **Default:** `'log_normal'`

Where:

- `media_effects_dist='log_normal'` is \\(\\beta m,g\\ {_\\sim\^{iid}}
  Lognormal(\\beta m, \\eta \^2m)\\)

- `media_effects_dist='normal'` is \\(\\beta m,g\\ {_\\sim\^{iid}} Normal (\\beta
  m, \\eta \^2m)\\)

The following example shows how to change the value to `'normal'`:

    model_spec = spec.ModelSpec(media_effects_dist='normal')

### `population_scaled_controls`

An optional sequence of control variable names whose values are scaled by
population. **Default:** `None`

The following example scales the `'GQV'` control variable by population:

    model_spec = spec.ModelSpec(population_scaled_controls=['GQV'])

### `unique_sigma_for_each_geo`

A boolean indicating whether to use a unique residual variance for each geo. If
`False`, then a single residual variance is used for all geos. **Default:**
`False`

The following example sets the model to use a unique residual variance for each
geo:

    model_spec = spec.ModelSpec(unique_sigma_for_each_geo=True)

### `adstock_decay_spec`

A string or mapping indicating the adstock decay function for each media, reach
and frequency, organic media and organic reach and frequency channel. If
`adstock_decay_spec` is a string, its value should be `'geometric'` or
`'binomial'`, denoting which decay function to use for all channels that use the
Adstock transformation. If `adstock_decay_spec` is a mapping, keys should be
channel names and should be `'geometric'` or `'binomial'`, with each key-value
pair denoting the adstock decay function to use for that channel. Channels that
are not specified in the mapping default to using `'geometric'`. **Default:**
`'geometric'`.

The following example sets the model to use binomial decay for all channels:

    from meridian.model import spec

    model_spec = spec.ModelSpec(
      adstock_decay_spec='binomial'
    )

Whereas to set the model to use binomial, geometric, and binomial for three
channels named `'Channel0'`, `'Channel1'`, and `'Channel2'`, respectively:

    from meridian.model import spec

    model_spec = spec.ModelSpec(
      adstock_decay_spec=dict(
        Channel0='binomial',
        Channel1='geometric',
        Channel2='binomial',
      )
    )

### `allows_negative_aggregate_baseline`

A boolean indicating whether to allow the total baseline outcome aggregated
across all geos and time periods to be negative in prior and posterior draws.
If `False`, the aggregate baseline is constrained to be non-negative, though
baseline values for individual geos or time periods may still be negative.
Setting this to `False` requires `prior.knot_values` to be a `Normal`
distribution with `loc=0` and a common `scale` across all knots. For additional
details, see [Prevent a negative
baseline](https://developers.google.com/meridian/docs/advanced-modeling/prevent-negative-baseline).
**Default:** `True`

The following example constrains the aggregate baseline to be non-negative:

    from meridian.model import spec

    model_spec = spec.ModelSpec(allows_negative_aggregate_baseline=False)