google-meridian 1.2.0__py3-none-any.whl → 1.2.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.
@@ -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
  ```
@@ -1,15 +1,15 @@
1
- google_meridian-1.2.0.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
1
+ google_meridian-1.2.1.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
2
2
  meridian/__init__.py,sha256=0fOT5oNZF7-pbiWWGUefV-ysafttieG079m1ijMFQO8,861
3
- meridian/constants.py,sha256=PZLPEDmqzTER3nzt84qV83WPulb-ScwYfq9nNfBBVkk,18312
4
- meridian/version.py,sha256=eV51cCZSYYHp8DRnwq4HDZVPlzL9bcnUV6t9V0FIaf4,644
3
+ meridian/constants.py,sha256=19A4Hzhfdrdj6CzjoqKxqTg3d3wbTGHgvz9TBwdIRZg,18485
4
+ meridian/version.py,sha256=2wFL4gzqzx21aqU1XHRxIuP5xZOZFXVxiDQUG5v38do,644
5
5
  meridian/analysis/__init__.py,sha256=nGBYz7k9FVdadO_WVGMKJcfq7Yy_TuuP8zgee4i9pSA,836
6
- meridian/analysis/analyzer.py,sha256=92so-WVfuoaEuouJwY_MdQz75lvOOF9RhSk-3rhmQ_Y,214705
6
+ meridian/analysis/analyzer.py,sha256=ftcjavIPKy1y0uL_uEKfFaSYnWdQYMc8YBnw8p7mf6g,217876
7
7
  meridian/analysis/formatter.py,sha256=ENIdR1CRiaVqIGEXx1HcnsA4ewgDD_nhsYCweJAThaw,7270
8
- meridian/analysis/optimizer.py,sha256=rGUGqpORlHQyyPN0ceR0rngpaQhDzLMzxfMcwIr4ksE,118267
9
- meridian/analysis/summarizer.py,sha256=IthOUTMufGvAvbxiDhaKwe7uYCyiTyiQ8vgdmUtdevs,18855
8
+ meridian/analysis/optimizer.py,sha256=4LhEbPBz8NtWWzwLuYiAhkuTiLfIJbhhdBPn973EVeQ,120492
9
+ meridian/analysis/summarizer.py,sha256=VF4uuZ5xavrfZdLk1Cwf1ZPrkiQ2-kVnLkjvpWtcZ98,19367
10
10
  meridian/analysis/summary_text.py,sha256=I_smDkZJYp2j77ea-9AIbgeraDa7-qUYyb-IthP2qO4,12438
11
11
  meridian/analysis/test_utils.py,sha256=z5NBjcOzvM7xwwTFI_26sfLyyGLYCd21j52eygQHBRs,77911
12
- meridian/analysis/visualizer.py,sha256=z3j2_BRlv7980FGpsDGxVQJFlVfkTvA8dhx1Qtv5apo,94025
12
+ meridian/analysis/visualizer.py,sha256=RgrqIBRFG_HJBlz_IcOVaB3jSeXpD5PHLSkkGy2rWJs,94537
13
13
  meridian/analysis/templates/card.html.jinja,sha256=pv4MVbQ25CcvtZY-LH7bFW0OSeHobkeEkAleB1sfQ14,1284
14
14
  meridian/analysis/templates/chart.html.jinja,sha256=87i0xnXHRBoLLxBpKv2i960TLToWq4r1aVQZqaXIeMQ,1086
15
15
  meridian/analysis/templates/chips.html.jinja,sha256=Az0tQwF_-b03JDLyOzpeH-8fb-6jgJgbNfnUUSm-q6E,645
@@ -19,34 +19,34 @@ meridian/analysis/templates/style.css,sha256=RODTWc2pXcG9zW3q9SEJpVXgeD-WwQgzLpm
19
19
  meridian/analysis/templates/style.scss,sha256=nSrZOpcIrVyiL4eC9jLUlxIZtAKZ0Rt8pwfk4H1nMrs,5076
20
20
  meridian/analysis/templates/summary.html.jinja,sha256=LuENVDHYIpNo4pzloYaCR2K9XN1Ow6_9oQOcOwD9nGg,1707
21
21
  meridian/analysis/templates/table.html.jinja,sha256=mvLMZx92RcD2JAS2w2eZtfYG-6WdfwYVo7pM8TbHp4g,1176
22
- meridian/backend/__init__.py,sha256=rInUMct5eggmYlHifRXMGi1iLdPFGtsnNVoqF-HKS5E,14556
23
- meridian/backend/config.py,sha256=eEBqYm31BVYUmmio-EyQ2A0zHxvHBkX8atWyPeCRakw,1661
24
- meridian/backend/test_utils.py,sha256=CovPYZpX9_Me3eRU2iUSY22vKXKNkz3gWtrVAAIpB0U,3202
22
+ meridian/backend/__init__.py,sha256=9DCcwp-9hIr66vciMhdkROndO40AAlJXjtrAbFihero,28454
23
+ meridian/backend/config.py,sha256=B9VQnhBfg9RW04GNbt7F5uCugByenoJzt-keFLLYEp8,3561
24
+ meridian/backend/test_utils.py,sha256=jmlFdo3mQrDG-gmxd8GqYAhi_GdAXRUjRkHxRY0z_1A,6410
25
25
  meridian/data/__init__.py,sha256=StIe-wfYnnbfUbKtZHwnAQcRQUS8XCZk_PCaEzw90Ww,929
26
26
  meridian/data/arg_builder.py,sha256=Kqlt88bOqFj6D3xNwvWo4MBwNwcDFHzd-wMfEOmLoPU,3741
27
27
  meridian/data/data_frame_input_data_builder.py,sha256=_hexZMFAuAowgo6FaOGElHSFHqhGnHQwEEBcwnT3zUE,27295
28
- meridian/data/input_data.py,sha256=QYxm8Ri1Pk6CbGRbYWnSFFHncbei6bOu4w-nVfo27l0,43078
28
+ meridian/data/input_data.py,sha256=Qlxm4El6h1SRPsWDqZoKkOcMtrjiRWr3z8sU2mtghRA,43151
29
29
  meridian/data/input_data_builder.py,sha256=tbZjVXPDfmtndVyJA0fmzGzZwZb0RCEjXOTXb-ga8Nc,25648
30
30
  meridian/data/load.py,sha256=X2nmYCC-7A0RUgmdolTqCt0TD3NEZabQ5oGv-TugE00,40129
31
31
  meridian/data/nd_array_input_data_builder.py,sha256=lfpmnENGuSGKyUd7bDGAwoLqHqteOKmHdKl0VI2wCQA,16341
32
- meridian/data/test_utils.py,sha256=-Z6OUkDYjamtNVja-7SqHIrjJ7miBDXYQT8wZK_0txk,59763
32
+ meridian/data/test_utils.py,sha256=mw-QPTP15oXf32I7cxMe8iSFBLB3seqEiITZMTz_Eg8,59838
33
33
  meridian/data/time_coordinates.py,sha256=C5A5fscSLjPH6G9YT8OspgIlCrkMY7y8dMFEt3tNSnE,9874
34
34
  meridian/mlflow/__init__.py,sha256=elwXUqPQYi7VF9PYjelU1tydfcUrmtuoq6eJCOnV9bk,693
35
- meridian/mlflow/autolog.py,sha256=s240eLGAurzaNsulwRlyM1ZdBLvUzyr2eOMYgOyWAzk,6393
35
+ meridian/mlflow/autolog.py,sha256=SZsrynLjozcyrAFCNWiqchSa2yOszVnwFBGz23BmWUU,6379
36
36
  meridian/model/__init__.py,sha256=9NFfqUE5WgFc-9lQMkbfkwwV-bQIz0tsQ_3Jyq0A4SU,982
37
- meridian/model/adstock_hill.py,sha256=v07yiFNF1O8G79qUKtTdR5TbQVsaocg8Dwm6xEP_PVE,17631
38
- meridian/model/knots.py,sha256=OZ1zDNhzZmt-hh5hri-CuifEum0oRVZdHUqdkkNpstE,25979
37
+ meridian/model/adstock_hill.py,sha256=HoRKjyL03pCTBz6Utof9wEvlQCFM43BvrEW_oupj7NU,17688
38
+ meridian/model/knots.py,sha256=1geYCcWHjXy1Uzwi5bxIuHaIY2rfSYVfneEdPetcMA8,25978
39
39
  meridian/model/media.py,sha256=ZWYLN2iHLYWYrSRyR03L8DgFdVtgRs37JfTVjbzynBU,14364
40
40
  meridian/model/model.py,sha256=D6pQOD0A7ZE7MhDd2tLMSB7sVmDwyzJ82DuKaF_auVs,66516
41
- meridian/model/model_test_data.py,sha256=VKYyA9y1OFviVpVO_SALi1N7BJBjlVrVvl0P2xUiJvs,21733
42
- meridian/model/posterior_sampler.py,sha256=7i46E8oBR6zH756i-UylQ2FBxt4flN3h-PcrF4xycVY,28168
43
- meridian/model/prior_distribution.py,sha256=y0Dzcg4m-_ah7Vcs3GWSVfLsNYLxftDXFWtuCmxieU4,54526
41
+ meridian/model/model_test_data.py,sha256=cG_8vBMRJJ3O63uVDz4x1tdJRaVQXsYQtQvfScBoXV4,21878
42
+ meridian/model/posterior_sampler.py,sha256=xFnvRw-Arz8j6a2oj5ywOCN1r5JcbZRADBWOrdVPFE8,26910
43
+ meridian/model/prior_distribution.py,sha256=xHNysn9sGjQL4xUOyoeMbTJwYyCmTbXykvEA9rNkkXY,57081
44
44
  meridian/model/prior_sampler.py,sha256=IKkVF-TUN3tFvnHKRrXI3A5slKq1vPcUn5e3u9UsfPU,23422
45
45
  meridian/model/spec.py,sha256=6ioI_wU6E2c-i7oltJUjBYHb3FxhRCyhWtuCx8GqkFA,19206
46
- meridian/model/transformers.py,sha256=jbGTib5Yj4qutXOHPLeHEromLeUkKeaRJh3PNr8vIhs,8258
46
+ meridian/model/transformers.py,sha256=68pf6-6nG2eAgrE-ZM30S-cWCmNTftrRQZKjIoVHRO4,8230
47
47
  meridian/model/eda/__init__.py,sha256=MrGW1NER0JNsnWJRO5aeFEmMTDVYi4HgMmLXZaw_bbk,685
48
- meridian/model/eda/eda_engine.py,sha256=WJX3LW6DfV9z9i5wksrOSINj5zJh-nXhGLXZuVnZONM,10715
49
- google_meridian-1.2.0.dist-info/METADATA,sha256=MzZqcHOEVUJvfDapEsG1HewOUMBU_fAM1VEEP7RyoJY,22462
50
- google_meridian-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- google_meridian-1.2.0.dist-info/top_level.txt,sha256=nwaCebZvvU34EopTKZsjK0OMTFjVnkf4FfnBN_TAc0g,9
52
- google_meridian-1.2.0.dist-info/RECORD,,
48
+ meridian/model/eda/eda_engine.py,sha256=QjOFril0NTonBg9mqx6nrBUfK5F_A53UcX-l6QSqgo8,25462
49
+ google_meridian-1.2.1.dist-info/METADATA,sha256=zfGooQwkjN_mFUPDu7Y09SZ6euAEo_zaG4V_RAUt0T4,22462
50
+ google_meridian-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ google_meridian-1.2.1.dist-info/top_level.txt,sha256=nwaCebZvvU34EopTKZsjK0OMTFjVnkf4FfnBN_TAc0g,9
52
+ google_meridian-1.2.1.dist-info/RECORD,,
@@ -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)