google-meridian 1.0.9__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
meridian/model/media.py CHANGED
@@ -34,9 +34,29 @@ __all__ = [
34
34
  ]
35
35
 
36
36
 
37
+ def _roi_calibration_scaled_counterfactual(
38
+ metric_scaled: tf.Tensor,
39
+ calibration_period: tf.Tensor,
40
+ ) -> tf.Tensor:
41
+ """Calculate ROI calibration scaled counterfactual media or reach.
42
+
43
+ Args:
44
+ metric_scaled: A tensor of scaled metric values with shape `(n_geos,
45
+ n_times, n_channels)`.
46
+ calibration_period: A boolean tensor indicating which time periods are used
47
+ for calculation.
48
+
49
+ Returns:
50
+ A tensor of scaled metric values with shape `(n_geos, n_times, n_channels)`
51
+ where media values are set to zero during the calibration period.
52
+ """
53
+ factors = tf.where(calibration_period, 0.0, 1.0)
54
+ return tf.einsum("gtm,tm->gtm", metric_scaled, factors)
55
+
56
+
37
57
  @dataclasses.dataclass(frozen=True)
38
58
  class MediaTensors:
39
- """Container for media tensors.
59
+ """Container for (paid) media tensors.
40
60
 
41
61
  Attributes:
42
62
  media: A tensor constructed from `InputData.media`.
@@ -45,25 +65,31 @@ class MediaTensors:
45
65
  model's media data.
46
66
  media_scaled: The media tensor normalized by population and by the median
47
67
  value.
