google-meridian 1.2.1__py3-none-any.whl → 1.3.1__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.
Files changed (55) hide show
  1. google_meridian-1.3.1.dist-info/METADATA +209 -0
  2. google_meridian-1.3.1.dist-info/RECORD +76 -0
  3. {google_meridian-1.2.1.dist-info → google_meridian-1.3.1.dist-info}/top_level.txt +1 -0
  4. meridian/analysis/__init__.py +2 -0
  5. meridian/analysis/analyzer.py +179 -105
  6. meridian/analysis/formatter.py +2 -2
  7. meridian/analysis/optimizer.py +227 -87
  8. meridian/analysis/review/__init__.py +20 -0
  9. meridian/analysis/review/checks.py +721 -0
  10. meridian/analysis/review/configs.py +110 -0
  11. meridian/analysis/review/constants.py +40 -0
  12. meridian/analysis/review/results.py +544 -0
  13. meridian/analysis/review/reviewer.py +186 -0
  14. meridian/analysis/summarizer.py +21 -34
  15. meridian/analysis/templates/chips.html.jinja +12 -0
  16. meridian/analysis/test_utils.py +27 -5
  17. meridian/analysis/visualizer.py +41 -57
  18. meridian/backend/__init__.py +457 -118
  19. meridian/backend/test_utils.py +162 -0
  20. meridian/constants.py +39 -3
  21. meridian/model/__init__.py +1 -0
  22. meridian/model/eda/__init__.py +3 -0
  23. meridian/model/eda/constants.py +21 -0
  24. meridian/model/eda/eda_engine.py +1309 -196
  25. meridian/model/eda/eda_outcome.py +200 -0
  26. meridian/model/eda/eda_spec.py +84 -0
  27. meridian/model/eda/meridian_eda.py +220 -0
  28. meridian/model/knots.py +55 -49
  29. meridian/model/media.py +10 -8
  30. meridian/model/model.py +79 -16
  31. meridian/model/model_test_data.py +53 -0
  32. meridian/model/posterior_sampler.py +39 -32
  33. meridian/model/prior_distribution.py +12 -2
  34. meridian/model/prior_sampler.py +146 -90
  35. meridian/model/spec.py +7 -8
  36. meridian/model/transformers.py +11 -3
  37. meridian/version.py +1 -1
  38. schema/__init__.py +18 -0
  39. schema/serde/__init__.py +26 -0
  40. schema/serde/constants.py +48 -0
  41. schema/serde/distribution.py +515 -0
  42. schema/serde/eda_spec.py +192 -0
  43. schema/serde/function_registry.py +143 -0
  44. schema/serde/hyperparameters.py +363 -0
  45. schema/serde/inference_data.py +105 -0
  46. schema/serde/marketing_data.py +1321 -0
  47. schema/serde/meridian_serde.py +413 -0
  48. schema/serde/serde.py +47 -0
  49. schema/serde/test_data.py +4608 -0
  50. schema/utils/__init__.py +17 -0
  51. schema/utils/time_record.py +156 -0
  52. google_meridian-1.2.1.dist-info/METADATA +0 -409
  53. google_meridian-1.2.1.dist-info/RECORD +0 -52
  54. {google_meridian-1.2.1.dist-info → google_meridian-1.3.1.dist-info}/WHEEL +0 -0
  55. {google_meridian-1.2.1.dist-info → google_meridian-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -48,15 +48,14 @@ def _get_tau_g(
48
48
  parameter with zero at position `baseline_geo_idx` and matching
49
49
  `tau_g_excl_baseline` elsewhere.
50
50
  """
51
- rank = len(tau_g_excl_baseline.shape)
52
- shape = tau_g_excl_baseline.shape[:-1] + [1] if rank != 1 else 1
51
+ shape = tau_g_excl_baseline.shape[:-1] + (1,)
53
52
  tau_g = backend.concatenate(
54
53
  [
55
54
  tau_g_excl_baseline[..., :baseline_geo_idx],
56
55
  backend.zeros(shape, dtype=tau_g_excl_baseline.dtype),
57
56
  tau_g_excl_baseline[..., baseline_geo_idx:],
58
57
  ],
59
- axis=rank - 1,
58
+ axis=-1,
60
59
  )
61
60
  return backend.tfd.Deterministic(tau_g, name="tau_g")
62
61
 
@@ -70,15 +69,13 @@ class PriorDistributionSampler:
70
69
  def _sample_media_priors(
71
70
  self,
72
71
  n_draws: int,
73
- seed: int | None = None,
72
+ rng_handler: backend.RNGHandler,
74
73
  ) -> Mapping[str, backend.Tensor]:
75
74
  """Draws samples from the prior distributions of the media variables.
76
75
 
77
76
  Args:
78
77
  n_draws: Number of samples drawn from the prior distribution.
79
- seed: Used to set the seed for reproducible results. For more information,
80
- see [PRNGS and seeds]
81
- (https://github.com/tensorflow/probability/blob/main/PRNGS.md).
78
+ rng_handler: The backend-agnostic RNG handler managing the seed state.
82
79
 
83
80
  Returns:
84
81
  A mapping of media parameter names to a tensor of shape `[n_draws, n_geos,
@@ -89,31 +86,47 @@ class PriorDistributionSampler:
89
86
 
90
87
  prior = mmm.prior_broadcast
91
88
  sample_shape = [1, n_draws]
92
- sample_kwargs = {constants.SAMPLE_SHAPE: sample_shape, constants.SEED: seed}
89
+
93
90
  media_vars = {
94
- constants.ALPHA_M: prior.alpha_m.sample(**sample_kwargs),
95
- constants.EC_M: prior.ec_m.sample(**sample_kwargs),
96
- constants.ETA_M: prior.eta_m.sample(**sample_kwargs),
97
- constants.SLOPE_M: prior.slope_m.sample(**sample_kwargs),
91
+ constants.ALPHA_M: prior.alpha_m.sample(
92
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
93
+ ),
94
+ constants.EC_M: prior.ec_m.sample(
95
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
96
+ ),
97
+ constants.ETA_M: prior.eta_m.sample(
98
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
99
+ ),
100
+ constants.SLOPE_M: prior.slope_m.sample(
101
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
102
+ ),
98
103
  }
99
104
  beta_gm_dev = backend.tfd.Sample(
100
105
  backend.tfd.Normal(0, 1),
101
106
  [mmm.n_geos, mmm.n_media_channels],
102
107
  name=constants.BETA_GM_DEV,
103
- ).sample(**sample_kwargs)
108
+ ).sample(sample_shape=sample_shape, seed=rng_handler.get_next_seed())
104
109
 
105
110
  prior_type = mmm.model_spec.effective_media_prior_type
106
111
  if prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
107
- media_vars[constants.BETA_M] = prior.beta_m.sample(**sample_kwargs)
112
+ media_vars[constants.BETA_M] = prior.beta_m.sample(
113
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
114
+ )
108
115
  else:
109
116
  if prior_type == constants.TREATMENT_PRIOR_TYPE_ROI:
110
- treatment_parameter_m = prior.roi_m.sample(**sample_kwargs)
117
+ treatment_parameter_m = prior.roi_m.sample(
118
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
119
+ )
111
120
  media_vars[constants.ROI_M] = treatment_parameter_m
112
121
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_MROI:
113
- treatment_parameter_m = prior.mroi_m.sample(**sample_kwargs)
122
+ treatment_parameter_m = prior.mroi_m.sample(
123
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
124
+ )
114
125
  media_vars[constants.MROI_M] = treatment_parameter_m
115
126
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
116
- treatment_parameter_m = prior.contribution_m.sample(**sample_kwargs)
127
+ treatment_parameter_m = prior.contribution_m.sample(
128
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
129
+ )
117
130
  media_vars[constants.CONTRIBUTION_M] = treatment_parameter_m
118
131
  else:
119
132
  raise ValueError(f"Unsupported prior type: {prior_type}")
@@ -125,7 +138,7 @@ class PriorDistributionSampler:
125
138
  alpha=media_vars[constants.ALPHA_M],
126
139
  ec=media_vars[constants.EC_M],
127
140
  slope=media_vars[constants.SLOPE_M],
128
- decay_functions=mmm.adstock_decay_spec.media
141
+ decay_functions=mmm.adstock_decay_spec.media,
129
142
  )
130
143
  linear_predictor_counterfactual_difference = (
131
144
  mmm.linear_predictor_counterfactual_difference_media(
@@ -144,7 +157,7 @@ class PriorDistributionSampler:
144
157
  )
145
158
  media_vars[constants.BETA_M] = backend.tfd.Deterministic(
146
159
  beta_m_value, name=constants.BETA_M
147
- ).sample()
160
+ ).sample(seed=rng_handler.get_next_seed())
148
161
 
149
162
  beta_eta_combined = (
150
163
  media_vars[constants.BETA_M][..., backend.newaxis, :]
@@ -157,22 +170,20 @@ class PriorDistributionSampler:
157
170
  )
158
171
  media_vars[constants.BETA_GM] = backend.tfd.Deterministic(
159
172
  beta_gm_value, name=constants.BETA_GM
160
- ).sample()
173
+ ).sample(seed=rng_handler.get_next_seed())
161
174
 
162
175
  return media_vars
163
176
 
164
177
  def _sample_rf_priors(
165
178
  self,
166
179
  n_draws: int,
167
- seed: int | None = None,
180
+ rng_handler: backend.RNGHandler,
168
181
  ) -> Mapping[str, backend.Tensor]:
169
182
  """Draws samples from the prior distributions of the RF variables.
170
183
 
171
184
  Args:
172
185
  n_draws: Number of samples drawn from the prior distribution.
173
- seed: Used to set the seed for reproducible results. For more information,
174
- see [PRNGS and seeds]
175
- (https://github.com/tensorflow/probability/blob/main/PRNGS.md).
186
+ rng_handler: The backend-agnostic RNG handler managing the seed state.
176
187
 
177
188
  Returns:
178
189
  A mapping of RF parameter names to a tensor of shape
@@ -183,31 +194,47 @@ class PriorDistributionSampler:
183
194
 
184
195
  prior = mmm.prior_broadcast
185
196
  sample_shape = [1, n_draws]
186
- sample_kwargs = {constants.SAMPLE_SHAPE: sample_shape, constants.SEED: seed}
197
+
187
198
  rf_vars = {
188
- constants.ALPHA_RF: prior.alpha_rf.sample(**sample_kwargs),
189
- constants.EC_RF: prior.ec_rf.sample(**sample_kwargs),
190
- constants.ETA_RF: prior.eta_rf.sample(**sample_kwargs),
191
- constants.SLOPE_RF: prior.slope_rf.sample(**sample_kwargs),
199
+ constants.ALPHA_RF: prior.alpha_rf.sample(
200
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
201
+ ),
202
+ constants.EC_RF: prior.ec_rf.sample(
203
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
204
+ ),
205
+ constants.ETA_RF: prior.eta_rf.sample(
206
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
207
+ ),
208
+ constants.SLOPE_RF: prior.slope_rf.sample(
209
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
210
+ ),
192
211
  }
193
212
  beta_grf_dev = backend.tfd.Sample(
194
213
  backend.tfd.Normal(0, 1),
195
214
  [mmm.n_geos, mmm.n_rf_channels],
196
215
  name=constants.BETA_GRF_DEV,
197
- ).sample(**sample_kwargs)
216
+ ).sample(sample_shape=sample_shape, seed=rng_handler.get_next_seed())
198
217
 
199
218
  prior_type = mmm.model_spec.effective_rf_prior_type
200
219
  if prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
201
- rf_vars[constants.BETA_RF] = prior.beta_rf.sample(**sample_kwargs)
220
+ rf_vars[constants.BETA_RF] = prior.beta_rf.sample(
221
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
222
+ )
202
223
  else:
203
224
  if prior_type == constants.TREATMENT_PRIOR_TYPE_ROI:
204
- treatment_parameter_rf = prior.roi_rf.sample(**sample_kwargs)
225
+ treatment_parameter_rf = prior.roi_rf.sample(
226
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
227
+ )
205
228
  rf_vars[constants.ROI_RF] = treatment_parameter_rf
206
229
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_MROI:
207
- treatment_parameter_rf = prior.mroi_rf.sample(**sample_kwargs)
230
+ treatment_parameter_rf = prior.mroi_rf.sample(
231
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
232
+ )
208
233
  rf_vars[constants.MROI_RF] = treatment_parameter_rf
209
234
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
210
- treatment_parameter_rf = prior.contribution_rf.sample(**sample_kwargs)
235
+ treatment_parameter_rf = prior.contribution_rf.sample(
236
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
237
+ )
211
238
  rf_vars[constants.CONTRIBUTION_RF] = treatment_parameter_rf
212
239
  else:
213
240
  raise ValueError(f"Unsupported prior type: {prior_type}")
@@ -240,7 +267,7 @@ class PriorDistributionSampler:
240
267
  rf_vars[constants.BETA_RF] = backend.tfd.Deterministic(
241
268
  beta_rf_value,
242
269
  name=constants.BETA_RF,
243
- ).sample()
270
+ ).sample(seed=rng_handler.get_next_seed())
244
271
 
245
272
  beta_eta_combined = (
246
273
  rf_vars[constants.BETA_RF][..., backend.newaxis, :]
@@ -253,22 +280,20 @@ class PriorDistributionSampler:
253
280
  )
254
281
  rf_vars[constants.BETA_GRF] = backend.tfd.Deterministic(
255
282
  beta_grf_value, name=constants.BETA_GRF
256
- ).sample()
283
+ ).sample(seed=rng_handler.get_next_seed())
257
284
 
258
285
  return rf_vars
259
286
 
260
287
  def _sample_organic_media_priors(
261
288
  self,
262
289
  n_draws: int,
263
- seed: int | None = None,
290
+ rng_handler: backend.RNGHandler,
264
291
  ) -> Mapping[str, backend.Tensor]:
265
292
  """Draws samples from the prior distributions of organic media variables.
266
293
 
267
294
  Args:
268
295
  n_draws: Number of samples drawn from the prior distribution.
269
- seed: Used to set the seed for reproducible results. For more information,
270
- see [PRNGS and seeds]
271
- (https://github.com/tensorflow/probability/blob/main/PRNGS.md).
296
+ rng_handler: The backend-agnostic RNG handler managing the seed state.
272
297
 
273
298
  Returns:
274
299
  A mapping of organic media parameter names to a tensor of shape
@@ -279,27 +304,37 @@ class PriorDistributionSampler:
279
304
 
280
305
  prior = mmm.prior_broadcast
281
306
  sample_shape = [1, n_draws]
282
- sample_kwargs = {constants.SAMPLE_SHAPE: sample_shape, constants.SEED: seed}
307
+
283
308
  organic_media_vars = {
284
- constants.ALPHA_OM: prior.alpha_om.sample(**sample_kwargs),
285
- constants.EC_OM: prior.ec_om.sample(**sample_kwargs),
286
- constants.ETA_OM: prior.eta_om.sample(**sample_kwargs),
287
- constants.SLOPE_OM: prior.slope_om.sample(**sample_kwargs),
309
+ constants.ALPHA_OM: prior.alpha_om.sample(
310
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
311
+ ),
312
+ constants.EC_OM: prior.ec_om.sample(
313
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
314
+ ),
315
+ constants.ETA_OM: prior.eta_om.sample(
316
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
317
+ ),
318
+ constants.SLOPE_OM: prior.slope_om.sample(
319
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
320
+ ),
288
321
  }
289
322
  beta_gom_dev = backend.tfd.Sample(
290
323
  backend.tfd.Normal(0, 1),
291
324
  [mmm.n_geos, mmm.n_organic_media_channels],
292
325
  name=constants.BETA_GOM_DEV,
293
- ).sample(**sample_kwargs)
326
+ ).sample(sample_shape=sample_shape, seed=rng_handler.get_next_seed())
294
327
 
295
328
  prior_type = mmm.model_spec.organic_media_prior_type
296
329
  if prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
297
330
  organic_media_vars[constants.BETA_OM] = prior.beta_om.sample(
298
- **sample_kwargs
331
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
299
332
  )
300
333
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
301
334
  organic_media_vars[constants.CONTRIBUTION_OM] = (
302
- prior.contribution_om.sample(**sample_kwargs)
335
+ prior.contribution_om.sample(
336
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
337
+ )
303
338
  )
304
339
  incremental_outcome_om = (
305
340
  organic_media_vars[constants.CONTRIBUTION_OM] * mmm.total_outcome
@@ -321,7 +356,7 @@ class PriorDistributionSampler:
321
356
  organic_media_vars[constants.BETA_OM] = backend.tfd.Deterministic(
322
357
  beta_om_value,
323
358
  name=constants.BETA_OM,
324
- ).sample()
359
+ ).sample(seed=rng_handler.get_next_seed())
325
360
  else:
326
361
  raise ValueError(f"Unsupported prior type: {prior_type}")
327
362
 
@@ -337,22 +372,20 @@ class PriorDistributionSampler:
337
372
  )
338
373
  organic_media_vars[constants.BETA_GOM] = backend.tfd.Deterministic(
339
374
  beta_gom_value, name=constants.BETA_GOM
340
- ).sample()
375
+ ).sample(seed=rng_handler.get_next_seed())
341
376
 
342
377
  return organic_media_vars
343
378
 
344
379
  def _sample_organic_rf_priors(
345
380
  self,
346
381
  n_draws: int,
347
- seed: int | None = None,
382
+ rng_handler: backend.RNGHandler,
348
383
  ) -> Mapping[str, backend.Tensor]:
349
384
  """Draws samples from the prior distributions of the organic RF variables.
350
385
 
351
386
  Args:
352
387
  n_draws: Number of samples drawn from the prior distribution.
353
- seed: Used to set the seed for reproducible results. For more information,
354
- see [PRNGS and seeds]
355
- (https://github.com/tensorflow/probability/blob/main/PRNGS.md).
388
+ rng_handler: The backend-agnostic RNG handler managing the seed state.
356
389
 
357
390
  Returns:
358
391
  A mapping of organic RF parameter names to a tensor of shape
@@ -363,27 +396,37 @@ class PriorDistributionSampler:
363
396
 
364
397
  prior = mmm.prior_broadcast
365
398
  sample_shape = [1, n_draws]
366
- sample_kwargs = {constants.SAMPLE_SHAPE: sample_shape, constants.SEED: seed}
399
+
367
400
  organic_rf_vars = {
368
- constants.ALPHA_ORF: prior.alpha_orf.sample(**sample_kwargs),
369
- constants.EC_ORF: prior.ec_orf.sample(**sample_kwargs),
370
- constants.ETA_ORF: prior.eta_orf.sample(**sample_kwargs),
371
- constants.SLOPE_ORF: prior.slope_orf.sample(**sample_kwargs),
401
+ constants.ALPHA_ORF: prior.alpha_orf.sample(
402
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
403
+ ),
404
+ constants.EC_ORF: prior.ec_orf.sample(
405
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
406
+ ),
407
+ constants.ETA_ORF: prior.eta_orf.sample(
408
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
409
+ ),
410
+ constants.SLOPE_ORF: prior.slope_orf.sample(
411
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
412
+ ),
372
413
  }
373
414
  beta_gorf_dev = backend.tfd.Sample(
374
415
  backend.tfd.Normal(0, 1),
375
416
  [mmm.n_geos, mmm.n_organic_rf_channels],
376
417
  name=constants.BETA_GORF_DEV,
377
- ).sample(**sample_kwargs)
418
+ ).sample(sample_shape=sample_shape, seed=rng_handler.get_next_seed())
378
419
 
379
420
  prior_type = mmm.model_spec.organic_media_prior_type
380
421
  if prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
381
422
  organic_rf_vars[constants.BETA_ORF] = prior.beta_orf.sample(
382
- **sample_kwargs
423
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
383
424
  )
384
425
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
385
426
  organic_rf_vars[constants.CONTRIBUTION_ORF] = (
386
- prior.contribution_orf.sample(**sample_kwargs)
427
+ prior.contribution_orf.sample(
428
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
429
+ )
387
430
  )
388
431
  incremental_outcome_orf = (
389
432
  organic_rf_vars[constants.CONTRIBUTION_ORF] * mmm.total_outcome
@@ -406,7 +449,7 @@ class PriorDistributionSampler:
406
449
  organic_rf_vars[constants.BETA_ORF] = backend.tfd.Deterministic(
407
450
  beta_orf_value,
408
451
  name=constants.BETA_ORF,
409
- ).sample()
452
+ ).sample(seed=rng_handler.get_next_seed())
410
453
  else:
411
454
  raise ValueError(f"Unsupported prior type: {prior_type}")
412
455
 
@@ -422,22 +465,20 @@ class PriorDistributionSampler:
422
465
  )
423
466
  organic_rf_vars[constants.BETA_GORF] = backend.tfd.Deterministic(
424
467
  beta_gorf_value, name=constants.BETA_GORF
425
- ).sample()
468
+ ).sample(seed=rng_handler.get_next_seed())
426
469
 
427
470
  return organic_rf_vars
428
471
 
429
472
  def _sample_non_media_treatments_priors(
430
473
  self,
431
474
  n_draws: int,
432
- seed: int | None = None,
475
+ rng_handler: backend.RNGHandler,
433
476
  ) -> Mapping[str, backend.Tensor]:
434
477
  """Draws from the prior distributions of the non-media treatment variables.
435
478
 
436
479
  Args:
437
480
  n_draws: Number of samples drawn from the prior distribution.
438
- seed: Used to set the seed for reproducible results. For more information,
439
- see [PRNGS and seeds]
440
- (https://github.com/tensorflow/probability/blob/main/PRNGS.md).
481
+ rng_handler: The backend-agnostic RNG handler managing the seed state.
441
482
 
442
483
  Returns:
443
484
  A mapping of non-media treatment parameter names to a tensor of shape
@@ -448,23 +489,27 @@ class PriorDistributionSampler:
448
489
 
449
490
  prior = mmm.prior_broadcast
450
491
  sample_shape = [1, n_draws]
451
- sample_kwargs = {constants.SAMPLE_SHAPE: sample_shape, constants.SEED: seed}
492
+
452
493
  non_media_treatments_vars = {
453
- constants.XI_N: prior.xi_n.sample(**sample_kwargs),
494
+ constants.XI_N: prior.xi_n.sample(
495
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
496
+ ),
454
497
  }
455
498
  gamma_gn_dev = backend.tfd.Sample(
456
499
  backend.tfd.Normal(0, 1),
457
500
  [mmm.n_geos, mmm.n_non_media_channels],
458
501
  name=constants.GAMMA_GN_DEV,
459
- ).sample(**sample_kwargs)
502
+ ).sample(sample_shape=sample_shape, seed=rng_handler.get_next_seed())
460
503
  prior_type = mmm.model_spec.non_media_treatments_prior_type
461
504
  if prior_type == constants.TREATMENT_PRIOR_TYPE_COEFFICIENT:
462
505
  non_media_treatments_vars[constants.GAMMA_N] = prior.gamma_n.sample(
463
- **sample_kwargs
506
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
464
507
  )
465
508
  elif prior_type == constants.TREATMENT_PRIOR_TYPE_CONTRIBUTION:
466
509
  non_media_treatments_vars[constants.CONTRIBUTION_N] = (
467
- prior.contribution_n.sample(**sample_kwargs)
510
+ prior.contribution_n.sample(
511
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
512
+ )
468
513
  )
469
514
  incremental_outcome_n = (
470
515
  non_media_treatments_vars[constants.CONTRIBUTION_N]
@@ -485,7 +530,7 @@ class PriorDistributionSampler:
485
530
  )
486
531
  non_media_treatments_vars[constants.GAMMA_N] = backend.tfd.Deterministic(
487
532
  gamma_n_value, name=constants.GAMMA_N
488
- ).sample()
533
+ ).sample(seed=rng_handler.get_next_seed())
489
534
  else:
490
535
  raise ValueError(f"Unsupported prior type: {prior_type}")
491
536
  non_media_treatments_vars[constants.GAMMA_GN] = backend.tfd.Deterministic(
@@ -493,7 +538,7 @@ class PriorDistributionSampler:
493
538
  + non_media_treatments_vars[constants.XI_N][..., backend.newaxis, :]
494
539
  * gamma_gn_dev,
495
540
  name=constants.GAMMA_GN,
496
- ).sample()
541
+ ).sample(seed=rng_handler.get_next_seed())
497
542
  return non_media_treatments_vars
498
543
 
499
544
  def _sample_prior(
@@ -509,21 +554,28 @@ class PriorDistributionSampler:
509
554
  if seed is not None:
510
555
  backend.set_random_seed(seed)
511
556
 
557
+ rng_handler = backend.RNGHandler(seed)
558
+
512
559
  prior = mmm.prior_broadcast
513
560
  # `sample_shape` is prepended to the shape of each BatchBroadcast in `prior`
514
561
  # when it is sampled.
515
562
  sample_shape = [1, n_draws]
516
- sample_kwargs = {constants.SAMPLE_SHAPE: sample_shape, constants.SEED: seed}
517
563
 
518
- tau_g_excl_baseline = prior.tau_g_excl_baseline.sample(**sample_kwargs)
564
+ tau_g_excl_baseline = prior.tau_g_excl_baseline.sample(
565
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
566
+ )
519
567
  base_vars = {
520
- constants.KNOT_VALUES: prior.knot_values.sample(**sample_kwargs),
521
- constants.SIGMA: prior.sigma.sample(**sample_kwargs),
568
+ constants.KNOT_VALUES: prior.knot_values.sample(
569
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
570
+ ),
571
+ constants.SIGMA: prior.sigma.sample(
572
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
573
+ ),
522
574
  constants.TAU_G: (
523
575
  _get_tau_g(
524
576
  tau_g_excl_baseline=tau_g_excl_baseline,
525
577
  baseline_geo_idx=mmm.baseline_geo_idx,
526
- ).sample()
578
+ ).sample(seed=rng_handler.get_next_seed())
527
579
  ),
528
580
  }
529
581
 
@@ -534,49 +586,53 @@ class PriorDistributionSampler:
534
586
  backend.to_tensor(mmm.knot_info.weights),
535
587
  ),
536
588
  name=constants.MU_T,
537
- ).sample()
589
+ ).sample(seed=rng_handler.get_next_seed())
538
590
 
539
591
  # Omit gamma_c, xi_c, and gamma_gc parameters from sampled distributions if
540
592
  # there are no control variables in the model.
541
593
  if mmm.n_controls:
542
594
  base_vars |= {
543
- constants.GAMMA_C: prior.gamma_c.sample(**sample_kwargs),
544
- constants.XI_C: prior.xi_c.sample(**sample_kwargs),
595
+ constants.GAMMA_C: prior.gamma_c.sample(
596
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
597
+ ),
598
+ constants.XI_C: prior.xi_c.sample(
599
+ sample_shape=sample_shape, seed=rng_handler.get_next_seed()
600
+ ),
545
601
  }
546
602
 
547
603
  gamma_gc_dev = backend.tfd.Sample(
548
604
  backend.tfd.Normal(0, 1),
549
605
  [mmm.n_geos, mmm.n_controls],
550
606
  name=constants.GAMMA_GC_DEV,
551
- ).sample(**sample_kwargs)
607
+ ).sample(sample_shape=sample_shape, seed=rng_handler.get_next_seed())
552
608
  base_vars[constants.GAMMA_GC] = backend.tfd.Deterministic(
553
609
  base_vars[constants.GAMMA_C][..., backend.newaxis, :]
554
610
  + base_vars[constants.XI_C][..., backend.newaxis, :] * gamma_gc_dev,
555
611
  name=constants.GAMMA_GC,
556
- ).sample()
612
+ ).sample(seed=rng_handler.get_next_seed())
557
613
 
558
614
  media_vars = (
559
- self._sample_media_priors(n_draws, seed)
615
+ self._sample_media_priors(n_draws, rng_handler)
560
616
  if mmm.media_tensors.media is not None
561
617
  else {}
562
618
  )
563
619
  rf_vars = (
564
- self._sample_rf_priors(n_draws, seed)
620
+ self._sample_rf_priors(n_draws, rng_handler)
565
621
  if mmm.rf_tensors.reach is not None
566
622
  else {}
567
623
  )
568
624
  organic_media_vars = (
569
- self._sample_organic_media_priors(n_draws, seed)
625
+ self._sample_organic_media_priors(n_draws, rng_handler)
570
626
  if mmm.organic_media_tensors.organic_media is not None
571
627
  else {}
572
628
  )
573
629
  organic_rf_vars = (
574
- self._sample_organic_rf_priors(n_draws, seed)
630
+ self._sample_organic_rf_priors(n_draws, rng_handler)
575
631
  if mmm.organic_rf_tensors.organic_reach is not None
576
632
  else {}
577
633
  )
578
634
  non_media_treatments_vars = (
579
- self._sample_non_media_treatments_priors(n_draws, seed)
635
+ self._sample_non_media_treatments_priors(n_draws, rng_handler)
580
636
  if mmm.non_media_treatments_normalized is not None
581
637
  else {}
582
638
  )
meridian/model/spec.py CHANGED
@@ -203,14 +203,13 @@ class ModelSpec:
203
203
  non-media value will be scaled by population. If `None`, then no non-media
204
204
  variables are scaled by population. Default: `None`.
205
205
  adstock_decay_spec: A string or mapping specifying the adstock decay
206
- function for each media, RF, organic media and organic RF channel.
207
- * If a string, must be either `'geometric'` or `'binomial'`, specifying
208
- that decay function for all channels.
209
- * If a mapping, keys should be channel names and values should be
210
- `'geometric'` or `'binomial'`, with each key-value pair denoting the
211
- adstock decay function to use for that channel. Channels that are not
212
- specified in the mapping default to using 'geometric'.
213
- Default: `'geometric'`.
206
+ function for each media, RF, organic media and organic RF channel. If a
207
+ string, must be either `'geometric'` or `'binomial'`, specifying that
208
+ decay function for all channels. If a mapping, keys should be channel
209
+ names and values should be `'geometric'` or `'binomial'`, with each
210
+ key-value pair denoting the adstock decay function to use for that
211
+ channel. Channels that are not specified in the mapping default to using
212
+ 'geometric'. Default: `'geometric'`.
214
213
  enable_aks: A boolean indicating whether to use the Automatic Knot Selection
215
214
  algorithm to select an optimal number of knots for running the model
216
215
  instead of the default 1 for national models and n_times for geo models.
@@ -196,11 +196,19 @@ class KpiTransformer(TensorTransformer):
196
196
  each geo, used to to compute the population scale factors.
197
197
  """
198
198
  self._population = population
199
- population_scaled_kpi = backend.divide_no_nan(
199
+ self._population_scaled_kpi = backend.divide_no_nan(
200
200
  kpi, self._population[:, backend.newaxis]
201
201
  )
202
- self._population_scaled_mean = backend.reduce_mean(population_scaled_kpi)
203
- self._population_scaled_stdev = backend.reduce_std(population_scaled_kpi)
202
+ self._population_scaled_mean = backend.reduce_mean(
203
+ self._population_scaled_kpi
204
+ )
205
+ self._population_scaled_stdev = backend.reduce_std(
206
+ self._population_scaled_kpi
207
+ )
208
+
209
+ @property
210
+ def population_scaled_kpi(self):
211
+ return self._population_scaled_kpi
204
212
 
205
213
  @property
206
214
  def population_scaled_mean(self):
meridian/version.py CHANGED
@@ -14,4 +14,4 @@
14
14
 
15
15
  """Module for Meridian version."""
16
16
 
17
- __version__ = "1.2.1"
17
+ __version__ = "1.3.1"
schema/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ # Copyright 2025 The Meridian Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Module containing MMM schema library."""
16
+
17
+ from schema import serde
18
+ from schema import utils
@@ -0,0 +1,26 @@
1
+ # Copyright 2025 The Meridian Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """A serialization and deserialization library for Meridian models.
16
+
17
+ For entry points API, see `meridian_serde` module docs.
18
+ """
19
+
20
+ from schema.serde import constants
21
+ from schema.serde import distribution
22
+ from schema.serde import eda_spec
23
+ from schema.serde import hyperparameters
24
+ from schema.serde import inference_data
25
+ from schema.serde import meridian_serde
26
+ from schema.serde import serde