google-meridian 1.2.0__tar.gz → 1.2.1__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 (63) hide show
  1. {google_meridian-1.2.0/google_meridian.egg-info → google_meridian-1.2.1}/PKG-INFO +2 -2
  2. {google_meridian-1.2.0 → google_meridian-1.2.1}/README.md +1 -1
  3. {google_meridian-1.2.0 → google_meridian-1.2.1/google_meridian.egg-info}/PKG-INFO +2 -2
  4. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/analyzer.py +101 -37
  5. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/optimizer.py +132 -88
  6. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/summarizer.py +31 -16
  7. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/visualizer.py +16 -5
  8. google_meridian-1.2.1/meridian/backend/__init__.py +975 -0
  9. google_meridian-1.2.1/meridian/backend/config.py +118 -0
  10. google_meridian-1.2.1/meridian/backend/test_utils.py +181 -0
  11. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/constants.py +14 -9
  12. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/input_data.py +7 -2
  13. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/test_utils.py +5 -3
  14. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/mlflow/autolog.py +2 -2
  15. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/adstock_hill.py +10 -9
  16. google_meridian-1.2.1/meridian/model/eda/eda_engine.py +735 -0
  17. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/knots.py +1 -1
  18. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/model_test_data.py +15 -9
  19. google_meridian-1.2.1/meridian/model/posterior_sampler.py +668 -0
  20. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/prior_distribution.py +104 -39
  21. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/transformers.py +5 -5
  22. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/version.py +1 -1
  23. google_meridian-1.2.0/meridian/backend/__init__.py +0 -514
  24. google_meridian-1.2.0/meridian/backend/config.py +0 -59
  25. google_meridian-1.2.0/meridian/backend/test_utils.py +0 -95
  26. google_meridian-1.2.0/meridian/model/eda/eda_engine.py +0 -306
  27. google_meridian-1.2.0/meridian/model/posterior_sampler.py +0 -668
  28. {google_meridian-1.2.0 → google_meridian-1.2.1}/LICENSE +0 -0
  29. {google_meridian-1.2.0 → google_meridian-1.2.1}/MANIFEST.in +0 -0
  30. {google_meridian-1.2.0 → google_meridian-1.2.1}/google_meridian.egg-info/SOURCES.txt +0 -0
  31. {google_meridian-1.2.0 → google_meridian-1.2.1}/google_meridian.egg-info/dependency_links.txt +0 -0
  32. {google_meridian-1.2.0 → google_meridian-1.2.1}/google_meridian.egg-info/requires.txt +0 -0
  33. {google_meridian-1.2.0 → google_meridian-1.2.1}/google_meridian.egg-info/top_level.txt +0 -0
  34. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/__init__.py +0 -0
  35. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/__init__.py +0 -0
  36. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/formatter.py +0 -0
  37. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/summary_text.py +0 -0
  38. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/card.html.jinja +0 -0
  39. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/chart.html.jinja +0 -0
  40. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/chips.html.jinja +0 -0
  41. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/insights.html.jinja +0 -0
  42. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/stats.html.jinja +0 -0
  43. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/style.scss +0 -0
  44. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/summary.html.jinja +0 -0
  45. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/templates/table.html.jinja +0 -0
  46. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/analysis/test_utils.py +0 -0
  47. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/__init__.py +0 -0
  48. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/arg_builder.py +0 -0
  49. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/data_frame_input_data_builder.py +0 -0
  50. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/input_data_builder.py +0 -0
  51. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/load.py +0 -0
  52. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/nd_array_input_data_builder.py +0 -0
  53. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/data/time_coordinates.py +0 -0
  54. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/mlflow/__init__.py +0 -0
  55. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/__init__.py +0 -0
  56. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/eda/__init__.py +0 -0
  57. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/media.py +0 -0
  58. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/model.py +0 -0
  59. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/prior_sampler.py +0 -0
  60. {google_meridian-1.2.0 → google_meridian-1.2.1}/meridian/model/spec.py +0 -0
  61. {google_meridian-1.2.0 → google_meridian-1.2.1}/pyproject.toml +0 -0
  62. {google_meridian-1.2.0 → google_meridian-1.2.1}/setup.cfg +0 -0
  63. {google_meridian-1.2.0 → google_meridian-1.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-meridian
3
- Version: 1.2.0
3
+ Version: 1.2.1
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:
@@ -403,7 +403,7 @@ To cite this repository:
403
403
  author = {Google Meridian Marketing Mix Modeling Team},
404
404
  title = {Meridian: Marketing Mix Modeling},
405
405
  url = {https://github.com/google/meridian},
406
- version = {1.2.0},
406
+ version = {1.2.1},
407
407
  year = {2025},
408
408
  }
409
409
  ```
@@ -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.2.0},
154
+ version = {1.2.1},
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.2.0
3
+ Version: 1.2.1
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:
@@ -403,7 +403,7 @@ To cite this repository:
403
403
  author = {Google Meridian Marketing Mix Modeling Team},
404
404
  title = {Meridian: Marketing Mix Modeling},
405
405
  url = {https://github.com/google/meridian},
406
- version = {1.2.0},
406
+ version = {1.2.1},
407
407
  year = {2025},
408
408
  }
409
409
  ```
@@ -675,43 +675,66 @@ def _validate_flexible_selected_times(
675
675
  selected_times: Sequence[str] | Sequence[bool] | None,
676
676
  media_selected_times: Sequence[str] | Sequence[bool] | None,
677
677
  new_n_media_times: int,
678
+ new_time: Sequence[str] | None = None,
678
679
  ):
679
680
  """Raises an error if selected times or media selected times is invalid.
680
681
 
681
- This checks that the `selected_times` and `media_selected_times` arguments
682
- are lists of booleans with the same number of elements as `new_n_media_times`.
683
- This is only relevant if the time dimension of any of the variables in
684
- `new_data` used in the analysis is modified.
682
+ This checks that (1) the `selected_times` and `media_selected_times` arguments
683
+ are lists of booleans with the same number of elements as `new_n_media_times`,
684
+ or (2) the `selected_times` and `media_selected_times` arguments are lists of
685
+ strings and the `new_time` list is provided and `selected_times` and
686
+ `media_selected_times` are subsets of `new_time`. This is only relevant if the
687
+ time dimension of any of the variables in `new_data` used in the analysis is
688
+ modified.
685
689
 
686
690
  Args:
687
691
  selected_times: Optional list of times to validate.
688
692
  media_selected_times: Optional list of media times to validate.
689
693
  new_n_media_times: The number of time periods in the new data.
694
+ new_time: The optional time dimension of the new data.
690
695
  """
691
696
  if selected_times and (
692
- not _is_bool_list(selected_times)
693
- or len(selected_times) != new_n_media_times
697
+ not (
698
+ _is_bool_list(selected_times)
699
+ and len(selected_times) == new_n_media_times
700
+ )
701
+ and not (
702
+ _is_str_list(selected_times)
703
+ and new_time is not None
704
+ and set(selected_times) <= set(new_time)
705
+ )
694
706
  ):
695
707
  raise ValueError(
696
708
  "If `media`, `reach`, `frequency`, `organic_media`,"
697
709
  " `organic_reach`, `organic_frequency`, `non_media_treatments`, or"
698
710
  " `revenue_per_kpi` is provided with a different number of time"
699
- " periods than in `InputData`, then `selected_times` must be a list"
711
+ " periods than in `InputData`, then (1) `selected_times` must be a list"
700
712
  " of booleans with length equal to the number of time periods in"
701
- " the new data."
713
+ " the new data, or (2) `selected_times` must be a list of strings and"
714
+ " `new_time` must be provided and `selected_times` must be a subset of"
715
+ " `new_time`."
702
716
  )
703
717
 
704
718
  if media_selected_times and (
705
- not _is_bool_list(media_selected_times)
706
- or len(media_selected_times) != new_n_media_times
719
+ not (
720
+ _is_bool_list(media_selected_times)
721
+ and len(media_selected_times) == new_n_media_times
722
+ )
723
+ and not (
724
+ _is_str_list(media_selected_times)
725
+ and new_time is not None
726
+ and set(media_selected_times) <= set(new_time)
727
+ )
707
728
  ):
708
729
  raise ValueError(
709
730
  "If `media`, `reach`, `frequency`, `organic_media`,"
710
731
  " `organic_reach`, `organic_frequency`, `non_media_treatments`, or"
711
732
  " `revenue_per_kpi` is provided with a different number of time"
712
- " periods than in `InputData`, then `media_selected_times` must be"
733
+ " periods than in `InputData`, then (1) `media_selected_times` must be"
713
734
  " a list of booleans with length equal to the number of time"
714
- " periods in the new data."
735
+ " periods in the new data, or (2) `media_selected_times` must be a list"
736
+ " of strings and `new_time` must be provided and"
737
+ " `media_selected_times` must be a subset of `new_time`."
715
738
  )
716
739
 
717
740
 
@@ -4056,6 +4079,7 @@ class Analyzer:
4056
4079
 
4057
4080
  def response_curves(
4058
4081
  self,
4082
+ new_data: DataTensors | None = None,
4059
4083
  spend_multipliers: list[float] | None = None,
4060
4084
  use_posterior: bool = True,
4061
4085
  selected_geos: Sequence[str] | None = None,
@@ -4081,6 +4105,15 @@ class Analyzer:
4081
4105
  `selected_times` are also scaled by the multiplier.)
4082
4106
 
4083
4107
  Args:
4108
+ new_data: Optional `DataTensors` object with optional new tensors:
4109
+ `media`, `reach`, `frequency`, `media_spend`, `rf_spend`,
4110
+ `revenue_per_kpi`, `times`. If provided, the response curves are
4111
+ calculated using the values of the tensors passed in `new_data` and the
4112
+ original values of all the remaining tensors. If `None`, the response
4113
+ curves are calculated using the original values of all the tensors. If
4114
+ any of the tensors in `new_data` is provided with a different number of
4115
+ time periods than in `InputData`, then all tensors must be provided with
4116
+ the same number of time periods and the `time` tensor must be provided.
4084
4117
  spend_multipliers: List of multipliers. Each channel's total spend is
4085
4118
  multiplied by these factors to obtain the values at which the curve is
4086
4119
  calculated for that channel.
@@ -4088,8 +4121,11 @@ class Analyzer:
4088
4121
  generated. If `False`, prior response curves are generated.
4089
4122
  selected_geos: Optional list containing a subset of geos to include. By
4090
4123
  default, all geos are included.
4091
- selected_times: Optional list containing a subset of dates to include. By
4092
- default, all time periods are included.
4124
+ selected_times: Optional list containing a subset of dates to include. If
4125
+ `new_data` is provided with modified time periods, then `selected_times`
4126
+ must be a subset of `new_data.times`. Otherwise, `selected_times` must
4127
+ be a subset of `self._meridian.input_data.time`. By default, all time
4128
+ periods are included.
4093
4129
  by_reach: Boolean. For channels with reach and frequency. If `True`, plots
4094
4130
  the response curve by reach. If `False`, plots the response curve by
4095
4131
  frequency.
@@ -4118,11 +4154,49 @@ class Analyzer:
4118
4154
  "aggregate_geos": True,
4119
4155
  "aggregate_times": True,
4120
4156
  }