48
- media_counterfactual: A tensor containing the media counterfactual values.
49
- If ROI priors are used, then the ROI of media channels is based on the
50
- difference in expected sales between the `media` tensor and this
51
- `media_counterfactual` tensor.
52
- media_counterfactual_scaled: A tensor containing the media counterfactual
53
- scaled values.
54
- media_spend_counterfactual: A tensor containing the media spend
55
- counterfactual values. If ROI priors are used, then the ROI of media
56
- channels is based on the spend difference between `media_spend` tensor and
57
- this `media_spend_counterfactual` tensor.
68
+ prior_media_scaled_counterfactual: A tensor containing `media_scaled` values
69
+ corresponding to the counterfactual scenario required for the prior
70
+ calculation. For ROI priors, the counterfactual scenario is where media is
71
+ set to zero during the calibration period. For mROI priors, the
72
+ counterfactual scenario is where media is increased by a small factor for
73
+ all `n_media_times`. For contribution priors, the counterfactual scenario
74
+ is where media is set to zero for all `n_media_times`. This attribute is
75
+ set to `None` when it would otherwise be a tensor of zeros, i.e., when
76
+ contribution contribution priors are used, or when ROI priors are used and
77
+ `roi_calibration_period` is `None`.
78
+ prior_denominator: If ROI, mROI, or contribution priors are used, this
79
+ represents the denominator. It is a tensor with dimension equal to
80
+ `n_media_channels`. For ROI priors, it is the spend during the overlapping
81
+ time periods between the calibration period and the modeling time window.
82
+ For mROI priors, it is the ROI prior denominator multiplied by a small
83
+ factor. For contribution priors, it is the total observed outcome
84
+ (repeated for each channel.)
58
85
  """
59
86
 
60
87
  media: tf.Tensor | None = None
61
88
  media_spend: tf.Tensor | None = None
62
89
  media_transformer: transformers.MediaTransformer | None = None
63
90
  media_scaled: tf.Tensor | None = None
64
- media_counterfactual: tf.Tensor | None = None
65
- media_counterfactual_scaled: tf.Tensor | None = None
66
- media_spend_counterfactual: tf.Tensor | None = None
91
+ prior_media_scaled_counterfactual: tf.Tensor | None = None
92
+ prior_denominator: tf.Tensor | None = None
67
93
 
68
94
 
69
95
  def build_media_tensors(
@@ -81,43 +107,55 @@ def build_media_tensors(
81
107
  media, tf.convert_to_tensor(input_data.population, dtype=tf.float32)
82
108
  )
83
109
  media_scaled = media_transformer.forward(media)
84
-
85
- # Derive counterfactual media tensors depending on whether mroi or roi priors
86
- # are used and whether roi_calibration_period is specified.
87
- if (
88
- model_spec.paid_media_prior_type
89
- == constants.PAID_MEDIA_PRIOR_TYPE_MROI
90
- ):
91
- factor = constants.MROI_FACTOR
110
+ prior_type = model_spec.effective_media_prior_type
111
+ calibration_period = model_spec.roi_calibration_period
112
+ if calibration_period is not None:
113
+ calibration_period_tf = tf.convert_to_tensor(
114
+ calibration_period, dtype=tf.bool
115
+ )
92
116
  else:
93
- factor = 0
117
+ calibration_period_tf = None
94
118
 
95
- if model_spec.roi_calibration_period is None:
96
- media_counterfactual = factor * media
97
- media_counterfactual_scaled = factor * media_scaled
98
- media_spend_counterfactual = factor * media_spend
119
+ aggregated_media_spend = tf.convert_to_tensor(
120
+ input_data.aggregate_media_spend(
121
+ calibration_period=calibration_period
122
+ ),
123
+ dtype=tf.float32,
124
+ )
125
+ # Set `prior_media_scaled_counterfactual` and `prior_denominator` depending on
126
+ # the prior type.
127
+ if prior_type == constants.TREATMENT_PRIOR_TYPE_ROI:
128
+ prior_denominator = aggregated_media_spend
129
+
130
+ if calibration_period is None:
131
+ prior_media_scaled_counterfactual = None
132
+ else:
133
+ prior_media_scaled_counterfactual = (
134
+ _roi_calibration_scaled_counterfactual(
135
+ media_scaled,
136
+ calibration_period=calibration_period_tf,
137
+ )
138
+ )
139
+ elif prior_type == constants.TREATMENT_PRIOR_TYPE_MROI:
140
+ prior_media_scaled_counterfactual = media_scaled * constants.MROI_FACTOR
141
+ prior_denominator = aggregated_media_spend * (constants.MROI_FACTOR - 1.0)
142
+ elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
143
+ prior_media_scaled_counterfactual = None
144
+ total_outcome = tf.cast(input_data.get_total_outcome(), tf.float32)
145
+ prior_denominator = tf.repeat(total_outcome, len(input_data.media_channel))
146
+ elif prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
147
+ prior_media_scaled_counterfactual = None
148
+ prior_denominator = None
99
149
  else:
100
- media_counterfactual = tf.where(
101
- model_spec.roi_calibration_period, factor * media, media
102
- )
103
- media_counterfactual_scaled = tf.where(
104
- model_spec.roi_calibration_period, factor * media_scaled, media_scaled
105
- )
106
- n_times = len(input_data.time)
107
- media_spend_counterfactual = tf.where(
108
- model_spec.roi_calibration_period[..., -n_times:, :],
109
- factor * media_spend,
110
- media_spend,
111
- )
150
+ raise ValueError(f"Unsupported prior type: {prior_type}")
112
151
 
113
152
  return MediaTensors(
114
153
  media=media,
115
154
  media_spend=media_spend,
116
155
  media_transformer=media_transformer,
117
156
  media_scaled=media_scaled,
118
- media_counterfactual=media_counterfactual,
119
- media_counterfactual_scaled=media_counterfactual_scaled,
120
- media_spend_counterfactual=media_spend_counterfactual,
157
+ prior_media_scaled_counterfactual=prior_media_scaled_counterfactual,
158
+ prior_denominator=prior_denominator,
121
159
  )
122
160
 
123
161
 
@@ -131,17 +169,11 @@ class OrganicMediaTensors:
131
169
  the model's organic media data.
132
170
  organic_media_scaled: The organic media tensor normalized by population and
133
171
  by the median value.
134
- organic_media_counterfactual: A tensor containing the organic media
135
- counterfactual values.
136
- organic_media_counterfactual_scaled: A tensor containing the organic media
137
- counterfactual scaled values.
138
172
  """
139
173
 
140
174
  organic_media: tf.Tensor | None = None
141
175
  organic_media_transformer: transformers.MediaTransformer | None = None
142
176
  organic_media_scaled: tf.Tensor | None = None
143
- organic_media_counterfactual: tf.Tensor | None = None
144
- organic_media_counterfactual_scaled: tf.Tensor | None = None
145
177
 
146
178
 
