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.
- {google_meridian-1.2.0.dist-info → google_meridian-1.2.1.dist-info}/METADATA +2 -2
- {google_meridian-1.2.0.dist-info → google_meridian-1.2.1.dist-info}/RECORD +24 -24
- meridian/analysis/analyzer.py +101 -37
- meridian/analysis/optimizer.py +132 -88
- meridian/analysis/summarizer.py +31 -16
- meridian/analysis/visualizer.py +16 -5
- meridian/backend/__init__.py +475 -14
- meridian/backend/config.py +75 -16
- meridian/backend/test_utils.py +87 -1
- meridian/constants.py +14 -9
- meridian/data/input_data.py +7 -2
- meridian/data/test_utils.py +5 -3
- meridian/mlflow/autolog.py +2 -2
- meridian/model/adstock_hill.py +10 -9
- meridian/model/eda/eda_engine.py +440 -11
- meridian/model/knots.py +1 -1
- meridian/model/model_test_data.py +15 -9
- meridian/model/posterior_sampler.py +365 -365
- meridian/model/prior_distribution.py +104 -39
- meridian/model/transformers.py +5 -5
- meridian/version.py +1 -1
- {google_meridian-1.2.0.dist-info → google_meridian-1.2.1.dist-info}/WHEEL +0 -0
- {google_meridian-1.2.0.dist-info → google_meridian-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {google_meridian-1.2.0.dist-info → google_meridian-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,7 @@ used by the Meridian model object.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
22
|
+
|
|
22
23
|
from collections.abc import MutableMapping, Sequence
|
|
23
24
|
import dataclasses
|
|
24
25
|
from typing import Any
|
|
@@ -26,7 +27,6 @@ import warnings
|
|
|
26
27
|
|
|
27
28
|
from meridian import backend
|
|
28
29
|
from meridian import constants
|
|
29
|
-
|
|
30
30
|
import numpy as np
|
|
31
31
|
|
|
32
32
|
|
|
@@ -34,6 +34,7 @@ __all__ = [
|
|
|
34
34
|
'IndependentMultivariateDistribution',
|
|
35
35
|
'PriorDistribution',
|
|
36
36
|
'distributions_are_equal',
|
|
37
|
+
'lognormal_dist_from_mean_std',
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
|
|
@@ -175,14 +176,14 @@ class PriorDistribution:
|
|
|
175
176
|
xi_n: Prior distribution on the hierarchical standard deviation of
|
|
176
177
|
`gamma_gn` which is the coefficient on non-media channel `n` for geo `g`.
|
|
177
178
|
Hierarchy is defined over geos. Default distribution is `HalfNormal(5.0)`.
|
|
178
|
-
alpha_m: Prior distribution on the
|
|
179
|
+
alpha_m: Prior distribution on the Adstock decay parameter for media input.
|
|
180
|
+
Default distribution is `Uniform(0.0, 1.0)`.
|
|
181
|
+
alpha_rf: Prior distribution on the Adstock decay parameter for RF input.
|
|
182
|
+
Default distribution is `Uniform(0.0, 1.0)`.
|
|
183
|
+
alpha_om: Prior distribution on the Adstock decay parameter for organic
|
|
179
184
|
media input. Default distribution is `Uniform(0.0, 1.0)`.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
alpha_om: Prior distribution on the `geometric decay` Adstock parameter for
|
|
183
|
-
organic media input. Default distribution is `Uniform(0.0, 1.0)`.
|
|
184
|
-
alpha_orf: Prior distribution on the `geometric decay` Adstock parameter for
|
|
185
|
-
organic RF input. Default distribution is `Uniform(0.0, 1.0)`.
|
|
185
|
+
alpha_orf: Prior distribution on the Adstock decay parameter for organic RF
|
|
186
|
+
input. Default distribution is `Uniform(0.0, 1.0)`.
|
|
186
187
|
ec_m: Prior distribution on the `half-saturation` Hill parameter for media
|
|
187
188
|
input. Default distribution is `TruncatedNormal(0.8, 0.8, 0.1, 10)`.
|
|
188
189
|
ec_rf: Prior distribution on the `half-saturation` Hill parameter for RF
|
|
@@ -772,7 +773,7 @@ class PriorDistribution:
|
|
|
772
773
|
)
|
|
773
774
|
if (
|
|
774
775
|
not isinstance(self.slope_m, backend.tfd.Deterministic)
|
|
775
|
-
or (
|
|
776
|
+
or (backend.rank(self.slope_m.loc) == 0 and self.slope_m.loc != 1.0)
|
|
776
777
|
or (
|
|
777
778
|
self.slope_m.batch_shape.as_list()
|
|
778
779
|
and any(x != 1.0 for x in self.slope_m.loc)
|
|
@@ -791,7 +792,7 @@ class PriorDistribution:
|
|
|
791
792
|
)
|
|
792
793
|
if (
|
|
793
794
|
not isinstance(self.slope_om, backend.tfd.Deterministic)
|
|
794
|
-
or (
|
|
795
|
+
or (backend.rank(self.slope_om.loc) == 0 and self.slope_om.loc != 1.0)
|
|
795
796
|
or (
|
|
796
797
|
self.slope_om.batch_shape.as_list()
|
|
797
798
|
and any(x != 1.0 for x in self.slope_om.loc)
|
|
@@ -1000,8 +1001,7 @@ class IndependentMultivariateDistribution(backend.tfd.Distribution):
|
|
|
1000
1001
|
"""Check for deterministic distributions and raise an error if found."""
|
|
1001
1002
|
|
|
1002
1003
|
if any(
|
|
1003
|
-
isinstance(dist, backend.tfd.Deterministic)
|
|
1004
|
-
for dist in distributions
|
|
1004
|
+
isinstance(dist, backend.tfd.Deterministic) for dist in distributions
|
|
1005
1005
|
):
|
|
1006
1006
|
raise ValueError(
|
|
1007
1007
|
f'{self.__class__.__name__} cannot contain `Deterministic` '
|
|
@@ -1029,9 +1029,7 @@ class IndependentMultivariateDistribution(backend.tfd.Distribution):
|
|
|
1029
1029
|
[dist.batch_shape_tensor() for dist in self._distributions],
|
|
1030
1030
|
axis=0,
|
|
1031
1031
|
)
|
|
1032
|
-
return backend.reduce_sum(
|
|
1033
|
-
distribution_batch_shape_tensors, keepdims=True
|
|
1034
|
-
)
|
|
1032
|
+
return backend.reduce_sum(distribution_batch_shape_tensors, keepdims=True)
|
|
1035
1033
|
|
|
1036
1034
|
def _batch_shape(self):
|
|
1037
1035
|
return backend.TensorShape(sum(self._distribution_batch_shapes))
|
|
@@ -1043,10 +1041,7 @@ class IndependentMultivariateDistribution(backend.tfd.Distribution):
|
|
|
1043
1041
|
|
|
1044
1042
|
def _quantile(self, value):
|
|
1045
1043
|
value = self._broadcast_value(value)
|
|
1046
|
-
split_value = backend.split(
|
|
1047
|
-
value,
|
|
1048
|
-
self._distribution_batch_shapes, axis=-1
|
|
1049
|
-
)
|
|
1044
|
+
split_value = backend.split(value, self._distribution_batch_shapes, axis=-1)
|
|
1050
1045
|
quantiles = [
|
|
1051
1046
|
dist.quantile(sv) for dist, sv in zip(self._distributions, split_value)
|
|
1052
1047
|
]
|
|
@@ -1055,11 +1050,7 @@ class IndependentMultivariateDistribution(backend.tfd.Distribution):
|
|
|
1055
1050
|
|
|
1056
1051
|
def _log_prob(self, value):
|
|
1057
1052
|
value = self._broadcast_value(value)
|
|
1058
|
-
split_value = backend.split(
|
|
1059
|
-
value,
|
|
1060
|
-
self._distribution_batch_shapes,
|
|
1061
|
-
axis=-1
|
|
1062
|
-
)
|
|
1053
|
+
split_value = backend.split(value, self._distribution_batch_shapes, axis=-1)
|
|
1063
1054
|
log_probs = [
|
|
1064
1055
|
dist.log_prob(sv) for dist, sv in zip(self._distributions, split_value)
|
|
1065
1056
|
]
|
|
@@ -1068,11 +1059,7 @@ class IndependentMultivariateDistribution(backend.tfd.Distribution):
|
|
|
1068
1059
|
|
|
1069
1060
|
def _log_cdf(self, value):
|
|
1070
1061
|
value = self._broadcast_value(value)
|
|
1071
|
-
split_value = backend.split(
|
|
1072
|
-
value,
|
|
1073
|
-
self._distribution_batch_shapes,
|
|
1074
|
-
axis=-1
|
|
1075
|
-
)
|
|
1062
|
+
split_value = backend.split(value, self._distribution_batch_shapes, axis=-1)
|
|
1076
1063
|
|
|
1077
1064
|
log_cdfs = [
|
|
1078
1065
|
dist.log_cdf(sv) for dist, sv in zip(self._distributions, split_value)
|
|
@@ -1173,6 +1160,87 @@ def distributions_are_equal(
|
|
|
1173
1160
|
return True
|
|
1174
1161
|
|
|
1175
1162
|
|
|
1163
|
+
def lognormal_dist_from_mean_std(
|
|
1164
|
+
mean: float | Sequence[float], std: float | Sequence[float]
|
|
1165
|
+
) -> backend.tfd.LogNormal:
|
|
1166
|
+
"""Define a lognormal distribution from its mean and standard deviation.
|
|
1167
|
+
|
|
1168
|
+
This function parameterizes lognormal distributions by their mean and
|
|
1169
|
+
standard deviation.
|
|
1170
|
+
|
|
1171
|
+
Args:
|
|
1172
|
+
mean: A float or array-like object defining the distribution mean. Must be
|
|
1173
|
+
positive.
|
|
1174
|
+
std: A float or array-like object defining the distribution standard
|
|
1175
|
+
deviation. Must be non-negative.
|
|
1176
|
+
|
|
1177
|
+
Returns:
|
|
1178
|
+
A `backend.tfd.LogNormal` object with the input mean and standard deviation.
|
|
1179
|
+
"""
|
|
1180
|
+
|
|
1181
|
+
mean = np.asarray(mean)
|
|
1182
|
+
std = np.asarray(std)
|
|
1183
|
+
|
|
1184
|
+
mu = np.log(mean) - 0.5 * np.log((std / mean) ** 2 + 1)
|
|
1185
|
+
sigma = np.sqrt(np.log((std / mean) ** 2 + 1))
|
|
1186
|
+
|
|
1187
|
+
return backend.tfd.LogNormal(mu, sigma)
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def lognormal_dist_from_range(
|
|
1191
|
+
low: float | Sequence[float],
|
|
1192
|
+
high: float | Sequence[float],
|
|
1193
|
+
mass_percent: float | Sequence[float] = 0.95,
|
|
1194
|
+
) -> backend.tfd.LogNormal:
|
|
1195
|
+
"""Define a LogNormal distribution from a specified range.
|
|
1196
|
+
|
|
1197
|
+
This function parameterizes lognormal distributions by the bounds of a range,
|
|
1198
|
+
so that the specificed probability mass falls within the bounds defined by
|
|
1199
|
+
`low` and `high`. The probability mass is symmetric about the median. For
|
|
1200
|
+
example, to define a lognormal distribution with a 95% probability mass of
|
|
1201
|
+
(1, 10), use:
|
|
1202
|
+
|
|
1203
|
+
```python
|
|
1204
|
+
lognormal = lognormal_dist_from_range(1.0, 10.0, mass_percent=0.95)
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
Args:
|
|
1208
|
+
low: Float or array-like denoting the lower bound of the range. Values must
|
|
1209
|
+
be non-negative.
|
|
1210
|
+
high: Float or array-like denoting the upper bound of range. Values must be
|
|
1211
|
+
non-negative.
|
|
1212
|
+
mass_percent: Float or array-like denoting the probability mass. Values must
|
|
1213
|
+
be between 0 and 1 (exlusive). Default: 0.95.
|
|
1214
|
+
|
|
1215
|
+
Returns:
|
|
1216
|
+
A `backend.tfd.LogNormal` object with the input percentage mass falling
|
|
1217
|
+
within the given range.
|
|
1218
|
+
"""
|
|
1219
|
+
low = np.asarray(low)
|
|
1220
|
+
high = np.asarray(high)
|
|
1221
|
+
mass_percent = np.asarray(mass_percent)
|
|
1222
|
+
|
|
1223
|
+
if not ((0.0 < low).all() and (low < high).all()): # pytype: disable=attribute-error
|
|
1224
|
+
raise ValueError("'low' and 'high' values must be non-negative and satisfy "
|
|
1225
|
+
"high > low.")
|
|
1226
|
+
|
|
1227
|
+
if not ((0.0 < mass_percent).all() and (mass_percent < 1.0).all()): # pytype: disable=attribute-error
|
|
1228
|
+
raise ValueError(
|
|
1229
|
+
"'mass_percent' values must be between 0 and 1, exclusive."
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
normal = backend.tfd.Normal(0, 1)
|
|
1233
|
+
mass_lower = 0.5 - (mass_percent / 2)
|
|
1234
|
+
mass_upper = 0.5 + (mass_percent / 2)
|
|
1235
|
+
|
|
1236
|
+
sigma = np.log(high / low) / (
|
|
1237
|
+
normal.quantile(mass_upper) - normal.quantile(mass_lower)
|
|
1238
|
+
)
|
|
1239
|
+
mu = np.log(high) - normal.quantile(mass_upper) * sigma
|
|
1240
|
+
|
|
1241
|
+
return backend.tfd.LogNormal(mu, sigma)
|
|
1242
|
+
|
|
1243
|
+
|
|
1176
1244
|
def _convert_to_deterministic_0_distribution(
|
|
1177
1245
|
distribution: backend.tfd.Distribution,
|
|
1178
1246
|
) -> backend.tfd.Distribution:
|
|
@@ -1257,21 +1325,17 @@ def _validate_support(
|
|
|
1257
1325
|
"""
|
|
1258
1326
|
# Note that `tfp.distributions.BatchBroadcast` objects have a `distribution`
|
|
1259
1327
|
# attribute that points to a `tfp.distributions.Distribution` object.
|
|
1260
|
-
if isinstance(tfp_dist, backend.
|
|
1328
|
+
if isinstance(tfp_dist, backend.tfd.BatchBroadcast):
|
|
1261
1329
|
tfp_dist = tfp_dist.distribution
|
|
1262
1330
|
# Note that `tfp.distributions.Deterministic` does not have a `quantile`
|
|
1263
1331
|
# method implemented, so the min and max values must be extracted from the
|
|
1264
1332
|
# `loc` attribute instead.
|
|
1265
|
-
if isinstance(
|
|
1266
|
-
tfp_dist,
|
|
1267
|
-
backend.tfp.python.distributions.deterministic.Deterministic
|
|
1268
|
-
):
|
|
1333
|
+
if isinstance(tfp_dist, backend.tfd.Deterministic):
|
|
1269
1334
|
support_min_vals = tfp_dist.loc
|
|
1270
1335
|
support_max_vals = tfp_dist.loc
|
|
1271
1336
|
for i in (0, 1):
|
|
1272
|
-
if (
|
|
1273
|
-
|
|
1274
|
-
and np.any(tfp_dist.loc == bounds[i])
|
|
1337
|
+
if prevent_deterministic_prior_at_bounds[i] and np.any(
|
|
1338
|
+
tfp_dist.loc == bounds[i]
|
|
1275
1339
|
):
|
|
1276
1340
|
raise ValueError(
|
|
1277
1341
|
f'{parameter_name} was assigned a point mass (deterministic) prior'
|
|
@@ -1284,9 +1348,9 @@ def _validate_support(
|
|
|
1284
1348
|
except (AttributeError, NotImplementedError):
|
|
1285
1349
|
warnings.warn(
|
|
1286
1350
|
f'The prior distribution for {parameter_name} does not have a'
|
|
1287
|
-
|
|
1351
|
+
' `quantile` method implemented, so the support range validation'
|
|
1288
1352
|
f' was skipped. Confirm that your prior for {parameter_name} is'
|
|
1289
|
-
|
|
1353
|
+
' appropriate.'
|
|
1290
1354
|
)
|
|
1291
1355
|
return
|
|
1292
1356
|
if np.any(support_min_vals < bounds[0]):
|
|
@@ -1300,6 +1364,7 @@ def _validate_support(
|
|
|
1300
1364
|
f' greater than the parameter maximum {bounds[1]}.'
|
|
1301
1365
|
)
|
|
1302
1366
|
|
|
1367
|
+
|
|
1303
1368
|
# Dictionary of parameters that have a limited parameters space. The tuple
|
|
1304
1369
|
# contains the lower and upper bounds, respectively.
|
|
1305
1370
|
_parameter_space_bounds = {
|
meridian/model/transformers.py
CHANGED
|
@@ -73,10 +73,8 @@ class MediaTransformer(TensorTransformer):
|
|
|
73
73
|
)
|
|
74
74
|
# Tensor of medians of the positive portion of `media`. Used as a component
|
|
75
75
|
# for scaling.
|
|
76
|
-
self._population_scaled_median_m = backend.
|
|
77
|
-
|
|
78
|
-
inp=[population_scaled_media_nan],
|
|
79
|
-
Tout=backend.float32,
|
|
76
|
+
self._population_scaled_median_m = backend.nanmedian(
|
|
77
|
+
population_scaled_media_nan, axis=(0, 1)
|
|
80
78
|
)
|
|
81
79
|
if backend.reduce_any(backend.is_nan(self._population_scaled_median_m)):
|
|
82
80
|
raise ValueError(
|
|
@@ -145,7 +143,9 @@ class CenteringAndScalingTransformer(TensorTransformer):
|
|
|
145
143
|
self._means = backend.reduce_mean(tensor, axis=(0, 1))
|
|
146
144
|
self._stdevs = backend.reduce_std(tensor, axis=(0, 1))
|
|
147
145
|
|
|
148
|
-
@backend.function(
|
|
146
|
+
@backend.function(
|
|
147
|
+
jit_compile=True, static_argnames="apply_population_scaling"
|
|
148
|
+
)
|
|
149
149
|
def forward(
|
|
150
150
|
self, tensor: backend.Tensor, apply_population_scaling: bool = True
|
|
151
151
|
) -> backend.Tensor:
|
meridian/version.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|