diff_diff.to_meridian_roi_prior#

diff_diff.to_meridian_roi_prior(*, incremental_outcome=None, incremental_outcome_se=None, aggregation_result=None, scale=None, spend, parameter='roi_m', se_widening=1.0)[source]

Build a Meridian lognormal ROI/mROI prior from scoped experiment result(s).

Per experiment roi = incremental_outcome / spend with standard deviation incremental_outcome_se / spend * se_widening. Multiple experiments for the same channel are pooled with spend weights - the spend-weighted average ROI the Meridian FAQ suggests for multiple experiments (citing section 3.4 of Google’s MMM calibration whitepaper): roi_mean = sum(w_i * roi_i) with w_i = spend_i / sum(spend). The pooled roi_sd = sqrt(sum((w_i * sd_i)^2)) is this library’s uncertainty propagation for that average (treats experiments as independent - widen via se_widening when experiments share controls or windows). The pooled mean/sd map to lognormal (mu, sigma) via Google’s closed form.

Two input routes. Either the caller supplies the scoped estimand explicitly - incremental_outcome is the total incremental outcome the experiment measured, matching the estimand of the target prior: for parameter="roi_m" that is the outcome attributable to the channel’s spend against a zero-spend counterfactual (a full holdout), divided here by spend = the channel’s total spend over the window; for parameter="mroi_m" it is the marginal outcome of the spend change, divided by that spend change - or passes aggregation_result=: with scale= for a ‘simple’/’group’ container (this function deriving incremental_outcome = att * scale and incremental_outcome_se = se * scale per container row), or a results.aggregate('total') container alone (a single already-scaled total row; NO scale accepted). Rescaling happens ONLY under that explicit contract; sign, estimand match, and population remain the caller’s responsibility.

Parameters:
  • incremental_outcome (float or sequence of float, optional) – Total incremental outcome per experiment (the estimand matching parameter), in the same currency/units as the MMM. Must be finite; the pooled ROI must be positive (lognormal support). Required together with incremental_outcome_se unless aggregation_result is given (the two routes are mutually exclusive).

  • incremental_outcome_se (float or sequence of float, optional) – Standard error of incremental_outcome (finite, positive), computed for the caller’s aggregation. Required together with incremental_outcome unless aggregation_result is given.

  • aggregation_result (AggregationResult, optional) – The pinned container returned by results.aggregate('simple') (one experiment), results.aggregate('group') (one experiment per cohort, feeding the spend-weighted pooling below), or results.aggregate('total') (one already-scaled total experiment). Mutually exclusive with incremental_outcome/incremental_outcome_se; requires scale for ‘simple’/’group’ containers and FORBIDS it for ‘total’. Rows are consumed in aggregation_result.to_dataframe() order - the order summary() prints - and per-row sequence kwargs (scale, spend) align to that order. Raw results objects and EventStudyResults are rejected; this function never calls aggregate() itself. Group-container cautions: (1) cohort rows come from ONE fit sharing controls and windows, and the container carries only per-row SEs (the joint covariance’s off-diagonals are discarded upstream), so the independence pooling below can misstate the pooled uncertainty - it is anti-conservative when the net weighted covariance is positive (shared controls typically induce positive correlation), and the direction cannot be determined from marginal SEs alone; se_widening > 1 is a conservative heuristic, not an exact correction; (2) a scalar spend replicates per row, making total_spend = n_rows * spend and the pooled prior the arithmetic MEAN of cohort ROIs, sum(effect_i) / (n_rows * spend) - an n-fold understatement of channel ROI if the scalar was meant as the channel’s TOTAL spend; pass per-cohort spends.

  • scale (float, sequence of float, or "auto", optional) – Converts each container row’s per-observation ATT to that experiment’s total incremental outcome: incremental_outcome = att * scale, incremental_outcome_se = se * scale. Required with a ‘simple’/’group’ aggregation_result; forbidden with a ‘total’ container, whose row is already the estimator-owned total (finite, positive; scalar or one value per row in to_dataframe() order; e.g. treated units x treated periods for an unweighted additive fit - an example, not a universal formula). scale="auto" derives scale from the container’s per-row n and is accepted ONLY for ImputationDiD and TwoStageDiD fits, whose n is the treated observations the ATT averages over. Passing "auto" acknowledges three assumptions the container cannot verify: additive-level outcome (not log/rate/share), an unweighted fit (survey-weighted fits pair a weighted att with a raw-count n), and fully identified effects (ImputationDiD reports the raw treated-observation count at BOTH levels while averaging finite effects only; TwoStageDiD does so only at 'simple' - its 'group' counts are post-filter; the affected fits warn at fit time on the degenerate branch, and "auto" there overcounts). An overflowing att * scale surfaces via this function’s ordinary finiteness errors - incremental_outcome is validated before incremental_outcome_se, so a row where both overflow reports incremental_outcome.

  • spend (float or sequence of float) – Spend the incremental outcome is divided by (finite, positive): the channel’s total spend over the window for roi_m, or the spend change for mroi_m.

  • parameter ({"roi_m", "mroi_m"}, default "roi_m") – Which Meridian prior the estimate informs. roi_m is the return on the channel’s full spend (a full-holdout / zero-spend estimand); mroi_m is the marginal return of a spend change. Under saturation these differ, so the caller declares which their incremental_outcome measures; the returned prior and .to_code() target that parameter with its own Meridian default for non-experiment channels.

  • se_widening (float, default 1.0) – Multiplier on each experiment’s ROI standard deviation (finite, positive). Values above 1 encode skepticism about experiment-to-MMM transferability.

Returns:

Pooled roi_mean/roi_sd, the lognormal mu/sigma, the target parameter, per-experiment detail, plus .to_dict() and the channel-scoped .to_code() snippet helper.

Return type:

MeridianROIPrior

Raises:

ValueError – On an invalid parameter, non-positive spend/se/se_widening, non-finite inputs, broadcasting mismatches, empty inputs, a non-positive pooled ROI mean (lognormal support), or non-finite scaled/pooled results.