google-meridian 1.3.0__py3-none-any.whl → 1.3.2__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 (52) hide show
  1. google_meridian-1.3.2.dist-info/METADATA +209 -0
  2. google_meridian-1.3.2.dist-info/RECORD +76 -0
  3. {google_meridian-1.3.0.dist-info → google_meridian-1.3.2.dist-info}/top_level.txt +1 -0
  4. meridian/analysis/__init__.py +1 -2
  5. meridian/analysis/analyzer.py +0 -1
  6. meridian/analysis/optimizer.py +5 -3
  7. meridian/analysis/review/checks.py +81 -30
  8. meridian/analysis/review/constants.py +4 -0
  9. meridian/analysis/review/results.py +40 -9
  10. meridian/analysis/summarizer.py +1 -1
  11. meridian/analysis/visualizer.py +1 -1
  12. meridian/backend/__init__.py +229 -24
  13. meridian/backend/test_utils.py +194 -0
  14. meridian/constants.py +1 -0
  15. meridian/data/load.py +2 -0
  16. meridian/model/eda/__init__.py +0 -1
  17. meridian/model/eda/constants.py +12 -2
  18. meridian/model/eda/eda_engine.py +353 -45
  19. meridian/model/eda/eda_outcome.py +21 -1
  20. meridian/model/knots.py +17 -0
  21. meridian/model/model_test_data.py +15 -0
  22. meridian/{analysis/templates → templates}/card.html.jinja +1 -1
  23. meridian/{analysis/templates → templates}/chart.html.jinja +1 -1
  24. meridian/{analysis/templates → templates}/chips.html.jinja +1 -1
  25. meridian/{analysis → templates}/formatter.py +12 -1
  26. meridian/templates/formatter_test.py +216 -0
  27. meridian/{analysis/templates → templates}/insights.html.jinja +1 -1
  28. meridian/{analysis/templates → templates}/stats.html.jinja +1 -1
  29. meridian/{analysis/templates → templates}/style.css +1 -1
  30. meridian/{analysis/templates → templates}/style.scss +1 -1
  31. meridian/{analysis/templates → templates}/summary.html.jinja +4 -2
  32. meridian/{analysis/templates → templates}/table.html.jinja +1 -1
  33. meridian/version.py +1 -1
  34. schema/__init__.py +30 -0
  35. schema/serde/__init__.py +26 -0
  36. schema/serde/constants.py +48 -0
  37. schema/serde/distribution.py +515 -0
  38. schema/serde/eda_spec.py +192 -0
  39. schema/serde/function_registry.py +143 -0
  40. schema/serde/hyperparameters.py +363 -0
  41. schema/serde/inference_data.py +105 -0
  42. schema/serde/marketing_data.py +1321 -0
  43. schema/serde/meridian_serde.py +413 -0
  44. schema/serde/serde.py +47 -0
  45. schema/serde/test_data.py +4608 -0
  46. schema/utils/__init__.py +17 -0
  47. schema/utils/time_record.py +156 -0
  48. google_meridian-1.3.0.dist-info/METADATA +0 -409
  49. google_meridian-1.3.0.dist-info/RECORD +0 -62
  50. meridian/model/eda/meridian_eda.py +0 -220
  51. {google_meridian-1.3.0.dist-info → google_meridian-1.3.2.dist-info}/WHEEL +0 -0
  52. {google_meridian-1.3.0.dist-info → google_meridian-1.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -29,6 +29,7 @@ __all__ = [
29
29
  "StandardDeviationArtifact",
30
30
  "VIFArtifact",
31
31
  "KpiInvariabilityArtifact",
32
+ "CostPerMediaUnitArtifact",
32
33
  "EDACheckType",
33
34
  "ArtifactType",
34
35
  "EDAOutcome",
@@ -101,7 +102,8 @@ class PairwiseCorrArtifact(AnalysisArtifact):
101
102
  Attributes:
102
103
  corr_matrix: Pairwise correlation matrix.
103
104
  extreme_corr_var_pairs: DataFrame of variable pairs exceeding the
104
- correlation threshold.
105
+ correlation threshold. Includes 'correlation' and 'abs_correlation'
106
+ columns, and is sorted by 'abs_correlation' in descending order.
105
107
  extreme_corr_threshold: The threshold used to identify extreme correlation
106
108
  pairs.
107
109
  """
@@ -153,6 +155,23 @@ class KpiInvariabilityArtifact(AnalysisArtifact):
153
155
  kpi_stdev: xr.DataArray
154
156
 
155
157
 
158
+ @dataclasses.dataclass(frozen=True)
159
+ class CostPerMediaUnitArtifact(AnalysisArtifact):
160
+ """Encapsulates artifacts from a Cost per Media Unit analysis.
161
+
162
+ Attributes:
163
+ cost_per_media_unit_da: DataArray of cost per media unit.
164
+ cost_media_unit_inconsistency_df: DataFrame of time periods where cost and
165
+ media units are inconsistent (e.g., zero cost with positive media units,
166
+ or positive cost with zero media units).
167
+ outlier_df: DataFrame with outliers of cost per media unit.
168
+ """
169
+
170
+ cost_per_media_unit_da: xr.DataArray
171
+ cost_media_unit_inconsistency_df: pd.DataFrame
172
+ outlier_df: pd.DataFrame
173
+
174
+
156
175
  @enum.unique
157
176
  class EDACheckType(enum.Enum):
158
177
  """Enumeration for the type of an EDA check."""
@@ -161,6 +180,7 @@ class EDACheckType(enum.Enum):
161
180
  STANDARD_DEVIATION = enum.auto()
162
181
  MULTICOLLINEARITY = enum.auto()
163
182
  KPI_INVARIABILITY = enum.auto()
183
+ COST_PER_MEDIA_UNIT = enum.auto()
164
184
 
165
185
 
166
186
  ArtifactType = typing.TypeVar("ArtifactType", bound="AnalysisArtifact")
meridian/model/knots.py CHANGED
@@ -19,6 +19,7 @@ from collections.abc import Collection, Sequence
19
19
  import copy
20
20
  import dataclasses
21
21
  import math
22
+ import pprint
22
23
  from typing import Any
23
24
  from meridian import constants
24
25
  from meridian.data import input_data
@@ -289,6 +290,22 @@ class AKS:
289
290
  penalty = geo_scaling_factor * base_penalty
290
291
 
291
292
  aspline = self.aspline(x=x, y=y, knots=knots, penalty=penalty)
293
+ # Ensure defined knot range covers at least one of the available knot sets.
294
+ available_knots_lengths = np.unique(
295
+ np.fromiter(
296
+ (len(x) for x in aspline[constants.KNOTS_SELECTED]), dtype=int
297
+ )
298
+ ).tolist()
299
+ if not any(
300
+ min_internal_knots <= k <= max_internal_knots
301
+ for k in available_knots_lengths
302
+ ):
303
+ raise ValueError(
304
+ f'The range [{min_internal_knots}, {max_internal_knots}] does not'
305
+ ' contain any of the available knot lengths:'
306
+ f' {pprint.pformat(available_knots_lengths)}'
307
+ )
308
+
292
309
  n_knots = np.array([len(x) for x in aspline[constants.KNOTS_SELECTED]])
293
310
  feasible_idx = np.where(
294
311
  (n_knots >= min_internal_knots) & (n_knots <= max_internal_knots)
@@ -143,6 +143,7 @@ class WithInputDataSamples:
143
143
  _short_input_data_with_rf_only: input_data.InputData
144
144
  _short_input_data_with_media_and_rf: input_data.InputData
145
145
  _national_input_data_media_only: input_data.InputData
146
+ _national_input_data_rf_only: input_data.InputData
146
147
  _national_input_data_media_and_rf: input_data.InputData
147
148
  _test_dist_media_and_rf: collections.OrderedDict[str, backend.Tensor]
148
149
  _test_dist_media_only: collections.OrderedDict[str, backend.Tensor]
@@ -282,6 +283,16 @@ class WithInputDataSamples:
282
283
  seed=0,
283
284
  )
284
285
  )
286
+ cls._national_input_data_rf_only = (
287
+ test_utils.sample_input_data_non_revenue_revenue_per_kpi(
288
+ n_geos=cls._N_GEOS_NATIONAL,
289
+ n_times=cls._N_TIMES,
290
+ n_media_times=cls._N_MEDIA_TIMES,
291
+ n_controls=cls._N_CONTROLS,
292
+ n_rf_channels=cls._N_RF_CHANNELS,
293
+ seed=0,
294
+ )
295
+ )
285
296
  cls._national_input_data_media_only = (
286
297
  test_utils.sample_input_data_non_revenue_revenue_per_kpi(
287
298
  n_geos=cls._N_GEOS_NATIONAL,
@@ -581,6 +592,10 @@ class WithInputDataSamples:
581
592
  def national_input_data_media_only(self) -> input_data.InputData:
582
593
  return self._national_input_data_media_only.copy(deep=True)
583
594
 
595
+ @property
596
+ def national_input_data_rf_only(self) -> input_data.InputData:
597
+ return self._national_input_data_rf_only.copy(deep=True)
598
+
584
599
  @property
585
600
  def national_input_data_media_and_rf(self) -> input_data.InputData:
586
601
  return self._national_input_data_media_and_rf.copy(deep=True)
@@ -1,5 +1,5 @@
1
1
  {#
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  {#
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  {#
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -88,7 +88,7 @@ AXIS_CONFIG = immutabledict.immutabledict({
88
88
 
89
89
 
90
90
  _template_loader = jinja2.FileSystemLoader(
91
- os.path.abspath(os.path.dirname(__file__)) + '/templates'
91
+ os.path.abspath(os.path.dirname(__file__))
92
92
  )
93
93
 
94
94
 
@@ -206,6 +206,17 @@ def create_template_env() -> jinja2.Environment:
206
206
  )
207
207
 
208
208
 
209
+ def create_summary_html(
210
+ template_env: jinja2.Environment,
211
+ title: str,
212
+ cards: Sequence[str],
213
+ ) -> str:
214
+ """Creates the HTML snippet for the summary page."""
215
+ return template_env.get_template('summary.html.jinja').render(
216
+ title=title, cards=cards
217
+ )
218
+
219
+
209
220
  def create_card_html(
210
221
  template_env: jinja2.Environment,
211
222
  card_spec: CardSpec,
@@ -0,0 +1,216 @@
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
+ from xml.etree import ElementTree as ET
16
+
17
+ from absl.testing import absltest
18
+ from absl.testing import parameterized
19
+ from meridian.templates import formatter
20
+
21
+
22
+ class FormatterTest(parameterized.TestCase):
23
+
24
+ def test_custom_title_params_correct(self):
25
+ title_params = formatter.custom_title_params('test title')
26
+ self.assertEqual(
27
+ title_params.to_dict(),
28
+ {
29
+ 'anchor': 'start',
30
+ 'color': '#3C4043',
31
+ 'font': 'Google Sans Display',
32
+ 'fontSize': 18,
33
+ 'fontWeight': 'normal',
34
+ 'offset': 10,
35
+ 'text': 'test title',
36
+ },
37
+ )
38
+
39
+ def test_bar_chart_width(self):
40
+ num_bars = 3
41
+ width = formatter.bar_chart_width(num_bars)
42
+ self.assertEqual(width, 186)
43
+
44
+ @parameterized.named_parameters(
45
+ ('zero_percent', 0.0, '0%'),
46
+ ('less_than_one_percent', 0.0005, '0.05%'),
47
+ ('one_percent', 0.01, '1%'),
48
+ ('greater_than_one_percent', 0.4257, '43%'),
49
+ )
50
+ def test_format_percent_correct(self, percent, expected):
51
+ formatted_percent = formatter.format_percent(percent)
52
+ self.assertEqual(formatted_percent, expected)
53
+
54
+ def test_compact_number_expr_default(self):
55
+ expr = formatter.compact_number_expr()
56
+ self.assertEqual(expr, "replace(format(datum.value, '.3~s'), 'G', 'B')")
57
+
58
+ def test_compact_number_expr_params(self):
59
+ expr = formatter.compact_number_expr('other', 2)
60
+ self.assertEqual(expr, "replace(format(datum.other, '.2~s'), 'G', 'B')")
61
+
62
+ @parameterized.named_parameters(
63
+ ('rounded_up_percent', 0.4257, 15, '42.6% (15)'),
64
+ ('rounded_down_percent', 0.4251, 15, '42.5% (15)'),
65
+ ('thousand_value', 0.42, 2e4, '42.0% (20k)'),
66
+ ('million_value', 0.42, 3e7, '42.0% (30M)'),
67
+ ('billion_value', 0.42, 4e9, '42.0% (4B)'),
68
+ )
69
+ def test_format_number_text_correct(self, percent, value, expected):
70
+ formatted_text = formatter.format_number_text(percent, value)
71
+ self.assertEqual(formatted_text, expected)
72
+
73
+ @parameterized.named_parameters(
74
+ ('zero_precision_thousands', 12345, '$', '$12k'),
75
+ ('round_up_thousands', 14900, '€', '€15k'),
76
+ ('million_value', 3.21e6, '£', '£3.2M'),
77
+ ('billion_value_round_up', 4.28e9, '¥', '¥4.3B'),
78
+ ('negative', -12345, '₮', '-₮12k'),
79
+ )
80
+ def test_format_monetary_num_correct(self, num, currency, expected):
81
+ formatted_number = formatter.format_monetary_num(num, currency)
82
+ self.assertEqual(formatted_number, expected)
83
+
84
+ @parameterized.named_parameters(
85
+ ('decimals', -0.1234, 2, '$', '-$0.12'),
86
+ ('zero_precision_thousands', 12345, 0, '', '12k'),
87
+ ('round_up_thousands', 14900, 0, '$', '$15k'),
88
+ ('million_value', 3.21e6, 2, '$', '$3.21M'),
89
+ ('negative', -12345, 0, '$', '-$12k'),
90
+ )
91
+ def test_compact_number_correct(self, num, precision, currency, expected):
92
+ formatted_number = formatter.compact_number(num, precision, currency)
93
+ self.assertEqual(formatted_number, expected)
94
+
95
+ def test_create_summary_html(self):
96
+ template_env = formatter.create_template_env()
97
+ title = 'Integration Test Report'
98
+ cards = ['<card>Card 1</card>', '<card>Card 2</card>']
99
+
100
+ html_result = formatter.create_summary_html(template_env, title, cards)
101
+
102
+ # Since summary.html contains DOCTYPE (which breaks ElementTree XML parser),
103
+ # we verify the output using string assertions.
104
+ self.assertIn('<!DOCTYPE html>', html_result)
105
+ self.assertIn(title, html_result)
106
+ self.assertIn('<card>Card 1</card>', html_result)
107
+ self.assertIn('<card>Card 2</card>', html_result)
108
+
109
+ def test_create_card_html_structure(self):
110
+ template_env = formatter.create_template_env()
111
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
112
+ stats_spec = formatter.StatsSpec(title='stats_title', stat='test_stat')
113
+ chart_spec = formatter.ChartSpec(
114
+ 'test_chart_id', 'test_chart_json', 'test_chart_description'
115
+ )
116
+
117
+ card_html = ET.fromstring(
118
+ formatter.create_card_html(
119
+ template_env, card_spec, 'test_insights', [chart_spec], [stats_spec]
120
+ )
121
+ )
122
+ self.assertEqual(card_html.tag, 'card')
123
+ self.assertLen(card_html, 4)
124
+ self.assertEqual(card_html[0].tag, 'card-title')
125
+ self.assertEqual(card_html[1].tag, 'card-insights')
126
+ self.assertEqual(card_html[2].tag, 'stats-section')
127
+ self.assertEqual(card_html[3].tag, 'charts')
128
+
129
+ def test_create_card_html_text(self):
130
+ template_env = formatter.create_template_env()
131
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
132
+ chart_spec = formatter.ChartSpec(
133
+ 'test_chart_id', 'test_chart_json', 'test_chart_description'
134
+ )
135
+ card_html = ET.fromstring(
136
+ formatter.create_card_html(
137
+ template_env, card_spec, 'test_insights', [chart_spec]
138
+ )
139
+ )
140
+ self.assertContainsSubset('test_title', card_html[0].text)
141
+ self.assertContainsSubset('test_insights', card_html[1][1].text)
142
+
143
+ def test_create_card_html_multiple_charts(self):
144
+ template_env = formatter.create_template_env()
145
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
146
+ chart_spec1 = formatter.ChartSpec(
147
+ 'test_chart_id1', 'test_chart_json1', 'test_chart_description1'
148
+ )
149
+ chart_spec2 = formatter.ChartSpec(
150
+ 'test_chart_id2', 'test_chart_json2', 'test_chart_description2'
151
+ )
152
+ card_html = ET.fromstring(
153
+ formatter.create_card_html(
154
+ template_env, card_spec, 'test_insights', [chart_spec1, chart_spec2]
155
+ )
156
+ )
157
+ charts = card_html[2]
158
+ self.assertLen(charts, 4) # Each chart has 2 items, chart and script.
159
+
160
+ def test_create_card_html_chart_structure(self):
161
+ template_env = formatter.create_template_env()
162
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
163
+ chart_spec = formatter.ChartSpec(
164
+ 'test_chart_id', 'test_chart_json', 'test_chart_description'
165
+ )
166
+ card_html = ET.fromstring(
167
+ formatter.create_card_html(
168
+ template_env, card_spec, 'test_insights', [chart_spec]
169
+ )
170
+ )
171
+ chart_html = card_html[2]
172
+ self.assertEqual(chart_html.tag, 'charts')
173
+ self.assertEqual(chart_html[0].tag, 'chart')
174
+ self.assertEqual(chart_html[0][0].tag, 'chart-embed')
175
+ self.assertEqual(chart_html[0][1].tag, 'chart-description')
176
+ self.assertContainsSubset('test_chart_description', chart_html[0][1].text)
177
+ self.assertEqual(chart_html[1].tag, 'script')
178
+ self.assertContainsSubset('test_chart_json', chart_html[1].text)
179
+
180
+ def test_create_card_html_mulitple_stats(self):
181
+ template_env = formatter.create_template_env()
182
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
183
+ stat1 = formatter.StatsSpec(title='stats_title1', stat='test_stat1')
184
+ stat2 = formatter.StatsSpec(title='stats_title2', stat='test_stat2')
185
+ card_html = ET.fromstring(
186
+ formatter.create_card_html(
187
+ template_env, card_spec, 'test_insights', stats_specs=[stat1, stat2]
188
+ )
189
+ )
190
+ stats = card_html[2]
191
+ self.assertLen(stats, 2)
192
+
193
+ def test_create_card_html_stats_structure(self):
194
+ template_env = formatter.create_template_env()
195
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
196
+ stats_spec = formatter.StatsSpec(
197
+ title='stats_title', stat='test_stat', delta='+0.3'
198
+ )
199
+ card_html = ET.fromstring(
200
+ formatter.create_card_html(
201
+ template_env, card_spec, 'test_insights', stats_specs=[stats_spec]
202
+ )
203
+ )
204
+ stats_html = card_html[2]
205
+ self.assertEqual(stats_html.tag, 'stats-section')
206
+ self.assertEqual(stats_html[0].tag, 'stats')
207
+ self.assertEqual(stats_html[0][0].tag, 'stats-title')
208
+ self.assertEqual(stats_html[0][0].text, 'stats_title')
209
+ self.assertEqual(stats_html[0][1].tag, 'stat')
210
+ self.assertEqual(stats_html[0][1].text, 'test_stat')
211
+ self.assertEqual(stats_html[0][2].tag, 'delta')
212
+ self.assertContainsSubset('+0.3', stats_html[0][2].text)
213
+
214
+
215
+ if __name__ == '__main__':
216
+ absltest.main()
@@ -1,5 +1,5 @@
1
1
  {#
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  {#
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2024 Google LLC
2
+ * Copyright 2025 Google LLC
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2024 Google LLC
2
+ * Copyright 2025 Google LLC
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  <!--
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -42,7 +42,9 @@ limitations under the License.
42
42
  </div>
43
43
  </div>
44
44
 
45
- {% include "chips.html.jinja" %}
45
+ {% if start_date is defined and start_date %}
46
+ {% include "chips.html.jinja" %}
47
+ {% endif %}
46
48
 
47
49
  <cards>
48
50
  {# Each card is laid out in a grid. See; .card display layout. #}
@@ -1,5 +1,5 @@
1
1
  {#
2
- Copyright 2024 Google LLC
2
+ Copyright 2025 Google LLC
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
meridian/version.py CHANGED
@@ -14,4 +14,4 @@
14
14
 
15
15
  """Module for Meridian version."""
16
16
 
17
- __version__ = "1.3.0"
17
+ __version__ = "1.3.2"
schema/__init__.py ADDED
@@ -0,0 +1,30 @@
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
+ try: # pylint: disable=g-statement-before-imports
18
+ # A quick check for schema dependencies.
19
+ # If this fails, it's likely because meridian was installed without
20
+ # `pip install google-meridian[schema]`.
21
+ from mmm.v1.model.meridian import meridian_model_pb2 # pylint: disable=g-import-not-at-top
22
+ except ModuleNotFoundError as exc:
23
+ raise ImportError(
24
+ 'Schema dependencies not found. Please install meridian with '
25
+ '`pip install google-meridian[schema]`.'
26
+ ) from exc
27
+
28
+ # pylint: disable=g-import-not-at-top
29
+ from schema import serde
30
+ 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
@@ -0,0 +1,48 @@
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
+ """Constants shared across the Meridian serde library."""
16
+
17
+ # Constants for hyperparameters protobuf structure
18
+ BASELINE_GEO_ONEOF = 'baseline_geo_oneof'
19
+ BASELINE_GEO_INT = 'baseline_geo_int'
20
+ BASELINE_GEO_STRING = 'baseline_geo_string'
21
+ CONTROL_POPULATION_SCALING_ID = 'control_population_scaling_id'
22
+ HOLDOUT_ID = 'holdout_id'
23
+ NON_MEDIA_POPULATION_SCALING_ID = 'non_media_population_scaling_id'
24
+ ADSTOCK_DECAY_SPEC = 'adstock_decay_spec'
25
+ GLOBAL_ADSTOCK_DECAY = 'global_adstock_decay'
26
+ ADSTOCK_DECAY_BY_CHANNEL = 'adstock_decay_by_channel'
27
+ DEFAULT_DECAY = 'geometric'
28
+
29
+ # Constants for marketing data protobuf structure
30
+ GEO_INFO = 'geo_info'
31
+ METADATA = 'metadata'
32
+ REACH_FREQUENCY = 'reach_frequency'
33
+
34
+ # Constants for distribution protobuf structure
35
+ DISTRIBUTION_TYPE = 'distribution_type'
36
+ BATCH_BROADCAST_DISTRIBUTION = 'batch_broadcast'
37
+ DETERMINISTIC_DISTRIBUTION = 'deterministic'
38
+ HALF_NORMAL_DISTRIBUTION = 'half_normal'
39
+ LOG_NORMAL_DISTRIBUTION = 'log_normal'
40
+ NORMAL_DISTRIBUTION = 'normal'
41
+ TRANSFORMED_DISTRIBUTION = 'transformed'
42
+ TRUNCATED_NORMAL_DISTRIBUTION = 'truncated_normal'
43
+ UNIFORM_DISTRIBUTION = 'uniform'
44
+ BETA_DISTRIBUTION = 'beta'
45
+ BIJECTOR_TYPE = 'bijector_type'
46
+ SHIFT_BIJECTOR = 'shift'
47
+ SCALE_BIJECTOR = 'scale'
48
+ RECIPROCAL_BIJECTOR = 'reciprocal'