4157
+ if new_data is None:
4158
+ new_data = DataTensors()
4159
+ # TODO: b/442920356 - Support flexible time without providing exact dates.
4160
+ required_tensors_names = constants.PERFORMANCE_DATA + (constants.TIME,)
4161
+ filled_data = new_data.validate_and_fill_missing_data(
4162
+ required_tensors_names=required_tensors_names,
4163
+ meridian=self._meridian,
4164
+ allow_modified_times=True,
4165
+ )
4166
+ new_n_media_times = filled_data.get_modified_times(self._meridian)
4167
+
4168
+ if new_n_media_times is None:
4169
+ _validate_selected_times(
4170
+ selected_times=selected_times,
4171
+ input_times=self._meridian.input_data.time,
4172
+ n_times=self._meridian.n_times,
4173
+ arg_name="selected_times",
4174
+ comparison_arg_name="the input data",
4175
+ )
4176
+ else:
4177
+ new_time = np.asarray(filled_data.time).astype(str).tolist()
4178
+ _validate_flexible_selected_times(
4179
+ selected_times=selected_times,
4180
+ media_selected_times=None,
4181
+ new_n_media_times=new_n_media_times,
4182
+ new_time=new_time,
4183
+ )
4184
+ # TODO: b/407847021 - Switch to Sequence[str] once it is supported.
4185
+ if selected_times is not None:
4186
+ selected_times = [x in selected_times for x in new_time]
4187
+ dim_kwargs["selected_times"] = selected_times
4188
+
4121
4189
  if self._meridian.n_rf_channels > 0 and use_optimal_frequency:
