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.
@@ -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 `geometric decay` Adstock parameter for
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
- alpha_rf: Prior distribution on the `geometric decay` Adstock parameter for
181
- RF input. Default distribution is `Uniform(0.0, 1.0)`.
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 (np.isscalar(self.slope_m.loc.numpy()) and self.slope_m.loc != 1.0)
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 (np.isscalar(self.slope_om.loc.numpy()) and self.slope_om.loc != 1.0)
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.tfp.distributions.BatchBroadcast):
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
- prevent_deterministic_prior_at_bounds[i]
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
- f' `quantile` method implemented, so the support range validation'
1351
+ ' `quantile` method implemented, so the support range validation'
1288
1352
  f' was skipped. Confirm that your prior for {parameter_name} is'
1289
- f' appropriate.'
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 = {
@@ -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.numpy_function(
77
- func=lambda x: np.nanmedian(x, axis=[0, 1]),
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(jit_compile=True)
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
@@ -14,4 +14,4 @@
14
14
 
15
15
  """Module for Meridian version."""
16
16
 
17
- __version__ = "1.2.0"
17
+ __version__ = "1.2.1"