google-meridian 1.1.2__tar.gz → 1.1.4__tar.gz

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 (54) hide show
  1. {google_meridian-1.1.2/google_meridian.egg-info → google_meridian-1.1.4}/PKG-INFO +2 -2
  2. {google_meridian-1.1.2 → google_meridian-1.1.4}/README.md +1 -1
  3. {google_meridian-1.1.2 → google_meridian-1.1.4/google_meridian.egg-info}/PKG-INFO +2 -2
  4. {google_meridian-1.1.2 → google_meridian-1.1.4}/google_meridian.egg-info/SOURCES.txt +1 -0
  5. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/__init__.py +6 -4
  6. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/analyzer.py +68 -25
  7. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/optimizer.py +298 -48
  8. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/constants.py +3 -0
  9. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/data_frame_input_data_builder.py +41 -0
  10. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/input_data_builder.py +12 -4
  11. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/load.py +262 -346
  12. google_meridian-1.1.4/meridian/mlflow/autolog.py +206 -0
  13. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/media.py +7 -0
  14. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/model.py +14 -16
  15. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/posterior_sampler.py +13 -9
  16. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/prior_sampler.py +4 -6
  17. google_meridian-1.1.4/meridian/version.py +17 -0
  18. {google_meridian-1.1.2 → google_meridian-1.1.4}/pyproject.toml +1 -1
  19. google_meridian-1.1.2/meridian/mlflow/autolog.py +0 -54
  20. {google_meridian-1.1.2 → google_meridian-1.1.4}/LICENSE +0 -0
  21. {google_meridian-1.1.2 → google_meridian-1.1.4}/MANIFEST.in +0 -0
  22. {google_meridian-1.1.2 → google_meridian-1.1.4}/google_meridian.egg-info/dependency_links.txt +0 -0
  23. {google_meridian-1.1.2 → google_meridian-1.1.4}/google_meridian.egg-info/requires.txt +0 -0
  24. {google_meridian-1.1.2 → google_meridian-1.1.4}/google_meridian.egg-info/top_level.txt +0 -0
  25. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/__init__.py +0 -0
  26. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/formatter.py +0 -0
  27. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/summarizer.py +0 -0
  28. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/summary_text.py +0 -0
  29. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/card.html.jinja +0 -0
  30. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/chart.html.jinja +0 -0
  31. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/chips.html.jinja +0 -0
  32. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/insights.html.jinja +0 -0
  33. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/stats.html.jinja +0 -0
  34. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/style.scss +0 -0
  35. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/summary.html.jinja +0 -0
  36. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/templates/table.html.jinja +0 -0
  37. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/test_utils.py +0 -0
  38. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/analysis/visualizer.py +0 -0
  39. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/__init__.py +0 -0
  40. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/arg_builder.py +0 -0
  41. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/input_data.py +0 -0
  42. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/nd_array_input_data_builder.py +0 -0
  43. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/test_utils.py +0 -0
  44. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/data/time_coordinates.py +0 -0
  45. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/mlflow/__init__.py +0 -0
  46. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/__init__.py +0 -0
  47. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/adstock_hill.py +0 -0
  48. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/knots.py +0 -0
  49. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/model_test_data.py +0 -0
  50. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/prior_distribution.py +0 -0
  51. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/spec.py +0 -0
  52. {google_meridian-1.1.2 → google_meridian-1.1.4}/meridian/model/transformers.py +0 -0
  53. {google_meridian-1.1.2 → google_meridian-1.1.4}/setup.cfg +0 -0
  54. {google_meridian-1.1.2 → google_meridian-1.1.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-meridian
3
- Version: 1.1.2
3
+ Version: 1.1.4
4
4
  Summary: Google's open source mixed marketing model library, helps you understand your return on investment and direct your ad spend with confidence.
5
5
  Author-email: The Meridian Authors <no-reply@google.com>
6
6
  License:
@@ -397,7 +397,7 @@ To cite this repository:
397
397
  author = {Google Meridian Marketing Mix Modeling Team},
398
398
  title = {Meridian: Marketing Mix Modeling},
399
399
  url = {https://github.com/google/meridian},
400
- version = {1.1.2},
400
+ version = {1.1.4},
401
401
  year = {2025},
402
402
  }
403
403
  ```
@@ -151,7 +151,7 @@ To cite this repository:
151
151
  author = {Google Meridian Marketing Mix Modeling Team},
152
152
  title = {Meridian: Marketing Mix Modeling},
153
153
  url = {https://github.com/google/meridian},
154
- version = {1.1.2},
154
+ version = {1.1.4},
155
155
  year = {2025},
156
156
  }
157
157
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-meridian
3
- Version: 1.1.2
3
+ Version: 1.1.4
4
4
  Summary: Google's open source mixed marketing model library, helps you understand your return on investment and direct your ad spend with confidence.
5
5
  Author-email: The Meridian Authors <no-reply@google.com>
6
6
  License:
@@ -397,7 +397,7 @@ To cite this repository:
397
397
  author = {Google Meridian Marketing Mix Modeling Team},
398
398
  title = {Meridian: Marketing Mix Modeling},
399
399
  url = {https://github.com/google/meridian},
400
- version = {1.1.2},
400
+ version = {1.1.4},
401
401
  year = {2025},
402
402
  }
403
403
  ```
@@ -10,6 +10,7 @@ google_meridian.egg-info/requires.txt
10
10
  google_meridian.egg-info/top_level.txt
11
11
  meridian/__init__.py
12
12
  meridian/constants.py
13
+ meridian/version.py
13
14
  meridian/analysis/__init__.py
14
15
  meridian/analysis/analyzer.py
15
16
  meridian/analysis/formatter.py
@@ -13,10 +13,12 @@
13
13
  # limitations under the License.
14
14
 
15
15
  """Meridian API."""
16
-
17
- __version__ = "1.1.2"
18
-
19
-
20
16
  from meridian import analysis
21
17
  from meridian import data
22
18
  from meridian import model
19
+ from meridian.version import __version__
20
+
21
+ try:
22
+ from meridian import mlflow # pylint: disable=g-import-not-at-top
23
+ except ImportError:
24
+ pass
@@ -59,14 +59,20 @@ class DataTensors(tf.experimental.ExtensionType):
59
59
  Attributes:
60
60
  media: Optional tensor with dimensions `(n_geos, T, n_media_channels)` for
61
61
  any time dimension `T`.
62
- media_spend: Optional tensor with dimensions `(n_geos, T, n_media_channels)`
63
- for any time dimension `T`.
62
+ media_spend: Optional tensor with dimensions `(n_media_channels,)` or
63
+ `(n_geos, T, n_media_channels)` for any time dimension `T`. If the object
64
+ includes variables with modified time periods, then this tensor must be
65
+ provided at the geo and time granularity.
64
66
  reach: Optional tensor with dimensions `(n_geos, T, n_rf_channels)` for any
65
67
  time dimension `T`.
66
68
  frequency: Optional tensor with dimensions `(n_geos, T, n_rf_channels)` for
67
69
  any time dimension `T`.
68
- rf_spend: Optional tensor with dimensions `(n_geos, T, n_rf_channels)` for
69
- any time dimension `T`.
70
+ rf_impressions: Optional tensor with dimensions `(n_geos, T, n_rf_channels)`
71
+ for any time dimension `T`.
72
+ rf_spend: Optional tensor with dimensions `(n_rf_channels,)` or `(n_geos, T,
73
+ n_rf_channels)` for any time dimension `T`. If the object includes
74
+ variables with modified time periods, then this tensor must be provided at
75
+ the geo and time granularity.
70
76
  organic_media: Optional tensor with dimensions `(n_geos, T,
71
77
  n_organic_media_channels)` for any time dimension `T`.
72
78
  organic_reach: Optional tensor with dimensions `(n_geos, T,
@@ -86,6 +92,7 @@ class DataTensors(tf.experimental.ExtensionType):
86
92
  media_spend: Optional[tf.Tensor]
87
93
  reach: Optional[tf.Tensor]
88
94
  frequency: Optional[tf.Tensor]
95
+ rf_impressions: Optional[tf.Tensor]
89
96
  rf_spend: Optional[tf.Tensor]
90
97
  organic_media: Optional[tf.Tensor]
91
98
  organic_reach: Optional[tf.Tensor]
@@ -101,6 +108,7 @@ class DataTensors(tf.experimental.ExtensionType):
101
108
  media_spend: Optional[tf.Tensor] = None,
102
109
  reach: Optional[tf.Tensor] = None,
103
110
  frequency: Optional[tf.Tensor] = None,
111
+ rf_impressions: Optional[tf.Tensor] = None,
104
112
  rf_spend: Optional[tf.Tensor] = None,
105
113
  organic_media: Optional[tf.Tensor] = None,
106
114
  organic_reach: Optional[tf.Tensor] = None,
@@ -118,6 +126,11 @@ class DataTensors(tf.experimental.ExtensionType):
118
126
  self.frequency = (
119
127
  tf.cast(frequency, tf.float32) if frequency is not None else None
120
128
  )
129
+ self.rf_impressions = (
130
+ tf.cast(rf_impressions, tf.float32)
131
+ if rf_impressions is not None
132
+ else None
133
+ )
121
134
  self.rf_spend = (
122
135
  tf.cast(rf_spend, tf.float32) if rf_spend is not None else None
123
136
  )
@@ -189,7 +202,10 @@ class DataTensors(tf.experimental.ExtensionType):
189
202
  """
190
203
  for field in self._tf_extension_type_fields():
191
204
  new_tensor = getattr(self, field.name)
192
- old_tensor = getattr(meridian.input_data, field.name)
205
+ if field.name == constants.RF_IMPRESSIONS:
206
+ old_tensor = getattr(meridian.rf_tensors, field.name)
207
+ else:
208
+ old_tensor = getattr(meridian.input_data, field.name)
193
209
  # The time dimension is always the second dimension, except for when spend
194
210
  # data is provided with only one dimension of (n_channels).
195
211
  if (
@@ -293,7 +309,13 @@ class DataTensors(tf.experimental.ExtensionType):
293
309
  "This is not supported and will be ignored."
294
310
  )
295
311
  if field.name in required_variables:
296
- if getattr(meridian.input_data, field.name) is None:
312
+ if field.name == constants.RF_IMPRESSIONS:
313
+ if meridian.n_rf_channels == 0:
314
+ raise ValueError(
315
+ "New `rf_impressions` is not allowed because there are no R&F"
316
+ " channels in the Meridian model."
317
+ )
318
+ elif getattr(meridian.input_data, field.name) is None:
297
319
  raise ValueError(
298
320
  f"New `{field.name}` is not allowed because the input data to the"
299
321
  f" Meridian model does not contain `{field.name}`."
@@ -322,7 +344,10 @@ class DataTensors(tf.experimental.ExtensionType):
322
344
  if var_name in [constants.REVENUE_PER_KPI, constants.TIME]:
323
345
  continue
324
346
  new_tensor = getattr(self, var_name)
325
- old_tensor = getattr(meridian.input_data, var_name)
347
+ if var_name == constants.RF_IMPRESSIONS:
348
+ old_tensor = getattr(meridian.rf_tensors, var_name)
349
+ else:
350
+ old_tensor = getattr(meridian.input_data, var_name)
326
351
  if new_tensor is not None:
327
352
  assert old_tensor is not None
328
353
  if new_tensor.shape[-1] != old_tensor.shape[-1]:
@@ -337,7 +362,10 @@ class DataTensors(tf.experimental.ExtensionType):
337
362
  """Validates the time dimension of the specified data variables."""
338
363
  for var_name in required_fields:
339
364
  new_tensor = getattr(self, var_name)
340
- old_tensor = getattr(meridian.input_data, var_name)
365
+ if var_name == constants.RF_IMPRESSIONS:
366
+ old_tensor = getattr(meridian.rf_tensors, var_name)
367
+ else:
368
+ old_tensor = getattr(meridian.input_data, var_name)
341
369
 
342
370
  # Skip spend data with only 1 dimension of (n_channels).
343
371
  if (
@@ -375,17 +403,13 @@ class DataTensors(tf.experimental.ExtensionType):
375
403
  missing_params = []
376
404
  for var_name in required_fields:
377
405
  new_tensor = getattr(self, var_name)
378
- old_tensor = getattr(meridian.input_data, var_name)
406
+ if var_name == constants.RF_IMPRESSIONS:
407
+ old_tensor = getattr(meridian.rf_tensors, var_name)
408
+ else:
409
+ old_tensor = getattr(meridian.input_data, var_name)
379
410
 
380
411
  if old_tensor is None:
381
412
  continue
382
- # Skip spend data with only 1 dimension of (n_channels).
383
- if (
384
- var_name in [constants.MEDIA_SPEND, constants.RF_SPEND]
385
- and new_tensor is not None
386
- and new_tensor.ndim == 1
387
- ):
388
- continue
389
413
 
390
414
  if new_tensor is None:
391
415
  missing_params.append(var_name)
@@ -397,6 +421,16 @@ class DataTensors(tf.experimental.ExtensionType):
397
421
  "time periods, which does not match the modified number of time "
398
422
  f"periods, {new_n_times}.",
399
423
  )
424
+ elif (
425
+ var_name in [constants.MEDIA_SPEND, constants.RF_SPEND]
426
+ and new_tensor.ndim == 1
427
+ ):
428
+ raise ValueError(
429
+ "If the time dimension of any variable in `new_data` is modified, "
430
+ "then spend variables must be provided at the geo and time "
431
+ "granularity with thee same number of time periods as the other "
432
+ f"new data variables. Found `{var_name}` with only 1 dimension."
433
+ )
400
434
  elif new_tensor.ndim > 1 and new_tensor.shape[1] != new_n_times:
401
435
  raise ValueError(
402
436
  "If the time dimension of any variable in `new_data` is "
@@ -3415,6 +3449,7 @@ class Analyzer:
3415
3449
  def optimal_freq(
3416
3450
  self,
3417
3451
  new_data: DataTensors | None = None,
3452
+ max_frequency: float | None = None,
3418
3453
  freq_grid: Sequence[float] | None = None,
3419
3454
  use_posterior: bool = True,
3420
3455
  use_kpi: bool = False,
@@ -3443,7 +3478,7 @@ class Analyzer:
3443
3478
  ROI numerator is KPI units.
3444
3479
 
3445
3480
  Args:
3446
- new_data: Optional `DataTensors` object containing `reach`, `frequency`,
3481
+ new_data: Optional `DataTensors` object containing `rf_impressions`,
3447
3482
  `rf_spend`, and `revenue_per_kpi`. If provided, the optimal frequency is
3448
3483
  calculated using the values of the tensors passed in `new_data` and the
3449
3484
  original values of all the remaining tensors. If `None`, the historical
@@ -3451,6 +3486,10 @@ class Analyzer:
3451
3486
  tensors in `new_data` is provided with a different number of time
3452
3487
  periods than in `InputData`, then all tensors must be provided with the
3453
3488
  same number of time periods.
3489
+ max_frequency: Maximum frequency value used to calculate the frequency
3490
+ grid. If `None`, the maximum frequency value is calculated from the
3491
+ historic frequency (maximum value of Meridian.input_data, not
3492
+ `new_data`). If `freq_grid` is provided, this argument has no effect.
3454
3493
  freq_grid: List of frequency values. The ROI of each channel is calculated
3455
3494
  for each frequency value in the list. By default, the list includes
3456
3495
  numbers from `1.0` to the maximum frequency in increments of `0.1`.
@@ -3506,7 +3545,11 @@ class Analyzer:
3506
3545
  )
3507
3546
 
3508
3547
  filled_data = new_data.validate_and_fill_missing_data(
3509
- constants.RF_DATA,
3548
+ [
3549
+ constants.RF_IMPRESSIONS,
3550
+ constants.RF_SPEND,
3551
+ constants.REVENUE_PER_KPI,
3552
+ ],
3510
3553
  self._meridian,
3511
3554
  )
3512
3555
  # TODO: Once treatment type filtering is added, remove adding
@@ -3527,7 +3570,9 @@ class Analyzer:
3527
3570
  (self._meridian.n_geos, n_times, self._meridian.n_media_channels)
3528
3571
  )
3529
3572
 
3530
- max_freq = np.max(np.array(filled_data.frequency))
3573
+ max_freq = max_frequency or np.max(
3574
+ np.array(self._meridian.rf_tensors.frequency)
3575
+ )
3531
3576
  if freq_grid is None:
3532
3577
  freq_grid = np.arange(1, max_freq, 0.1)
3533
3578
 
@@ -3537,8 +3582,8 @@ class Analyzer:
3537
3582
  metric_grid = np.zeros((len(freq_grid), self._meridian.n_rf_channels, 4))
3538
3583
 
3539
3584
  for i, freq in enumerate(freq_grid):
3540
- new_frequency = tf.ones_like(filled_data.frequency) * freq
3541
- new_reach = filled_data.frequency * filled_data.reach / new_frequency
3585
+ new_frequency = tf.ones_like(filled_data.rf_impressions) * freq
3586
+ new_reach = filled_data.rf_impressions / new_frequency
3542
3587
  new_roi_data = DataTensors(
3543
3588
  reach=new_reach,
3544
3589
  frequency=new_frequency,
@@ -3568,12 +3613,10 @@ class Analyzer:
3568
3613
 
3569
3614
  optimal_frequency = [freq_grid[i] for i in optimal_freq_idx]
3570
3615
  optimal_frequency_tensor = tf.convert_to_tensor(
3571
- tf.ones_like(filled_data.frequency) * optimal_frequency,
3616
+ tf.ones_like(filled_data.rf_impressions) * optimal_frequency,
3572
3617
  tf.float32,
3573
3618
  )
3574
- optimal_reach = (
3575
- filled_data.frequency * filled_data.reach / optimal_frequency_tensor
3576
- )
3619
+ optimal_reach = filled_data.rf_impressions / optimal_frequency_tensor
3577
3620
 
3578
3621
  new_summary_metrics_data = DataTensors(
3579
3622
  reach=optimal_reach,