@@ -899,13 +899,86 @@ def michaelis_menten(
899899 return alpha * x / (lam + x )
900900
901901
902- def hill_saturation (
902+ def hill_function (
903+ x : pt .TensorLike , slope : pt .TensorLike , kappa : pt .TensorLike
904+ ) -> pt .TensorVariable :
905+ r"""Hill Function
906+
907+ .. math::
908+ f(x) = 1 - \frac{\kappa^s}{\kappa^s + x^s}
909+
910+ where:
911+ - :math:`s` is the slope of the hill.
912+ - :math:`\kappa` is the half-saturation point as :math:`f(\kappa) = 0.5` for any value of :math:`s` and :math:`\kappa`.
913+ - :math:`x` is the independent variable and must be non-negative.
914+
915+ Hill function from Equation (5) in the paper [1]_.
916+
917+ .. plot::
918+ :context: close-figs
919+
920+ import numpy as np
921+ import matplotlib.pyplot as plt
922+ from pymc_marketing.mmm.transformers import hill_function
923+ x = np.linspace(0, 10, 100)
924+ # Varying slope
925+ slopes = [0.3, 0.7, 1.2]
926+ fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
927+ for i, slope in enumerate(slopes):
928+ plt.subplot(1, 3, i+1)
929+ y = hill_function(x, slope, 2).eval()
930+ plt.plot(x, y)
931+ plt.xlabel('x')
932+ plt.title(f'Slope = {slope}')
933+ plt.subplot(1,3,1)
934+ plt.ylabel('Hill Saturation Sigmoid')
935+ plt.tight_layout()
936+ plt.show()
937+ # Varying kappa
938+ kappas = [1, 5, 10]
939+ fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
940+ for i, kappa in enumerate(kappas):
941+ plt.subplot(1, 3, i+1)
942+ y = hill_function(x, 1, kappa).eval()
943+ plt.plot(x, y)
944+ plt.xlabel('x')
945+ plt.title(f'Kappa = {kappa}')
946+ plt.subplot(1,3,1)
947+ plt.ylabel('Hill Saturation Sigmoid')
948+ plt.tight_layout()
949+ plt.show()
950+
951+ Parameters
952+ ----------
953+ x : float or array-like
954+ The independent variable, typically representing the concentration of a
955+ substrate or the intensity of a stimulus.
956+ slope : float
957+ The slope of the hill. Must pe non-positive.
958+ kappa : float
959+ The half-saturation point as :math:`f(\kappa) = 0.5` for any value of :math:`s` and :math:`\kappa`.
960+
961+ Returns
962+ -------
963+ float
964+ The value of the Hill function given the parameters.
965+
966+ References
967+ ----------
968+ .. [1] Jin, Yuxue, et al. “Bayesian methods for media mix modeling with carryover and shape effects.” (2017).
969+ """ # noqa: E501
970+ return pt .as_tensor_variable (
971+ 1 - pt .power (kappa , slope ) / (pt .power (kappa , slope ) + pt .power (x , slope ))
972+ )
973+
974+
975+ def hill_saturation_sigmoid (
903976 x : pt .TensorLike ,
904977 sigma : pt .TensorLike ,
905978 beta : pt .TensorLike ,
906979 lam : pt .TensorLike ,
907980) -> pt .TensorVariable :
908- r"""Hill Saturation Function
981+ r"""Hill Saturation Sigmoid Function
909982
910983 .. math::
911984 f(x) = \frac{\sigma}{1 + e^{-\beta(x - \lambda)}} - \frac{\sigma}{1 + e^{\beta\lambda}}
@@ -929,45 +1002,45 @@ def hill_saturation(
9291002
9301003 import numpy as np
9311004 import matplotlib.pyplot as plt
932- from pymc_marketing.mmm.transformers import hill_saturation
1005+ from pymc_marketing.mmm.transformers import hill_saturation_sigmoid
9331006 x = np.linspace(0, 10, 100)
9341007 # Varying sigma
9351008 sigmas = [0.5, 1, 1.5]
9361009 fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
9371010 for i, sigma in enumerate(sigmas):
9381011 plt.subplot(1, 3, i+1)
939- y = hill_saturation (x, sigma, 2, 5).eval()
1012+ y = hill_saturation_sigmoid (x, sigma, 2, 5).eval()
9401013 plt.plot(x, y)
9411014 plt.xlabel('x')
9421015 plt.title(f'Sigma = {sigma}')
9431016 plt.subplot(1,3,1)
944- plt.ylabel('Hill Saturation')
1017+ plt.ylabel('Hill Saturation Sigmoid ')
9451018 plt.tight_layout()
9461019 plt.show()
9471020 # Varying beta
9481021 betas = [1, 2, 3]
9491022 fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
9501023 for i, beta in enumerate(betas):
9511024 plt.subplot(1, 3, i+1)
952- y = hill_saturation (x, 1, beta, 5).eval()
1025+ y = hill_saturation_sigmoid (x, 1, beta, 5).eval()
9531026 plt.plot(x, y)
9541027 plt.xlabel('x')
9551028 plt.title(f'Beta = {beta}')
9561029 plt.subplot(1,3,1)
957- plt.ylabel('Hill Saturation')
1030+ plt.ylabel('Hill Saturation Sigmoid ')
9581031 plt.tight_layout()
9591032 plt.show()
9601033 # Varying lam
9611034 lams = [3, 5, 7]
9621035 fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
9631036 for i, lam in enumerate(lams):
9641037 plt.subplot(1, 3, i+1)
965- y = hill_saturation (x, 1, 2, lam).eval()
1038+ y = hill_saturation_sigmoid (x, 1, 2, lam).eval()
9661039 plt.plot(x, y)
9671040 plt.xlabel('x')
9681041 plt.title(f'Lambda = {lam}')
9691042 plt.subplot(1,3,1)
970- plt.ylabel('Hill Saturation')
1043+ plt.ylabel('Hill Saturation Sigmoid ')
9711044 plt.tight_layout()
9721045 plt.show()
9731046
@@ -977,8 +1050,8 @@ def hill_saturation(
9771050 The independent variable, typically representing the concentration of a
9781051 substrate or the intensity of a stimulus.
9791052 sigma : float
980- The upper asymptote of the curve, representing the maximum value the
981- function will approach as x grows large.
1053+ The upper asymptote of the curve, representing the approximate maximum value the
1054+ function will approach as x grows large. The true maximum value is at `sigma * (1 - 1 / (1 + exp(beta * lam)))`
9821055 beta : float
9831056 The slope parameter, determining the steepness of the curve.
9841057 lam : float
@@ -988,7 +1061,7 @@ def hill_saturation(
9881061 Returns
9891062 -------
9901063 float or array-like
991- The value of the Hill function for each input value of x.
1064+ The value of the Hill saturation sigmoid function for each input value of x.
9921065 """
9931066 return sigma / (1 + pt .exp (- beta * (x - lam ))) - sigma / (1 + pt .exp (beta * lam ))
9941067
0 commit comments