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
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
"""Converts a fully specified trained model and analysis output.
|
|
16
|
+
|
|
17
|
+
A fully specified trained model and its analyses are in its canonical proto
|
|
18
|
+
form. This module provides the API for its conversion to secondary forms
|
|
19
|
+
(e.g. flat CSV tables collated in a Sheets file) for immediate consumption
|
|
20
|
+
(e.g. as data sources for a Looker Studio dashboard).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import abc
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
from typing import Generic, TypeVar
|
|
26
|
+
|
|
27
|
+
from mmm.v1 import mmm_pb2 as pb
|
|
28
|
+
from scenarioplanner.converters import mmm
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ['ModelConverter']
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# The output type of a converter.
|
|
35
|
+
O = TypeVar('O')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ModelConverter(abc.ABC, Generic[O]):
|
|
39
|
+
"""Converts a fully specified trained model to secondary form(s) `O`.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
mmm: An `Mmm` proto containing a trained model and its optional analyses.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
mmm_proto: pb.Mmm,
|
|
48
|
+
):
|
|
49
|
+
self._mmm = mmm.Mmm(mmm_proto)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def mmm(self) -> mmm.Mmm:
|
|
53
|
+
return self._mmm
|
|
54
|
+
|
|
55
|
+
@abc.abstractmethod
|
|
56
|
+
def __call__(self, **kwargs) -> Mapping[str, O]:
|
|
57
|
+
"""Converts bound `MmmOutput` proto to named secondary form(s) `O`."""
|
|
58
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
"""Utility library for compiling Google sheets.
|
|
16
|
+
|
|
17
|
+
This library requires authentication.
|
|
18
|
+
|
|
19
|
+
* If you're developing locally, set up Application Default Credentials (ADC)
|
|
20
|
+
in
|
|
21
|
+
your local environment:
|
|
22
|
+
|
|
23
|
+
<https://cloud.google.com/docs/authentication/application-default-credentials>
|
|
24
|
+
|
|
25
|
+
* If you're working in Colab, run the following command in a cell to
|
|
26
|
+
authenticate:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from google.colab import auth
|
|
30
|
+
auth.authenticate_user()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This command opens a window where you can complete the authentication.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from collections.abc import Mapping
|
|
37
|
+
import dataclasses
|
|
38
|
+
|
|
39
|
+
import google.auth
|
|
40
|
+
from googleapiclient import discovery
|
|
41
|
+
import numpy as np
|
|
42
|
+
import pandas as pd
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"Spreadsheet",
|
|
47
|
+
"upload_to_gsheet",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# https://developers.google.com/sheets/api/scopes#sheets-scopes
|
|
52
|
+
_SCOPES = [
|
|
53
|
+
"https://www.googleapis.com/auth/spreadsheets",
|
|
54
|
+
"https://www.googleapis.com/auth/drive",
|
|
55
|
+
]
|
|
56
|
+
_DEFAULT_SHEET_ID = 0
|
|
57
|
+
_ADD_SHEET_REQUEST_NAME = "addSheet"
|
|
58
|
+
_DELETE_SHEET_REQUEST_NAME = "deleteSheet"
|
|
59
|
+
_DEFAULT_SPREADSHEET_NAME = "Meridian Looker Studio Data"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclasses.dataclass(frozen=True)
|
|
63
|
+
class Spreadsheet:
|
|
64
|
+
"""Spreadsheet data class.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
url: URL of the spreadsheet.
|
|
68
|
+
id: ID of the spreadsheet.
|
|
69
|
+
sheet_id_by_tab_name: Mapping of sheet tab names to sheet IDs.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
url: str
|
|
73
|
+
id: str
|
|
74
|
+
sheet_id_by_tab_name: Mapping[str, int]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def upload_to_gsheet(
|
|
78
|
+
data: Mapping[str, pd.DataFrame],
|
|
79
|
+
credentials: google.auth.credentials.Credentials | None = None,
|
|
80
|
+
spreadsheet_name: str = _DEFAULT_SPREADSHEET_NAME,
|
|
81
|
+
) -> Spreadsheet:
|
|
82
|
+
"""Creates new spreadsheet.
|
|
83
|
+
|
|
84
|
+
Loads pre-authorized user credentials from the environment.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
data: Mapping of tab name to dataframe.
|
|
88
|
+
credentials: Optional credentials from the user.
|
|
89
|
+
spreadsheet_name: Name of the spreadsheet.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Spreadsheet data class.
|
|
93
|
+
"""
|
|
94
|
+
if credentials is None:
|
|
95
|
+
credentials, _ = google.auth.default(scopes=_SCOPES)
|
|
96
|
+
service = discovery.build("sheets", "v4", credentials=credentials)
|
|
97
|
+
spreadsheet = (
|
|
98
|
+
service.spreadsheets()
|
|
99
|
+
.create(body={"properties": {"title": spreadsheet_name}})
|
|
100
|
+
.execute()
|
|
101
|
+
)
|
|
102
|
+
spreadsheet_id = spreadsheet["spreadsheetId"]
|
|
103
|
+
|
|
104
|
+
# Build requests to add a worksheets and fill them in.
|
|
105
|
+
tab_requests = []
|
|
106
|
+
values_request_body = {
|
|
107
|
+
"data": [],
|
|
108
|
+
"valueInputOption": "USER_ENTERED",
|
|
109
|
+
}
|
|
110
|
+
data = {
|
|
111
|
+
k: v.replace([np.inf, -np.inf, np.nan], None) for k, v in data.items()
|
|
112
|
+
}
|
|
113
|
+
for tab_name, dataframe in data.items():
|
|
114
|
+
tab_requests.append(
|
|
115
|
+
{_ADD_SHEET_REQUEST_NAME: {"properties": {"title": tab_name}}}
|
|
116
|
+
)
|
|
117
|
+
values_request_body["data"].append({
|
|
118
|
+
"values": (
|
|
119
|
+
[dataframe.columns.values.tolist()] + dataframe.values.tolist()
|
|
120
|
+
),
|
|
121
|
+
"range": f"{tab_name}!A1",
|
|
122
|
+
})
|
|
123
|
+
# Delete first default tab
|
|
124
|
+
tab_requests.append(
|
|
125
|
+
{_DELETE_SHEET_REQUEST_NAME: {"sheetId": _DEFAULT_SHEET_ID}}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
created_tab_objects = (
|
|
129
|
+
service.spreadsheets()
|
|
130
|
+
.batchUpdate(
|
|
131
|
+
spreadsheetId=spreadsheet_id, body={"requests": tab_requests}
|
|
132
|
+
)
|
|
133
|
+
.execute()
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
sheet_id_by_tab_name = dict()
|
|
137
|
+
for tab in created_tab_objects["replies"]:
|
|
138
|
+
if _ADD_SHEET_REQUEST_NAME not in tab:
|
|
139
|
+
continue
|
|
140
|
+
add_sheet_response_properties = tab.get(_ADD_SHEET_REQUEST_NAME).get(
|
|
141
|
+
"properties"
|
|
142
|
+
)
|
|
143
|
+
tab_name = add_sheet_response_properties.get("title")
|
|
144
|
+
sheet_id = add_sheet_response_properties.get("sheetId")
|
|
145
|
+
sheet_id_by_tab_name[tab_name] = sheet_id
|
|
146
|
+
|
|
147
|
+
# Fill in the data.
|
|
148
|
+
service.spreadsheets().values().batchUpdate(
|
|
149
|
+
spreadsheetId=spreadsheet_id, body=values_request_body
|
|
150
|
+
).execute()
|
|
151
|
+
|
|
152
|
+
return Spreadsheet(
|
|
153
|
+
url=spreadsheet.get("spreadsheetUrl"),
|
|
154
|
+
id=spreadsheet_id,
|
|
155
|
+
sheet_id_by_tab_name=sheet_id_by_tab_name,
|
|
156
|
+
)
|