4122
- frequency = backend.ones_like(
4123
- self._meridian.rf_tensors.frequency
4124
- ) * backend.to_tensor(
4190
+ opt_freq_data = DataTensors(
4191
+ media=filled_data.media,
4192
+ rf_impressions=filled_data.reach * filled_data.frequency,
4193
+ media_spend=filled_data.media_spend,
4194
+ rf_spend=filled_data.rf_spend,
4195
+ revenue_per_kpi=filled_data.revenue_per_kpi,
4196
+ )
4197
+ frequency = backend.ones_like(filled_data.frequency) * backend.to_tensor(
4125
4198
  self.optimal_freq(
4199
+ new_data=opt_freq_data,
4126
4200
  selected_geos=selected_geos,
4127
4201
  selected_times=selected_times,
4128
4202
  use_kpi=use_kpi,
@@ -4130,12 +4204,12 @@ class Analyzer:
4130
4204
  dtype=backend.float32,
4131
4205
  )
4132
4206
  reach = backend.divide_no_nan(
4133
- self._meridian.rf_tensors.reach * self._meridian.rf_tensors.frequency,
4207
+ filled_data.reach * filled_data.frequency,
4134
4208
  frequency,
4135
4209
  )
4136
4210
  else:
4137
- frequency = self._meridian.rf_tensors.frequency
4138
- reach = self._meridian.rf_tensors.reach
4211
+ frequency = filled_data.frequency
4212
+ reach = filled_data.reach
4139
4213
  if spend_multipliers is None:
4140
4214
  spend_multipliers = list(np.arange(0, 2.2, 0.2))
4141
4215
  incremental_outcome = np.zeros((
@@ -4149,18 +4223,19 @@ class Analyzer:
4149
4223
  (len(self._meridian.input_data.get_all_paid_channels()), 3)
4150
4224
  ) # Last dimension = 3 for the mean, ci_lo and ci_hi.
4151
4225
  continue
4152
- new_data = _scale_tensors_by_multiplier(
4226
+ scaled_data = _scale_tensors_by_multiplier(
4153
4227
  data=DataTensors(
4154
- media=self._meridian.media_tensors.media,
4228
+ media=filled_data.media,
4155
4229
  reach=reach,
4156
4230
  frequency=frequency,
4231
+ revenue_per_kpi=filled_data.revenue_per_kpi,
4157
4232
  ),
4158
4233
  multiplier=multiplier,
4159
4234
  by_reach=by_reach,
4160
4235
  )
4161
4236
  inc_outcome_temp = self.incremental_outcome(
4162
4237
  use_posterior=use_posterior,
4163
- new_data=new_data.filter_fields(constants.PAID_DATA),
4238
+ new_data=scaled_data.filter_fields(constants.PAID_DATA),
4164
4239
  inverse_transform_outcome=True,
4165
4240
  batch_size=batch_size,
4166
4241
  use_kpi=use_kpi,
@@ -4171,22 +4246,11 @@ class Analyzer:
4171
4246
  inc_outcome_temp, confidence_level
4172
4247
  )
4173
4248
 
4174
- if self._meridian.n_media_channels > 0 and self._meridian.n_rf_channels > 0:
4175
- spend = backend.concatenate(
4176
- [
4177
- self._meridian.media_tensors.media_spend,
4178
- self._meridian.rf_tensors.rf_spend,
4179
- ],
4180
- axis=-1,
4181
- )
4182
- elif self._meridian.n_media_channels > 0:
4183
- spend = self._meridian.media_tensors.media_spend
4184
- else:
4185
- spend = self._meridian.rf_tensors.rf_spend
4186
-
4187
- if backend.rank(spend) == 3:
4249
+ spend = filled_data.total_spend()
4250
+ if spend is not None and spend.ndim == 3:
4188
4251
  spend = self.filter_and_aggregate_geos_and_times(
4189
4252
  tensor=spend,
4253
+ flexible_time_dim=True,
4190
4254
  **dim_kwargs,
4191
4255
  )
4192
4256
  spend_einsum = backend.einsum("k,m->km", np.array(spend_multipliers), spend)