google-meridian 1.3.1__py3-none-any.whl → 1.4.0__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.
- {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/METADATA +13 -9
- google_meridian-1.4.0.dist-info/RECORD +108 -0
- {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/top_level.txt +1 -0
- meridian/analysis/__init__.py +1 -2
- meridian/analysis/analyzer.py +0 -1
- meridian/analysis/optimizer.py +5 -3
- meridian/analysis/review/checks.py +81 -30
- meridian/analysis/review/constants.py +4 -0
- meridian/analysis/review/results.py +40 -9
- meridian/analysis/summarizer.py +8 -3
- meridian/analysis/test_utils.py +934 -485
- meridian/analysis/visualizer.py +11 -7
- meridian/backend/__init__.py +53 -5
- meridian/backend/test_utils.py +72 -0
- meridian/constants.py +2 -0
- meridian/data/load.py +2 -0
- meridian/data/test_utils.py +82 -10
- meridian/model/__init__.py +2 -0
- meridian/model/context.py +925 -0
- meridian/model/eda/__init__.py +0 -1
- meridian/model/eda/constants.py +13 -2
- meridian/model/eda/eda_engine.py +299 -37
- meridian/model/eda/eda_outcome.py +21 -1
- meridian/model/equations.py +418 -0
- meridian/model/knots.py +75 -47
- meridian/model/model.py +93 -792
- meridian/{analysis/templates → templates}/card.html.jinja +1 -1
- meridian/{analysis/templates → templates}/chart.html.jinja +1 -1
- meridian/{analysis/templates → templates}/chips.html.jinja +1 -1
- meridian/{analysis → templates}/formatter.py +12 -1
- meridian/templates/formatter_test.py +216 -0
- meridian/{analysis/templates → templates}/insights.html.jinja +1 -1
- meridian/{analysis/templates → templates}/stats.html.jinja +1 -1
- meridian/{analysis/templates → templates}/style.css +1 -1
- meridian/{analysis/templates → templates}/style.scss +1 -1
- meridian/{analysis/templates → templates}/summary.html.jinja +4 -2
- meridian/{analysis/templates → templates}/table.html.jinja +1 -1
- meridian/version.py +1 -1
- scenarioplanner/__init__.py +42 -0
- scenarioplanner/converters/__init__.py +25 -0
- scenarioplanner/converters/dataframe/__init__.py +28 -0
- scenarioplanner/converters/dataframe/budget_opt_converters.py +383 -0
- scenarioplanner/converters/dataframe/common.py +71 -0
- scenarioplanner/converters/dataframe/constants.py +137 -0
- scenarioplanner/converters/dataframe/converter.py +42 -0
- scenarioplanner/converters/dataframe/dataframe_model_converter.py +70 -0
- scenarioplanner/converters/dataframe/marketing_analyses_converters.py +543 -0
- scenarioplanner/converters/dataframe/rf_opt_converters.py +314 -0
- scenarioplanner/converters/mmm.py +743 -0
- scenarioplanner/converters/mmm_converter.py +58 -0
- scenarioplanner/converters/sheets.py +156 -0
- scenarioplanner/converters/test_data.py +714 -0
- scenarioplanner/linkingapi/__init__.py +47 -0
- scenarioplanner/linkingapi/constants.py +27 -0
- scenarioplanner/linkingapi/url_generator.py +131 -0
- scenarioplanner/mmm_ui_proto_generator.py +354 -0
- schema/__init__.py +15 -0
- schema/mmm_proto_generator.py +71 -0
- schema/model_consumer.py +133 -0
- schema/processors/__init__.py +77 -0
- schema/processors/budget_optimization_processor.py +832 -0
- schema/processors/common.py +64 -0
- schema/processors/marketing_processor.py +1136 -0
- schema/processors/model_fit_processor.py +367 -0
- schema/processors/model_kernel_processor.py +117 -0
- schema/processors/model_processor.py +412 -0
- schema/processors/reach_frequency_optimization_processor.py +584 -0
- schema/test_data.py +380 -0
- schema/utils/__init__.py +1 -0
- schema/utils/date_range_bucketing.py +117 -0
- google_meridian-1.3.1.dist-info/RECORD +0 -76
- meridian/model/eda/meridian_eda.py +0 -220
- {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/WHEEL +0 -0
- {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/licenses/LICENSE +0 -0
schema/test_data.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
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
|
+
"""Test data for MMM proto generator."""
|
|
16
|
+
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
import datetime
|
|
19
|
+
|
|
20
|
+
from mmm.v1 import mmm_pb2 as mmm_pb
|
|
21
|
+
from mmm.v1.common import date_interval_pb2 as date_interval_pb
|
|
22
|
+
from mmm.v1.fit import model_fit_pb2 as fit_pb
|
|
23
|
+
from mmm.v1.marketing.analysis import marketing_analysis_pb2
|
|
24
|
+
from mmm.v1.marketing.optimization import budget_optimization_pb2 as budget_pb
|
|
25
|
+
from mmm.v1.marketing.optimization import reach_frequency_optimization_pb2 as rf_pb
|
|
26
|
+
from schema.processors import budget_optimization_processor
|
|
27
|
+
from schema.processors import marketing_processor
|
|
28
|
+
from schema.processors import model_fit_processor
|
|
29
|
+
from schema.processors import model_processor
|
|
30
|
+
from schema.processors import reach_frequency_optimization_processor as rf_opt_processor
|
|
31
|
+
|
|
32
|
+
from google.type import date_pb2
|
|
33
|
+
|
|
34
|
+
# Weekly dates from 2022-11-21 to 2024-01-01.
|
|
35
|
+
ALL_TIMES_IN_MERIDIAN = (
|
|
36
|
+
'2022-11-21',
|
|
37
|
+
'2022-11-28',
|
|
38
|
+
'2022-12-05',
|
|
39
|
+
'2022-12-12',
|
|
40
|
+
'2022-12-19',
|
|
41
|
+
'2022-12-26',
|
|
42
|
+
'2023-01-02',
|
|
43
|
+
'2023-01-09',
|
|
44
|
+
'2023-01-16',
|
|
45
|
+
'2023-01-23',
|
|
46
|
+
'2023-01-30',
|
|
47
|
+
'2023-02-06',
|
|
48
|
+
'2023-02-13',
|
|
49
|
+
'2023-02-20',
|
|
50
|
+
'2023-02-27',
|
|
51
|
+
'2023-03-06',
|
|
52
|
+
'2023-03-13',
|
|
53
|
+
'2023-03-20',
|
|
54
|
+
'2023-03-27',
|
|
55
|
+
'2023-04-03',
|
|
56
|
+
'2023-04-10',
|
|
57
|
+
'2023-04-17',
|
|
58
|
+
'2023-04-24',
|
|
59
|
+
'2023-05-01',
|
|
60
|
+
'2023-05-08',
|
|
61
|
+
'2023-05-15',
|
|
62
|
+
'2023-05-22',
|
|
63
|
+
'2023-05-29',
|
|
64
|
+
'2023-06-05',
|
|
65
|
+
'2023-06-12',
|
|
66
|
+
'2023-06-19',
|
|
67
|
+
'2023-06-26',
|
|
68
|
+
'2023-07-03',
|
|
69
|
+
'2023-07-10',
|
|
70
|
+
'2023-07-17',
|
|
71
|
+
'2023-07-24',
|
|
72
|
+
'2023-07-31',
|
|
73
|
+
'2023-08-07',
|
|
74
|
+
'2023-08-14',
|
|
75
|
+
'2023-08-21',
|
|
76
|
+
'2023-08-28',
|
|
77
|
+
'2023-09-04',
|
|
78
|
+
'2023-09-11',
|
|
79
|
+
'2023-09-18',
|
|
80
|
+
'2023-09-25',
|
|
81
|
+
'2023-10-02',
|
|
82
|
+
'2023-10-09',
|
|
83
|
+
'2023-10-16',
|
|
84
|
+
'2023-10-23',
|
|
85
|
+
'2023-10-30',
|
|
86
|
+
'2023-11-06',
|
|
87
|
+
'2023-11-13',
|
|
88
|
+
'2023-11-20',
|
|
89
|
+
'2023-11-27',
|
|
90
|
+
'2023-12-04',
|
|
91
|
+
'2023-12-11',
|
|
92
|
+
'2023-12-18',
|
|
93
|
+
'2023-12-25',
|
|
94
|
+
'2024-01-01',
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
ALL_TIME_BUCKET_DATED_SPECS = (
|
|
98
|
+
# All
|
|
99
|
+
model_processor.DatedSpec(
|
|
100
|
+
start_date=datetime.date(2022, 11, 21),
|
|
101
|
+
end_date=datetime.date(2024, 1, 8),
|
|
102
|
+
date_interval_tag='ALL',
|
|
103
|
+
),
|
|
104
|
+
# Monthly buckets
|
|
105
|
+
model_processor.DatedSpec(
|
|
106
|
+
start_date=datetime.date(2022, 12, 5),
|
|
107
|
+
end_date=datetime.date(2023, 1, 2),
|
|
108
|
+
date_interval_tag='Y2022 Dec',
|
|
109
|
+
),
|
|
110
|
+
model_processor.DatedSpec(
|
|
111
|
+
start_date=datetime.date(2023, 1, 2),
|
|
112
|
+
end_date=datetime.date(2023, 2, 6),
|
|
113
|
+
date_interval_tag='Y2023 Jan',
|
|
114
|
+
),
|
|
115
|
+
model_processor.DatedSpec(
|
|
116
|
+
start_date=datetime.date(2023, 2, 6),
|
|
117
|
+
end_date=datetime.date(2023, 3, 6),
|
|
118
|
+
date_interval_tag='Y2023 Feb',
|
|
119
|
+
),
|
|
120
|
+
model_processor.DatedSpec(
|
|
121
|
+
start_date=datetime.date(2023, 3, 6),
|
|
122
|
+
end_date=datetime.date(2023, 4, 3),
|
|
123
|
+
date_interval_tag='Y2023 Mar',
|
|
124
|
+
),
|
|
125
|
+
model_processor.DatedSpec(
|
|
126
|
+
start_date=datetime.date(2023, 4, 3),
|
|
127
|
+
end_date=datetime.date(2023, 5, 1),
|
|
128
|
+
date_interval_tag='Y2023 Apr',
|
|
129
|
+
),
|
|
130
|
+
model_processor.DatedSpec(
|
|
131
|
+
start_date=datetime.date(2023, 5, 1),
|
|
132
|
+
end_date=datetime.date(2023, 6, 5),
|
|
133
|
+
date_interval_tag='Y2023 May',
|
|
134
|
+
),
|
|
135
|
+
model_processor.DatedSpec(
|
|
136
|
+
start_date=datetime.date(2023, 6, 5),
|
|
137
|
+
end_date=datetime.date(2023, 7, 3),
|
|
138
|
+
date_interval_tag='Y2023 Jun',
|
|
139
|
+
),
|
|
140
|
+
model_processor.DatedSpec(
|
|
141
|
+
start_date=datetime.date(2023, 7, 3),
|
|
142
|
+
end_date=datetime.date(2023, 8, 7),
|
|
143
|
+
date_interval_tag='Y2023 Jul',
|
|
144
|
+
),
|
|
145
|
+
model_processor.DatedSpec(
|
|
146
|
+
start_date=datetime.date(2023, 8, 7),
|
|
147
|
+
end_date=datetime.date(2023, 9, 4),
|
|
148
|
+
date_interval_tag='Y2023 Aug',
|
|
149
|
+
),
|
|
150
|
+
model_processor.DatedSpec(
|
|
151
|
+
start_date=datetime.date(2023, 9, 4),
|
|
152
|
+
end_date=datetime.date(2023, 10, 2),
|
|
153
|
+
date_interval_tag='Y2023 Sep',
|
|
154
|
+
),
|
|
155
|
+
model_processor.DatedSpec(
|
|
156
|
+
start_date=datetime.date(2023, 10, 2),
|
|
157
|
+
end_date=datetime.date(2023, 11, 6),
|
|
158
|
+
date_interval_tag='Y2023 Oct',
|
|
159
|
+
),
|
|
160
|
+
model_processor.DatedSpec(
|
|
161
|
+
start_date=datetime.date(2023, 11, 6),
|
|
162
|
+
end_date=datetime.date(2023, 12, 4),
|
|
163
|
+
date_interval_tag='Y2023 Nov',
|
|
164
|
+
),
|
|
165
|
+
model_processor.DatedSpec(
|
|
166
|
+
start_date=datetime.date(2023, 12, 4),
|
|
167
|
+
end_date=datetime.date(2024, 1, 1),
|
|
168
|
+
date_interval_tag='Y2023 Dec',
|
|
169
|
+
),
|
|
170
|
+
# Quarterly buckets
|
|
171
|
+
model_processor.DatedSpec(
|
|
172
|
+
start_date=datetime.date(2023, 1, 2),
|
|
173
|
+
end_date=datetime.date(2023, 4, 3),
|
|
174
|
+
date_interval_tag='Y2023 Q1',
|
|
175
|
+
),
|
|
176
|
+
model_processor.DatedSpec(
|
|
177
|
+
start_date=datetime.date(2023, 4, 3),
|
|
178
|
+
end_date=datetime.date(2023, 7, 3),
|
|
179
|
+
date_interval_tag='Y2023 Q2',
|
|
180
|
+
),
|
|
181
|
+
model_processor.DatedSpec(
|
|
182
|
+
start_date=datetime.date(2023, 7, 3),
|
|
183
|
+
end_date=datetime.date(2023, 10, 2),
|
|
184
|
+
date_interval_tag='Y2023 Q3',
|
|
185
|
+
),
|
|
186
|
+
model_processor.DatedSpec(
|
|
187
|
+
start_date=datetime.date(2023, 10, 2),
|
|
188
|
+
end_date=datetime.date(2024, 1, 1),
|
|
189
|
+
date_interval_tag='Y2023 Q4',
|
|
190
|
+
),
|
|
191
|
+
# Yearly buckets
|
|
192
|
+
model_processor.DatedSpec(
|
|
193
|
+
start_date=datetime.date(2023, 1, 2),
|
|
194
|
+
end_date=datetime.date(2024, 1, 1),
|
|
195
|
+
date_interval_tag='Y2023',
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _dated_spec_to_date_interval(
|
|
201
|
+
spec: model_processor.DatedSpec,
|
|
202
|
+
) -> date_interval_pb.DateInterval:
|
|
203
|
+
if spec.start_date is None or spec.end_date is None:
|
|
204
|
+
raise ValueError('Start date or end date is None.')
|
|
205
|
+
|
|
206
|
+
return date_interval_pb.DateInterval(
|
|
207
|
+
start_date=date_pb2.Date(
|
|
208
|
+
year=spec.start_date.year,
|
|
209
|
+
month=spec.start_date.month,
|
|
210
|
+
day=spec.start_date.day,
|
|
211
|
+
),
|
|
212
|
+
end_date=date_pb2.Date(
|
|
213
|
+
year=spec.end_date.year,
|
|
214
|
+
month=spec.end_date.month,
|
|
215
|
+
day=spec.end_date.day,
|
|
216
|
+
),
|
|
217
|
+
tag=spec.date_interval_tag,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class FakeModelFitProcessor(
|
|
222
|
+
model_processor.ModelProcessor[
|
|
223
|
+
model_fit_processor.ModelFitSpec, fit_pb.ModelFit
|
|
224
|
+
]
|
|
225
|
+
):
|
|
226
|
+
"""Fake ModelFitProcessor for testing."""
|
|
227
|
+
|
|
228
|
+
def __init__(self, trained_model: model_processor.TrainedModel):
|
|
229
|
+
self._trained_model = trained_model
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
def spec_type(cls):
|
|
233
|
+
return model_fit_processor.ModelFitSpec
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def output_type(cls):
|
|
237
|
+
return fit_pb.ModelFit
|
|
238
|
+
|
|
239
|
+
def execute(
|
|
240
|
+
self, specs: Sequence[model_fit_processor.ModelFitSpec]
|
|
241
|
+
) -> fit_pb.ModelFit:
|
|
242
|
+
return fit_pb.ModelFit()
|
|
243
|
+
|
|
244
|
+
def _set_output(self, output: mmm_pb.Mmm, result: fit_pb.ModelFit):
|
|
245
|
+
output.model_fit.CopyFrom(result)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class FakeBudgetOptimizationProcessor(
|
|
249
|
+
model_processor.ModelProcessor[
|
|
250
|
+
budget_optimization_processor.BudgetOptimizationSpec,
|
|
251
|
+
budget_pb.BudgetOptimization,
|
|
252
|
+
]
|
|
253
|
+
):
|
|
254
|
+
"""Fake BudgetOptimizationProcessor for testing."""
|
|
255
|
+
|
|
256
|
+
def __init__(self, trained_model: model_processor.TrainedModel):
|
|
257
|
+
self._trained_model = trained_model
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def spec_type(cls):
|
|
261
|
+
return budget_optimization_processor.BudgetOptimizationSpec
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def output_type(cls):
|
|
265
|
+
return budget_pb.BudgetOptimization
|
|
266
|
+
|
|
267
|
+
def execute(
|
|
268
|
+
self,
|
|
269
|
+
specs: Sequence[budget_optimization_processor.BudgetOptimizationSpec],
|
|
270
|
+
) -> budget_pb.BudgetOptimization:
|
|
271
|
+
results = []
|
|
272
|
+
for spec in specs:
|
|
273
|
+
result = budget_pb.BudgetOptimizationResult(
|
|
274
|
+
name=spec.optimization_name,
|
|
275
|
+
spec=budget_pb.BudgetOptimizationSpec(
|
|
276
|
+
date_interval=_dated_spec_to_date_interval(spec)
|
|
277
|
+
),
|
|
278
|
+
incremental_outcome_grid=budget_pb.IncrementalOutcomeGrid(
|
|
279
|
+
name=spec.grid_name
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
if spec.group_id:
|
|
283
|
+
result.group_id = spec.group_id
|
|
284
|
+
results.append(result)
|
|
285
|
+
|
|
286
|
+
return budget_pb.BudgetOptimization(results=results)
|
|
287
|
+
|
|
288
|
+
def _set_output(
|
|
289
|
+
self, output: mmm_pb.Mmm, result: budget_pb.BudgetOptimization
|
|
290
|
+
):
|
|
291
|
+
output.marketing_optimization.budget_optimization.CopyFrom(result)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class FakeReachFrequencyOptimizationProcessor(
|
|
295
|
+
model_processor.ModelProcessor[
|
|
296
|
+
rf_opt_processor.ReachFrequencyOptimizationSpec,
|
|
297
|
+
rf_pb.ReachFrequencyOptimization,
|
|
298
|
+
]
|
|
299
|
+
):
|
|
300
|
+
"""Fake ReachFrequencyOptimizationProcessor for testing."""
|
|
301
|
+
|
|
302
|
+
def __init__(self, trained_model: model_processor.TrainedModel):
|
|
303
|
+
self._trained_model = trained_model
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def spec_type(cls):
|
|
307
|
+
return rf_opt_processor.ReachFrequencyOptimizationSpec
|
|
308
|
+
|
|
309
|
+
@classmethod
|
|
310
|
+
def output_type(cls):
|
|
311
|
+
return rf_pb.ReachFrequencyOptimization
|
|
312
|
+
|
|
313
|
+
def execute(
|
|
314
|
+
self,
|
|
315
|
+
specs: Sequence[rf_opt_processor.ReachFrequencyOptimizationSpec],
|
|
316
|
+
) -> rf_pb.ReachFrequencyOptimization:
|
|
317
|
+
results = []
|
|
318
|
+
for spec in specs:
|
|
319
|
+
result = rf_pb.ReachFrequencyOptimizationResult(
|
|
320
|
+
name=spec.optimization_name,
|
|
321
|
+
spec=rf_pb.ReachFrequencyOptimizationSpec(
|
|
322
|
+
date_interval=_dated_spec_to_date_interval(spec)
|
|
323
|
+
),
|
|
324
|
+
frequency_outcome_grid=rf_pb.FrequencyOutcomeGrid(
|
|
325
|
+
name=spec.grid_name
|
|
326
|
+
),
|
|
327
|
+
)
|
|
328
|
+
if spec.group_id:
|
|
329
|
+
result.group_id = spec.group_id
|
|
330
|
+
results.append(result)
|
|
331
|
+
|
|
332
|
+
return rf_pb.ReachFrequencyOptimization(results=results)
|
|
333
|
+
|
|
334
|
+
def _set_output(
|
|
335
|
+
self,
|
|
336
|
+
output: mmm_pb.Mmm,
|
|
337
|
+
result: rf_pb.ReachFrequencyOptimization,
|
|
338
|
+
):
|
|
339
|
+
output.marketing_optimization.reach_frequency_optimization.CopyFrom(result)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class FakeMarketingProcessor(
|
|
343
|
+
model_processor.ModelProcessor[
|
|
344
|
+
marketing_processor.MarketingAnalysisSpec,
|
|
345
|
+
marketing_analysis_pb2.MarketingAnalysisList,
|
|
346
|
+
]
|
|
347
|
+
):
|
|
348
|
+
"""Fake MarketingProcessor for testing."""
|
|
349
|
+
|
|
350
|
+
def __init__(self, trained_model: model_processor.TrainedModel):
|
|
351
|
+
self._trained_model = trained_model
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def spec_type(cls):
|
|
355
|
+
return marketing_processor.MarketingAnalysisSpec
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def output_type(cls):
|
|
359
|
+
return marketing_analysis_pb2.MarketingAnalysisList
|
|
360
|
+
|
|
361
|
+
def execute(
|
|
362
|
+
self, specs: Sequence[marketing_processor.MarketingAnalysisSpec]
|
|
363
|
+
) -> marketing_analysis_pb2.MarketingAnalysisList:
|
|
364
|
+
marketing_analyses = []
|
|
365
|
+
for spec in specs:
|
|
366
|
+
marketing_analysis = marketing_analysis_pb2.MarketingAnalysis(
|
|
367
|
+
date_interval=_dated_spec_to_date_interval(spec)
|
|
368
|
+
)
|
|
369
|
+
marketing_analyses.append(marketing_analysis)
|
|
370
|
+
|
|
371
|
+
return marketing_analysis_pb2.MarketingAnalysisList(
|
|
372
|
+
marketing_analyses=marketing_analyses
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def _set_output(
|
|
376
|
+
self,
|
|
377
|
+
output: mmm_pb.Mmm,
|
|
378
|
+
result: marketing_analysis_pb2.MarketingAnalysisList,
|
|
379
|
+
):
|
|
380
|
+
output.marketing_analysis_list.CopyFrom(result)
|
schema/utils/__init__.py
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
"""Helper classes for generating date intervals for various time buckets."""
|
|
16
|
+
|
|
17
|
+
import abc
|
|
18
|
+
from collections.abc import Iterator, Sequence
|
|
19
|
+
import datetime
|
|
20
|
+
from typing import TypeAlias
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"DateRangeBucketer",
|
|
25
|
+
"MonthlyDateRangeGenerator",
|
|
26
|
+
"QuarterlyDateRangeGenerator",
|
|
27
|
+
"YearlyDateRangeGenerator",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
DateInterval: TypeAlias = tuple[datetime.date, datetime.date]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DateRangeBucketer(abc.ABC):
|
|
35
|
+
"""Generates `DateInterval` protos over a range of dates."""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
input_dates: Sequence[datetime.date],
|
|
40
|
+
):
|
|
41
|
+
"""Initializes the DateRangeBucketer with a sequence of dates.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
input_dates: A sequence of `datetime.date` objects representing the range
|
|
45
|
+
of dates to generate intervals for.
|
|
46
|
+
"""
|
|
47
|
+
if not all(
|
|
48
|
+
input_dates[i] < input_dates[i + 1] for i in range(len(input_dates) - 1)
|
|
49
|
+
):
|
|
50
|
+
raise ValueError("`input_dates` must be strictly ascending dates.")
|
|
51
|
+
|
|
52
|
+
self._input_dates = input_dates
|
|
53
|
+
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def generate_date_intervals(self) -> Iterator[DateInterval]:
|
|
56
|
+
"""Generates `DateInterval` protos for the class's input dates.
|
|
57
|
+
|
|
58
|
+
Each interval represents a month, quarter, or year, depending on the
|
|
59
|
+
instance of this class. An interval is excluded if the start date is not the
|
|
60
|
+
first available date (in `self._input_dates`) for the time bucket. The last
|
|
61
|
+
interval in `self._input_dates` is excluded in all cases.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
An iterator over generated `TimeInterval`s.
|
|
65
|
+
"""
|
|
66
|
+
raise NotImplementedError()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MonthlyDateRangeGenerator(DateRangeBucketer):
|
|
70
|
+
"""Generates monthly date intervals."""
|
|
71
|
+
|
|
72
|
+
def generate_date_intervals(self) -> Iterator[DateInterval]:
|
|
73
|
+
start_date = self._input_dates[0]
|
|
74
|
+
|
|
75
|
+
for date in self._input_dates:
|
|
76
|
+
if date.month != start_date.month:
|
|
77
|
+
if start_date.day <= 7:
|
|
78
|
+
yield (start_date, date)
|
|
79
|
+
|
|
80
|
+
start_date = date
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class QuarterlyDateRangeGenerator(DateRangeBucketer):
|
|
84
|
+
"""Generates quarterly date intervals."""
|
|
85
|
+
|
|
86
|
+
def generate_date_intervals(self) -> Iterator[DateInterval]:
|
|
87
|
+
start_date = self._input_dates[0]
|
|
88
|
+
for date in self._input_dates:
|
|
89
|
+
start_date_quarter_number = (start_date.month - 1) // 3 + 1
|
|
90
|
+
current_date_quarter_number = (date.month - 1) // 3 + 1
|
|
91
|
+
|
|
92
|
+
if start_date_quarter_number != current_date_quarter_number:
|
|
93
|
+
# The interval is only included if the start date is the first date of
|
|
94
|
+
# the quarter that's present in `self._input_dates`. We can detect this
|
|
95
|
+
# date by checking whether it's in the first month of the quarter and
|
|
96
|
+
# falls in the first seven days of the month.
|
|
97
|
+
if (
|
|
98
|
+
start_date.day <= 7
|
|
99
|
+
and start_date.month == ((start_date_quarter_number - 1) * 3) + 1
|
|
100
|
+
):
|
|
101
|
+
yield (start_date, date)
|
|
102
|
+
|
|
103
|
+
start_date = date
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class YearlyDateRangeGenerator(DateRangeBucketer):
|
|
107
|
+
"""Generates yearly date intervals."""
|
|
108
|
+
|
|
109
|
+
def generate_date_intervals(self) -> Iterator[DateInterval]:
|
|
110
|
+
start_date = self._input_dates[0]
|
|
111
|
+
|
|
112
|
+
for date in self._input_dates:
|
|
113
|
+
if date.year != start_date.year:
|
|
114
|
+
if start_date.day <= 7 and start_date.month == 1:
|
|
115
|
+
yield (start_date, date)
|
|
116
|
+
|
|
117
|
+
start_date = date
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
google_meridian-1.3.1.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
2
|
-
meridian/__init__.py,sha256=0fOT5oNZF7-pbiWWGUefV-ysafttieG079m1ijMFQO8,861
|
|
3
|
-
meridian/constants.py,sha256=ZmMIoJDFQvKIOVG9oPOQ7Cj16wt4HDS5fCPBrz_KiLE,20308
|
|
4
|
-
meridian/version.py,sha256=mmCkrGRWB8mI33apVnverT2ysfJofSyhlXSCVNotj9U,644
|
|
5
|
-
meridian/analysis/__init__.py,sha256=NWDhtRkKs-n66C6746rXT7Wk8tLdkrT9NDvuPG_B_5c,874
|
|
6
|
-
meridian/analysis/analyzer.py,sha256=EUKoN69EEz4vt4BpyRvrwEoHt7eLZ8UM6emNe14Tbj0,219975
|
|
7
|
-
meridian/analysis/formatter.py,sha256=AN2M4jdUV88XjNdi5-dK_mITES1J1Dk3Zs7DYo3OTKg,7290
|
|
8
|
-
meridian/analysis/optimizer.py,sha256=RX8HXSUi-tvLSjzF2OIbXQT3pMT75PzA5Y0xK6PPE_I,126277
|
|
9
|
-
meridian/analysis/summarizer.py,sha256=7hHBf2Bj0lKqamg7DGvaKIuCf06CSsPPhDM2wKsXUPk,19035
|
|
10
|
-
meridian/analysis/summary_text.py,sha256=I_smDkZJYp2j77ea-9AIbgeraDa7-qUYyb-IthP2qO4,12438
|
|
11
|
-
meridian/analysis/test_utils.py,sha256=aZq88pxtpMHwhcpfYz8nHhR0Alhi_OvgS9qBR4LBgO0,78346
|
|
12
|
-
meridian/analysis/visualizer.py,sha256=1vjX3XQF_BoXEGDUS4o2GheZQ5c0VGdHuc-nXySnpsY,94091
|
|
13
|
-
meridian/analysis/review/__init__.py,sha256=cF24EbhiVSs-tvtRf59uVin39tu6aCTTCaeEdv6ISZ8,804
|
|
14
|
-
meridian/analysis/review/checks.py,sha256=CBBZ0uNN91b_0H9DGdbDRVdiTRI4D7xptLCq_gK5WAI,25002
|
|
15
|
-
meridian/analysis/review/configs.py,sha256=5JJ8v6n22GNBmE78xNX6jwdjkZz2qar4Q9YTcVqzcoI,3653
|
|
16
|
-
meridian/analysis/review/constants.py,sha256=bL-se2BFfRxPr3F0Rfpdy7d8s3idMoaka_t6hN1K3Sc,1369
|
|
17
|
-
meridian/analysis/review/results.py,sha256=jsOPohmwozCrx8yt9p7_GiVxGmRDVeAbA8W48MUfAPA,16168
|
|
18
|
-
meridian/analysis/review/reviewer.py,sha256=BcfmqHjp-30iZBlrzWfXDN1IJU-UIjINxZ7lsrj5Mts,6675
|
|
19
|
-
meridian/analysis/templates/card.html.jinja,sha256=pv4MVbQ25CcvtZY-LH7bFW0OSeHobkeEkAleB1sfQ14,1284
|
|
20
|
-
meridian/analysis/templates/chart.html.jinja,sha256=87i0xnXHRBoLLxBpKv2i960TLToWq4r1aVQZqaXIeMQ,1086
|
|
21
|
-
meridian/analysis/templates/chips.html.jinja,sha256=t-ovn-2pcPCn1ev1VtSaSC3W7QxSy_iAO_L5-p68mdg,1030
|
|
22
|
-
meridian/analysis/templates/insights.html.jinja,sha256=6hEWipbOMiMzs9QGZ6dcB_73tNkj0ZtNiC8E89a98zg,606
|
|
23
|
-
meridian/analysis/templates/stats.html.jinja,sha256=9hQOG02FX1IHVIvdWS_-LI2bbSaqdyHEtCZkiArwAg0,772
|
|
24
|
-
meridian/analysis/templates/style.css,sha256=RODTWc2pXcG9zW3q9SEJpVXgeD-WwQgzLpmFcbXPhLg,5492
|
|
25
|
-
meridian/analysis/templates/style.scss,sha256=nSrZOpcIrVyiL4eC9jLUlxIZtAKZ0Rt8pwfk4H1nMrs,5076
|
|
26
|
-
meridian/analysis/templates/summary.html.jinja,sha256=LuENVDHYIpNo4pzloYaCR2K9XN1Ow6_9oQOcOwD9nGg,1707
|
|
27
|
-
meridian/analysis/templates/table.html.jinja,sha256=mvLMZx92RcD2JAS2w2eZtfYG-6WdfwYVo7pM8TbHp4g,1176
|
|
28
|
-
meridian/backend/__init__.py,sha256=ftXcdb3tIky_m8exhD9RRhaEqTEvMcqZ8lGkkR1PjsE,39495
|
|
29
|
-
meridian/backend/config.py,sha256=B9VQnhBfg9RW04GNbt7F5uCugByenoJzt-keFLLYEp8,3561
|
|
30
|
-
meridian/backend/test_utils.py,sha256=DYU5IpWiyM26aTY9Q84mOGRw0dQ9XmsZRsAJNFuZDp0,11667
|
|
31
|
-
meridian/data/__init__.py,sha256=StIe-wfYnnbfUbKtZHwnAQcRQUS8XCZk_PCaEzw90Ww,929
|
|
32
|
-
meridian/data/arg_builder.py,sha256=Kqlt88bOqFj6D3xNwvWo4MBwNwcDFHzd-wMfEOmLoPU,3741
|
|
33
|
-
meridian/data/data_frame_input_data_builder.py,sha256=_hexZMFAuAowgo6FaOGElHSFHqhGnHQwEEBcwnT3zUE,27295
|
|
34
|
-
meridian/data/input_data.py,sha256=Qlxm4El6h1SRPsWDqZoKkOcMtrjiRWr3z8sU2mtghRA,43151
|
|
35
|
-
meridian/data/input_data_builder.py,sha256=tbZjVXPDfmtndVyJA0fmzGzZwZb0RCEjXOTXb-ga8Nc,25648
|
|
36
|
-
meridian/data/load.py,sha256=X2nmYCC-7A0RUgmdolTqCt0TD3NEZabQ5oGv-TugE00,40129
|
|
37
|
-
meridian/data/nd_array_input_data_builder.py,sha256=lfpmnENGuSGKyUd7bDGAwoLqHqteOKmHdKl0VI2wCQA,16341
|
|
38
|
-
meridian/data/test_utils.py,sha256=mw-QPTP15oXf32I7cxMe8iSFBLB3seqEiITZMTz_Eg8,59838
|
|
39
|
-
meridian/data/time_coordinates.py,sha256=C5A5fscSLjPH6G9YT8OspgIlCrkMY7y8dMFEt3tNSnE,9874
|
|
40
|
-
meridian/mlflow/__init__.py,sha256=elwXUqPQYi7VF9PYjelU1tydfcUrmtuoq6eJCOnV9bk,693
|
|
41
|
-
meridian/mlflow/autolog.py,sha256=SZsrynLjozcyrAFCNWiqchSa2yOszVnwFBGz23BmWUU,6379
|
|
42
|
-
meridian/model/__init__.py,sha256=mhF5VkRxvwamRa_0AihgbFuXLMueRCK-Je_ZZvU5IFw,1013
|
|
43
|
-
meridian/model/adstock_hill.py,sha256=HoRKjyL03pCTBz6Utof9wEvlQCFM43BvrEW_oupj7NU,17688
|
|
44
|
-
meridian/model/knots.py,sha256=87kw5oa3T1k9GgT_aWXTqQx5XCxLsS2w1hnzc581XL0,26677
|
|
45
|
-
meridian/model/media.py,sha256=skjy4Vd8LfDQWlqR_2lJ1qbG9UcS1dow5W45BAu4qk8,14599
|
|
46
|
-
meridian/model/model.py,sha256=jMtfl7woWtJ8M8AX42QeZ5hUS8hlhPdZ-9OU8KahjKA,68984
|
|
47
|
-
meridian/model/model_test_data.py,sha256=XGBz8RGdCsjAUOmgxX3CfWSj-_hdq2Lc8saFCqmImwM,23901
|
|
48
|
-
meridian/model/posterior_sampler.py,sha256=f3MayglIgBeBjWeXJU_RgT9cCugcjJ3aEjHqaWPsTbg,26806
|
|
49
|
-
meridian/model/prior_distribution.py,sha256=ZArW4uXIPPQL6hRWiGZUzcHktbkjE_vOklvlbp9LR64,57662
|
|
50
|
-
meridian/model/prior_sampler.py,sha256=iLvCefhA4WY0ENcnLK9471WUZPPyzQ1je58MRjxKv74,25460
|
|
51
|
-
meridian/model/spec.py,sha256=VlK6WJiPo2lzOF0O2judtJ6O3uEw7wYL5AT8bioq4gE,19188
|
|
52
|
-
meridian/model/transformers.py,sha256=HxlVJitxP-wu-NOHU0tArFUZ4NAO3c7adAYj4Zvqnvo,8363
|
|
53
|
-
meridian/model/eda/__init__.py,sha256=w3p7ZUZLq5TOEHm8n2P1CWjGrzuNrkqSSnVFdlw17Dk,812
|
|
54
|
-
meridian/model/eda/constants.py,sha256=V9aOHQDvB3WAEyT0NE4gE8rqbStaGVh3XDlBPOKpuLc,739
|
|
55
|
-
meridian/model/eda/eda_engine.py,sha256=5Ikgiz-6d3uTBan71WwsgPHOqk0fAy390ZLCq9L6HoY,64846
|
|
56
|
-
meridian/model/eda/eda_outcome.py,sha256=P-0kNIbNXcyqMaNvFxiL3x6fhtYOL2trw-zPPZGXh5w,5670
|
|
57
|
-
meridian/model/eda/eda_spec.py,sha256=diieYyZH0ee3ZLy0rGFMcWrrgiUrz2HctMwOrmtJR6w,2871
|
|
58
|
-
meridian/model/eda/meridian_eda.py,sha256=GTdBaAtfsHS5s6P5ZESaeh1ElKV_o7dSqQosiMnFBKg,7537
|
|
59
|
-
schema/__init__.py,sha256=Df2XKjMKa0ry7CTlDuJnyxuTdSgAd5za922wMlE98cg,681
|
|
60
|
-
schema/serde/__init__.py,sha256=xyydIcWB5IUpcn3wu1m9HL1fK4gMWURbwTyRsQtolF0,975
|
|
61
|
-
schema/serde/constants.py,sha256=aYtD_RuA0GCkpC4TIQq3VjMqEc837Wn-TlJNm-yn_4Y,1842
|
|
62
|
-
schema/serde/distribution.py,sha256=jy3h6JD1TSs4gwociMis814sz_Fm2kFQ2UbkgjYJW9k,19347
|
|
63
|
-
schema/serde/eda_spec.py,sha256=uOqBeZpUU3Dzzc19rU1LjHWmUhRmVcx8oIZvZfVJHT8,7180
|
|
64
|
-
schema/serde/function_registry.py,sha256=GbgC5_9NDcA9Y7nqmdJ-4-LK5JPhhfI50Lmfy5ZBJOg,4858
|
|
65
|
-
schema/serde/hyperparameters.py,sha256=Igm-PZmIozrsKZH6c-XkrU_Nlf8OAuxpnJJfv7W1SfQ,13524
|
|
66
|
-
schema/serde/inference_data.py,sha256=DrwE9hU8LMrl0z8W_sUSIaPrRdym_lu0iOqpT4KZxsA,3623
|
|
67
|
-
schema/serde/marketing_data.py,sha256=yb-fRTe84Sjg7-v3wsvYRRXvrxLSFWSenO0_ikMvUpk,44845
|
|
68
|
-
schema/serde/meridian_serde.py,sha256=ZG05JaBG4LW8mhl-Cunje9Q6xyR4tyNTtLYedzMBYjA,15985
|
|
69
|
-
schema/serde/serde.py,sha256=8vUqhJxvZgX9UY3rXTyWJznRgapwDzzaHXDHwV_kKTA,1612
|
|
70
|
-
schema/serde/test_data.py,sha256=7hfEWyvZ9WcAkVAOXt6elX8stJlsfhfd-ASlHo9SRb8,107342
|
|
71
|
-
schema/utils/__init__.py,sha256=AkC4NMbmXC3PFBY9dFYxlf3qFsxt5OOBVdc9zmFXsC8,675
|
|
72
|
-
schema/utils/time_record.py,sha256=-KzHFjvSBUUXsfESPAfcJP_VFxaFLqj90Ac0kgKWfpI,4624
|
|
73
|
-
google_meridian-1.3.1.dist-info/METADATA,sha256=wu5D6r6v46vd5g4uKLfXEVuhxkO3jwEgS68wn6m0jR4,9547
|
|
74
|
-
google_meridian-1.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
-
google_meridian-1.3.1.dist-info/top_level.txt,sha256=yWkWDLV_UUanhKmk_xNPiKNdPDl1oyU1sBYwEnhaSf4,16
|
|
76
|
-
google_meridian-1.3.1.dist-info/RECORD,,
|