147
179
  def build_organic_media_tensors(
@@ -160,15 +192,11 @@ def build_organic_media_tensors(
160
192
  tf.convert_to_tensor(input_data.population, dtype=tf.float32),
161
193
  )
162
194
  organic_media_scaled = organic_media_transformer.forward(organic_media)
163
- organic_media_counterfactual = tf.zeros_like(organic_media)
164
- organic_media_counterfactual_scaled = tf.zeros_like(organic_media_scaled)
165
195
 
166
196
  return OrganicMediaTensors(
167
197
  organic_media=organic_media,
168
198
  organic_media_transformer=organic_media_transformer,
169
199
  organic_media_scaled=organic_media_scaled,
170
- organic_media_counterfactual=organic_media_counterfactual,
171
- organic_media_counterfactual_scaled=organic_media_counterfactual_scaled,
172
200
  )
173
201
 
174
202
 
@@ -184,16 +212,23 @@ class RfTensors:
184
212
  model's RF data.
185
213
  reach_scaled: A reach tensor normalized by population and by the median
186
214
  value.
187
- reach_counterfactual: A reach tensor with media counterfactual values. If
188
- ROI priors are used, then the ROI of R&F channels is based on the
189
- difference in expected sales between the `reach` tensor and this
190
- `reach_counterfactual` tensor.
191
- reach_counterfactual_scaled: A reach tensor with media counterfactual scaled
192
- values.
193
- rf_spend_counterfactual: A reach tensor with media spend counterfactual
194
- values. If ROI priors are used, then the ROI of R&F channels is based on
195
- the spend difference between `rf_spend` tensor and this
196
- `rf_spend_counterfactual` tensor.
215
+ prior_reach_scaled_counterfactual: A tensor containing `reach_scaled` values
216
+ corresponding to the counterfactual scenario required for the prior
217
+ calculation. For ROI priors, the counterfactual scenario is where reach is
218
+ set to zero during the calibration period. For mROI priors, the
219
+ counterfactual scenario is where reach is increased by a small factor for
220
+ all `n_rf_times`. For contribution priors, the counterfactual scenario is
221
+ where reach is set to zero for all `n_rf_times`. This attribute is set to
222
+ `None` when it would otherwise be a tensor of zeros, i.e., when
223
+ contribution contribution priors are used, or when ROI priors are used and
224
+ `rf_roi_calibration_period` is `None`.
225
+ prior_denominator: If ROI, mROI, or contribution priors are used, this
226
+ represents the denominator. It is a tensor with dimension equal to
227
+ `n_rf_channels`. For ROI priors, it is the spend during the overlapping
228
+ time periods between the calibration period and the modeling time window.
229
+ For mROI priors, it is the ROI prior denominator multiplied by a small
230
+ factor. For contribution priors, it is the total observed outcome
231
+ (repeated for each channel).
197
232
  """
198
233
 
199
234
  reach: tf.Tensor | None = None
@@ -201,9 +236,8 @@ class RfTensors:
201
236
  rf_spend: tf.Tensor | None = None
202
237
  reach_transformer: transformers.MediaTransformer | None = None
203
238
  reach_scaled: tf.Tensor | None = None
204
- reach_counterfactual: tf.Tensor | None = None
205
- reach_counterfactual_scaled: tf.Tensor | None = None
206
- rf_spend_counterfactual: tf.Tensor | None = None
239
+ prior_reach_scaled_counterfactual: tf.Tensor | None = None
240
+ prior_denominator: tf.Tensor | None = None
207
241
 
208
242
 
209
243
  def build_rf_tensors(
@@ -221,27 +255,39 @@ def build_rf_tensors(
221
255
  reach, tf.convert_to_tensor(input_data.population, dtype=tf.float32)
222
256
  )
223
257
  reach_scaled = reach_transformer.forward(reach)
224
-
225
- # marginal ROI by reach equals the ROI. The conversion between `beta_rf` and
226
- # `roi_rf` is the same, regardless of whether `roi_rf` represents ROI or
227
- # marginal ROI by reach.
228
- if model_spec.rf_roi_calibration_period is None:
229
- reach_counterfactual = tf.zeros_like(reach)
230
- reach_counterfactual_scaled = tf.zeros_like(reach_scaled)
231
- rf_spend_counterfactual = tf.zeros_like(rf_spend)
258
+ prior_type = model_spec.effective_rf_prior_type
259
+ calibration_period = model_spec.rf_roi_calibration_period
260
+ if calibration_period is not None:
261
+ calibration_period = tf.convert_to_tensor(calibration_period, dtype=tf.bool)
262
+ aggregated_rf_spend = tf.convert_to_tensor(
263
+ input_data.aggregate_rf_spend(calibration_period=calibration_period),
264
+ dtype=tf.float32,
265
+ )
266
+ # Set `prior_reach_scaled_counterfactual` and `prior_denominator` depending on
267
+ # the prior type.
268
+ if prior_type == constants.TREATMENT_PRIOR_TYPE_ROI:
269
+ prior_denominator = aggregated_rf_spend
270
+ if calibration_period is None:
271
+ prior_reach_scaled_counterfactual = None
272
+ else:
273
+ prior_reach_scaled_counterfactual = (
274
+ _roi_calibration_scaled_counterfactual(
275
+ reach_scaled,
276
+ calibration_period=calibration_period,
277
+ )
278
+ )
279
+ elif prior_type == constants.TREATMENT_PRIOR_TYPE_MROI:
280
+ prior_reach_scaled_counterfactual = reach_scaled * constants.MROI_FACTOR
281
+ prior_denominator = aggregated_rf_spend * (constants.MROI_FACTOR - 1.0)
282
+ elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
283
+ prior_reach_scaled_counterfactual = None
284
+ total_outcome = tf.cast(input_data.get_total_outcome(), tf.float32)
285
+ prior_denominator = tf.repeat(total_outcome, len(input_data.rf_channel))
286
+ elif prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
287
+ prior_reach_scaled_counterfactual = None
288
+ prior_denominator = None
232
289
  else:
233
- reach_counterfactual = tf.where(
234
- model_spec.rf_roi_calibration_period, 0, reach
235
- )
236
- reach_counterfactual_scaled = tf.where(
237
- model_spec.rf_roi_calibration_period, 0, reach_scaled
238
- )
239
- n_times = len(input_data.time)
240
- rf_spend_counterfactual = tf.where(
241
- model_spec.rf_roi_calibration_period[..., -n_times:, :],
242
- 0,
243
- rf_spend,
244
- )
290
+ raise ValueError(f"Unsupported prior type: {prior_type}")
245
291
 
246
292
  return RfTensors(
247
293
  reach=reach,
@@ -249,9 +295,8 @@ def build_rf_tensors(
249
295
  rf_spend=rf_spend,
250
296
  reach_transformer=reach_transformer,
251
297
  reach_scaled=reach_scaled,
252
- reach_counterfactual=reach_counterfactual,
253
- reach_counterfactual_scaled=reach_counterfactual_scaled,
254
- rf_spend_counterfactual=rf_spend_counterfactual,
298
+ prior_reach_scaled_counterfactual=prior_reach_scaled_counterfactual,
299
+ prior_denominator=prior_denominator,
255
300
  )
256
301
 
257
302
 
@@ -266,18 +311,12 @@ class OrganicRfTensors:
266
311
  using the model's organic RF data.
267
312
  organic_reach_scaled: An organic reach tensor normalized by population and
268
313
  by the median value.
269
- organic_reach_counterfactual: An organic reach tensor with media
270
- counterfactual values.
271
- organic_reach_counterfactual_scaled: An organic reach tensor with media
272
- counterfactual scaled values.
273
314
  """
274
315
 
275
316
  organic_reach: tf.Tensor | None = None
276
317
  organic_frequency: tf.Tensor | None = None
277
318
  organic_reach_transformer: transformers.MediaTransformer | None = None
278
319
  organic_reach_scaled: tf.Tensor | None = None
279
- organic_reach_counterfactual: tf.Tensor | None = None
280
- organic_reach_counterfactual_scaled: tf.Tensor | None = None
281
320
 
282
321
 
283
322
  def build_organic_rf_tensors(
@@ -298,14 +337,10 @@ def build_organic_rf_tensors(
298
337
  tf.convert_to_tensor(input_data.population, dtype=tf.float32),
299
338
  )
300
339
  organic_reach_scaled = organic_reach_transformer.forward(organic_reach)
301
- organic_reach_counterfactual = tf.zeros_like(organic_reach)
302
- organic_reach_counterfactual_scaled = tf.zeros_like(organic_reach_scaled)
303
340
 
304
341
  return OrganicRfTensors(
305
342
  organic_reach=organic_reach,
306
343
  organic_frequency=organic_frequency,
307
344
  organic_reach_transformer=organic_reach_transformer,
308
345
  organic_reach_scaled=organic_reach_scaled,
309
- organic_reach_counterfactual=organic_reach_counterfactual,
310
- organic_reach_counterfactual_scaled=organic_reach_counterfactual_scaled,
311
346
  )