himoto / hillfit

Fitting the Hill Equation to Experimental Data
MIT License
11 stars 4 forks source link

Possibility to modify existing Hill equation #29

Open timbo112711 opened 1 year ago

timbo112711 commented 1 year ago

If one wanted to modify the current Hill equation from

y = bottom + ((top - bottom) * x**nH) / (EC50**nH+ x**nH)

to

y = beta - ((beta * EC50**nH) / (x**nH + EC50**nH))

where beta is a regression coefficient from an OLS Media Mix Model.

would there be an easy solution for this or would the whole HillFit class need to be modified?

Reference: Section 2.2, equation 5 Bayesian Methods for Media Mix Modeling with Carryover and Shape Effects

himoto commented 1 year ago

Hello @timbo112711, thank you for your inquiry. I created beta-hill branch that you could try out. Although experimental, it supports user-defined fitting function, e.g.,

y = beta - ((beta * EC50**nH) / (x**nH + EC50**nH))

How to use:

x_data = [0.01, 1.01, 1.38, 1.63, 1.88, 2.13, 2.38, 2.63, 2.88, 3.13, 3.38,
                3.63, 3.88, 4.13, 4.38, 4.63, 4.88, 5.13, 5.38, 5.63, 5.88, 6.13,
                6.38, 6.63, 8.38]
y_data = [0.     , 0.     , 0.     , 0.03334, 0.04444, 0.11364, 0.19048,
                0.3063 , 0.32   , 0.62366, 0.78   , 0.94444, 0.9495 , 1.26416,
                1.54286, 1.50428, 1.61224, 1.85568, 1.88334, 1.86274, 1.91804,
                1.92792, 1.95744, 1.96492, 2.]

from hillfit.experimental import UserDefined

def beta_hill(x, beta, EC50, nH):
    '''User-defined fitting func.'''
    y = beta - ((beta * EC50**nH) / (x**nH + EC50**nH))
    return y

myfunc = UserDefined(x_data, y_data, beta_hill)
myfunc.fitting(curve_fit_kws={"maxfev": 1000})

I would be grateful if you could give me comments on this new feature.

Best, Hiroaki

timbo112711 commented 1 year ago

Hi @himoto, thank you so much for the quick reply on this. My team and I will try out the new beta-hill branch. Feedback to come!

Cheers, Tim

timbo112711 commented 1 year ago

Hello @himoto,

Thanks again for this quick experimental feature!

We were giving it a test run and had a question on how we would go about inputting the beta arg for the UserDefined equation. Here is an example:

we would run an OLS regression that looks something like this: sales = int + βTV + βRadio + βYouTube

where β is the regression coefficient.

Looking only at TV here, say β was 0.980. We want to take the 0.980 and inject that into the Hill equation. Something like this:

y = 0.980 - ((0.980 * EC50**nH) / (x**nH + EC50**nH)).

This could be a result of my not so great programming skills, but would we add that 0.980 into curve_fit_kws arg while fitting?

himoto commented 1 year ago

Hi @timbo112711,

If I understand what you would like to do correctly, you can do it in following two ways:

  1. Define a fitting function without beta:
def beta_hill(x, EC50, nH):
    '''User-defined fitting func.'''
    y = 0.980 - ((0.980 * EC50**nH) / (x**nH + EC50**nH))
    return y

Then run the rest of code I showed.

  1. Specify "bounds" in curve_fit_kws:

You can specify lower and upper bounds on parameters via curve_fit_kws["bounds"]. In your case, since you do not have to search beta, you can set search bounds in the following way:

import sys

myfunc.fitting(
    curve_fit_kws={
        "bounds": ((0.980 - sys.float_info.epsilon, -np.inf, -np.inf), (0.980, np.inf, np.inf)
    }
)
# beta is fixed to 0.980, EC50 and nH are searched in (-inf, inf).

The reason I subtract sys.float_info.epsilon, infinitesimal value, is that without doing this, you will get a ValueError:

ValueError: Each lower bound must be strictly less than each upper bound.
timbo112711 commented 1 year ago

Hi @himoto,

Got it that make sense. Thanks for the guidance. Stay tuned!