google-meridian 1.3.2__py3-none-any.whl → 1.5.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.
Files changed (78) hide show
  1. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/METADATA +18 -11
  2. google_meridian-1.5.0.dist-info/RECORD +112 -0
  3. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/WHEEL +1 -1
  4. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/top_level.txt +1 -0
  5. meridian/analysis/analyzer.py +558 -398
  6. meridian/analysis/optimizer.py +90 -68
  7. meridian/analysis/review/reviewer.py +4 -1
  8. meridian/analysis/summarizer.py +13 -3
  9. meridian/analysis/test_utils.py +2911 -2102
  10. meridian/analysis/visualizer.py +37 -14
  11. meridian/backend/__init__.py +106 -0
  12. meridian/constants.py +2 -0
  13. meridian/data/input_data.py +30 -52
  14. meridian/data/input_data_builder.py +2 -9
  15. meridian/data/test_utils.py +107 -51
  16. meridian/data/validator.py +48 -0
  17. meridian/mlflow/autolog.py +19 -9
  18. meridian/model/__init__.py +2 -0
  19. meridian/model/adstock_hill.py +3 -5
  20. meridian/model/context.py +1059 -0
  21. meridian/model/eda/constants.py +335 -4
  22. meridian/model/eda/eda_engine.py +723 -312
  23. meridian/model/eda/eda_outcome.py +177 -33
  24. meridian/model/equations.py +418 -0
  25. meridian/model/knots.py +58 -47
  26. meridian/model/model.py +228 -878
  27. meridian/model/model_test_data.py +38 -0
  28. meridian/model/posterior_sampler.py +103 -62
  29. meridian/model/prior_sampler.py +114 -94
  30. meridian/model/spec.py +23 -14
  31. meridian/templates/card.html.jinja +9 -7
  32. meridian/templates/chart.html.jinja +1 -6
  33. meridian/templates/finding.html.jinja +19 -0
  34. meridian/templates/findings.html.jinja +33 -0
  35. meridian/templates/formatter.py +41 -5
  36. meridian/templates/formatter_test.py +127 -0
  37. meridian/templates/style.css +66 -9
  38. meridian/templates/style.scss +85 -4
  39. meridian/templates/table.html.jinja +1 -0
  40. meridian/version.py +1 -1
  41. scenarioplanner/__init__.py +42 -0
  42. scenarioplanner/converters/__init__.py +25 -0
  43. scenarioplanner/converters/dataframe/__init__.py +28 -0
  44. scenarioplanner/converters/dataframe/budget_opt_converters.py +383 -0
  45. scenarioplanner/converters/dataframe/common.py +71 -0
  46. scenarioplanner/converters/dataframe/constants.py +137 -0
  47. scenarioplanner/converters/dataframe/converter.py +42 -0
  48. scenarioplanner/converters/dataframe/dataframe_model_converter.py +70 -0
  49. scenarioplanner/converters/dataframe/marketing_analyses_converters.py +543 -0
  50. scenarioplanner/converters/dataframe/rf_opt_converters.py +314 -0
  51. scenarioplanner/converters/mmm.py +743 -0
  52. scenarioplanner/converters/mmm_converter.py +58 -0
  53. scenarioplanner/converters/sheets.py +156 -0
  54. scenarioplanner/converters/test_data.py +714 -0
  55. scenarioplanner/linkingapi/__init__.py +47 -0
  56. scenarioplanner/linkingapi/constants.py +27 -0
  57. scenarioplanner/linkingapi/url_generator.py +131 -0
  58. scenarioplanner/mmm_ui_proto_generator.py +355 -0
  59. schema/__init__.py +5 -2
  60. schema/mmm_proto_generator.py +71 -0
  61. schema/model_consumer.py +133 -0
  62. schema/processors/__init__.py +77 -0
  63. schema/processors/budget_optimization_processor.py +832 -0
  64. schema/processors/common.py +64 -0
  65. schema/processors/marketing_processor.py +1137 -0
  66. schema/processors/model_fit_processor.py +367 -0
  67. schema/processors/model_kernel_processor.py +117 -0
  68. schema/processors/model_processor.py +415 -0
  69. schema/processors/reach_frequency_optimization_processor.py +584 -0
  70. schema/serde/distribution.py +12 -7
  71. schema/serde/hyperparameters.py +54 -107
  72. schema/serde/meridian_serde.py +6 -1
  73. schema/test_data.py +380 -0
  74. schema/utils/__init__.py +2 -0
  75. schema/utils/date_range_bucketing.py +117 -0
  76. schema/utils/proto_enum_converter.py +127 -0
  77. google_meridian-1.3.2.dist-info/RECORD +0 -76
  78. {google_meridian-1.3.2.dist-info → google_meridian-1.5.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
+ )