BINDING_MODEL
Overview
The BINDING_MODEL function fits a variety of ligand binding and sigmoidal dose-response models to experimental data. These models are fundamental in biochemistry, pharmacology, and biophysics for characterizing interactions between molecules such as receptor-ligand binding, enzyme kinetics, and drug-response relationships.
The function supports twelve distinct model types including single-site and two-site competitive binding, heterogeneous binding, rectangular hyperbola saturation, Boltzmann sigmoids, and rational function models. Each model is characterized by specific parameters that describe binding affinities, maximum binding capacities, or curve shape characteristics.
At its core, the function uses scipy.optimize.curve_fit from the SciPy library to perform non-linear least squares regression. The algorithm minimizes the sum of squared residuals between observed data and the model prediction:
\min_{\theta} \sum_{i=1}^{n} \left( y_i - f(x_i, \theta) \right)^2
where y_i represents observed values, x_i the independent variable (typically ligand concentration), and \theta the model parameters to be optimized.
For competitive binding models, the function uses variations of the sigmoidal dose-response equation. For example, the single-site competitive binding model follows:
Y = A_2 + \frac{A_1 - A_2}{1 + 10^{(x - \log x_0)}}
where A_1 and A_2 represent the upper and lower asymptotes, and \log x_0 is the inflection point (IC50 in log scale).
The two-site heterogeneous binding model captures systems with two independent binding sites having different affinities:
Y = \frac{B_{\max1} \cdot x}{K_1 + x} + \frac{B_{\max2} \cdot x}{K_2 + x}
The function returns optimal parameter values along with standard errors derived from the covariance matrix, enabling assessment of fit quality and parameter uncertainty. Appropriate bounds are automatically applied for parameters that have physical constraints (e.g., positive binding constants).
This example function is provided as-is without any representation of accuracy.
Excel Usage
=BINDING_MODEL(xdata, ydata, binding_model_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valuebinding_model_model(str, required): The binding_model_model value
Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.
Examples
Example 1: Demo case 1
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| single_site_competitive_binding | 0.1 | 2.75 |
| 1.3250000000000002 | 2.75 | |
| 2.5500000000000003 | 2.75 | |
| 3.7750000000000004 | 2.75 | |
| 5 | 2.75 |
Excel formula:
=BINDING_MODEL("single_site_competitive_binding", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.75;2.75;2.75;2.75;2.75})
Expected output:
| A1 | A2 | LOGx0 |
|---|---|---|
| 2.75 | 2.75 | 2.55 |
Example 2: Demo case 2
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| two_site_heterogeneous_binding | 0.1 | 0.12996017079405636 |
| 1.3250000000000002 | 1.1366893577378239 | |
| 2.5500000000000003 | 1.8755994176793738 | |
| 3.7750000000000004 | 2.423717070854732 | |
| 5 | 2.7253987644364344 |
Excel formula:
=BINDING_MODEL("two_site_heterogeneous_binding", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.12996017079405636;1.1366893577378239;1.8755994176793738;2.423717070854732;2.7253987644364344})
Expected output:
| Bmax1 | Bmax2 | k1 | k2 |
|---|---|---|---|
| 0.6185 | 4.8072 | 4.8409 | 4.8414 |
| 12611861.8231 | 12611857.2897 | 470646.7607 | 61351.118 |
Example 3: Demo case 3
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| two_site_competitive_binding | 0.1 | 2.75 |
| 1.3250000000000002 | 2.75 | |
| 2.5500000000000003 | 2.75 | |
| 3.7750000000000004 | 2.75 | |
| 5 | 2.75 |
Excel formula:
=BINDING_MODEL("two_site_competitive_binding", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.75;2.75;2.75;2.75;2.75})
Expected output:
| A1 | A2 | logx01 | logx02 | f |
|---|---|---|---|---|
| 2.75 | 2.75 | 1.325 | 3.775 | 0.5 |
Example 4: Demo case 4
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| rational_linear_linear | 0.1 | 0.9640376137175587 |
| 1.3250000000000002 | 0.7322857909033379 | |
| 2.5500000000000003 | 0.692559238727459 | |
| 3.7750000000000004 | 0.6682616941483859 | |
| 5 | 0.6666987751234869 |
Excel formula:
=BINDING_MODEL("rational_linear_linear", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.9640376137175587;0.7322857909033379;0.692559238727459;0.6682616941483859;0.6666987751234869})
Expected output:
| a | b | c |
|---|---|---|
| 2.288 | 1.04 | 1.443 |
| 0.3121 | 0.01144 | 0.2103 |
Example 5: Demo case 5
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| rational_normalized_numerator | 0.1 | 0.41997845148249724 |
| 1.3250000000000002 | 0.8191125738577802 | |
| 2.5500000000000003 | 1.0182805907520838 | |
| 3.7750000000000004 | 1.1214305702261564 | |
| 5 | 1.2246818548206133 |
Excel formula:
=BINDING_MODEL("rational_normalized_numerator", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.41997845148249724;0.8191125738577802;1.0182805907520838;1.1214305702261564;1.2246818548206133})
Expected output:
| a | b | c |
|---|---|---|
| 2.699 | 1.13 | 1.83 |
| 0.1065 | 0.1519 | 0.1971 |
Example 6: Demo case 6
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| rational_normalized_denom_linear | 0.1 | 0.43852598027486356 |
| 1.3250000000000002 | 0.8450040808154502 | |
| 2.5500000000000003 | 1.0523220604140058 | |
| 3.7750000000000004 | 1.161481225242571 | |
| 5 | 1.2706464006391858 |
Excel formula:
=BINDING_MODEL("rational_normalized_denom_linear", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.43852598027486356;0.8450040808154502;1.0523220604140058;1.161481225242571;1.2706464006391858})
Expected output:
| a | b | c |
|---|---|---|
| 2.509 | 0.9765 | 1.699 |
| 0.2731 | 0.132 | 0.05509 |
Example 7: Demo case 7
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| rational_normalized_num_linear | 0.1 | 0.3945608923626594 |
| 1.3250000000000002 | 0.4715012541865205 | |
| 2.5500000000000003 | 0.5010986982196675 | |
| 3.7750000000000004 | 0.513787024342421 | |
| 5 | 0.527072218818205 |
Excel formula:
=BINDING_MODEL("rational_normalized_num_linear", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.3945608923626594;0.4715012541865205;0.5010986982196675;0.513787024342421;0.527072218818205})
Expected output:
| a | b | c |
|---|---|---|
| 2.505 | 0.9583 | 1.766 |
| 0.2288 | 0.09172 | 0.01611 |
Example 8: Demo case 8
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| offset_reciprocal_hyperbola | 0.1 | 2.120849355572422 |
| 1.3250000000000002 | 2.012780422610218 | |
| 2.5500000000000003 | 1.9515255947447598 | |
| 3.7750000000000004 | 1.9076012828530458 | |
| 5 | 1.8871946444394427 |
Excel formula:
=BINDING_MODEL("offset_reciprocal_hyperbola", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.120849355572422;2.012780422610218;1.9515255947447598;1.9076012828530458;1.8871946444394427})
Expected output:
| a | b | c |
|---|---|---|
| 3.001 | 1.198 | 1.735 |
| 0.3336 | 0.1816 | 0.01873 |
Example 9: Demo case 9
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| rational_linear_quadratic | 0.1 | 2.3552484325266896 |
| 1.3250000000000002 | 0.3937363619329481 | |
| 2.5500000000000003 | 0.17766610281077172 | |
| 3.7750000000000004 | 0.049408836408718515 | |
| 5 | 0.07684997888239337 |
Excel formula:
=BINDING_MODEL("rational_linear_quadratic", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.3552484325266896;0.3937363619329481;0.17766610281077172;0.049408836408718515;0.07684997888239337})
Expected output:
| a | b | c | d |
|---|---|---|---|
| 3.124 | -0.04454 | 3.093 | 1.508 |
| 1.929 | 2.299 | 7.982 | 7.402 |
Example 10: Demo case 10
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| rectangular_hyperbola_saturation | 0.1 | 0.27985054044043783 |
| 1.3250000000000002 | 1.5948135745888388 | |
| 2.5500000000000003 | 2.026382357740335 | |
| 3.7750000000000004 | 2.2528257698782923 | |
| 5 | 2.30126094772332 |
Excel formula:
=BINDING_MODEL("rectangular_hyperbola_saturation", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.27985054044043783;1.5948135745888388;2.026382357740335;2.2528257698782923;2.30126094772332})
Expected output:
| a | b |
|---|---|
| 2.775 | 1.05 |
| 0.05635 | 0.08208 |
Example 11: Demo case 11
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| boltzmann_sigmoid | 0.01 | 2.75 |
| 2.0075 | 2.75 | |
| 4.005 | 2.75 | |
| 6.0024999999999995 | 2.75 | |
| 8 | 2.75 |
Excel formula:
=BINDING_MODEL("boltzmann_sigmoid", {0.01;2.0075;4.005;6.0024999999999995;8}, {2.75;2.75;2.75;2.75;2.75})
Expected output:
| A1 | A2 | x0 | dx |
|---|---|---|---|
| 2.75 | 2.75 | 4.005 | 1 |
Example 12: Demo case 12
Inputs:
| binding_model_model | xdata | ydata |
|---|---|---|
| double_boltzmann_sigmoid | 0.01 | 1.3816540661021661 |
| 2.0075 | 1.1030136074857504 | |
| 4.005 | 0.8671042453490405 | |
| 6.0024999999999995 | 0.666721135042897 | |
| 8 | 0.46429170454060964 |
Excel formula:
=BINDING_MODEL("double_boltzmann_sigmoid", {0.01;2.0075;4.005;6.0024999999999995;8}, {1.3816540661021661;1.1030136074857504;0.8671042453490405;0.666721135042897;0.46429170454060964})
Expected output:
| y0 | A | frac | x01 | x02 | k1 | k2 |
|---|---|---|---|---|---|---|
| 0.4089 | 1.039 | 0.5536 | 1.704 | 6.183 | 0.8241 | 0.9074 |
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def binding_model(xdata, ydata, binding_model_model):
"""
Fits binding_model models to data using scipy.optimize.curve_fit. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html for details.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
This example function is provided as-is without any representation of accuracy.
Args:
xdata (list[list]): The xdata value
ydata (list[list]): The ydata value
binding_model_model (str): The binding_model_model value Valid options: Single Site Competitive Binding, Two Site Heterogeneous Binding, Two Site Competitive Binding, Rational Linear Linear, Rational Normalized Numerator, Rational Normalized Denom Linear, Rational Normalized Num Linear, Offset Reciprocal Hyperbola, Rational Linear Quadratic, Rectangular Hyperbola Saturation, Boltzmann Sigmoid, Double Boltzmann Sigmoid.
Returns:
list[list]: 2D list [param_names, fitted_values, std_errors], or error string.
"""
def _validate_data(xdata, ydata):
"""Validate and convert both xdata and ydata to numpy arrays."""
for name, arg in [("xdata", xdata), ("ydata", ydata)]:
if not isinstance(arg, list) or len(arg) < 2:
raise ValueError(f"{name}: must be a 2D list with at least two rows")
vals = []
for i, row in enumerate(arg):
if not isinstance(row, list) or len(row) == 0:
raise ValueError(f"{name} row {i}: must be a non-empty list")
try:
vals.append(float(row[0]))
except Exception:
raise ValueError(f"{name} row {i}: non-numeric value")
if name == "xdata":
x_arr = np.asarray(vals, dtype=np.float64)
else:
y_arr = np.asarray(vals, dtype=np.float64)
if x_arr.shape[0] != y_arr.shape[0]:
raise ValueError("xdata and ydata must have the same number of rows")
return x_arr, y_arr
# Model definitions dictionary
models = {
'single_site_competitive_binding': {
'params': ['A1', 'A2', 'LOGx0'],
'model': lambda x, A1, A2, LOGx0: A2 + (A1 - A2) / (1.0 + np.power(10.0, x - LOGx0)),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.min(ya)), float(np.median(xa))),
},
'two_site_heterogeneous_binding': {
'params': ['Bmax1', 'Bmax2', 'k1', 'k2'],
'model': lambda x, Bmax1, Bmax2, k1, k2: Bmax1 * x / (k1 + x) + Bmax2 * x / (k2 + x),
'guess': lambda xa, ya: (float(np.max(ya) * 0.6), float(np.max(ya) * 0.4), float(np.percentile(xa, 25)), float(np.percentile(xa, 75))),
'bounds': (0.0, np.inf),
},
'two_site_competitive_binding': {
'params': ['A1', 'A2', 'logx01', 'logx02', 'f'],
'model': lambda x, A1, A2, logx01, logx02, f: A2 + (A1 - A2) * (f / (1.0 + np.power(10.0, x - logx01)) + (1.0 - f) / (1.0 + np.power(10.0, x - logx02))),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.min(ya)), float(np.percentile(xa, 25)), float(np.percentile(xa, 75)), 0.5),
'bounds': ([-np.inf, -np.inf, -np.inf, -np.inf, 0.0], [np.inf, np.inf, np.inf, np.inf, 1.0]),
},
'rational_linear_linear': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: (b + c * x) / (1.0 + a * x),
'guess': lambda xa, ya: (float(min(ya)), 1.0, 0.1),
},
'rational_normalized_numerator': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: (1.0 + c * x) / (a + b * x),
'guess': lambda xa, ya: (1.0, 1.0, 0.1),
},
'rational_normalized_denom_linear': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: (b + c * x) / (a + x),
'guess': lambda xa, ya: (1.0, float(np.min(ya)), 0.1),
},
'rational_normalized_num_linear': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: (b + x) / (a + c * x),
'guess': lambda xa, ya: (1.0, float(np.min(ya)), 0.1),
},
'offset_reciprocal_hyperbola': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: c + b / (x + a),
'guess': lambda xa, ya: (1.0, 1.0, float(np.min(ya))),
},
'rational_linear_quadratic': {
'params': ['a', 'b', 'c', 'd'],
'model': lambda x, a, b, c, d: (a + b * x) / (1.0 + c * x + d * np.square(x)),
'guess': lambda xa, ya: (float(np.min(ya)), 0.1, 0.0, 0.0),
},
'rectangular_hyperbola_saturation': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * b * x / (1.0 + b * x),
'guess': lambda xa, ya: (float(np.max(ya)), 0.1),
'bounds': ([-np.inf, 0.0], np.inf),
},
'boltzmann_sigmoid': {
'params': ['A1', 'A2', 'x0', 'dx'],
'model': lambda x, A1, A2, x0, dx: (A1 - A2) / (1.0 + np.exp((x - x0) / dx)) + A2,
'guess': lambda xa, ya: (float(np.max(ya)), float(np.min(ya)), float(np.median(xa)), 1.0),
},
'double_boltzmann_sigmoid': {
'params': ['y0', 'A', 'frac', 'x01', 'x02', 'k1', 'k2'],
'model': lambda x, y0, A, frac, x01, x02, k1, k2: y0 + A * (frac / (1.0 + np.exp((x - x01) / k1)) + (1.0 - frac) / (1.0 + np.exp((x - x02) / k2))),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 0.5, float(np.percentile(xa, 25)), float(np.percentile(xa, 75)), 1.0, 1.0),
'bounds': ([-np.inf, -np.inf, 0.0, -np.inf, -np.inf, 0.0, 0.0], [np.inf, np.inf, 1.0, np.inf, np.inf, np.inf, np.inf]),
}
}
# Validate model parameter
if binding_model_model not in models:
return f"Invalid model: {str(binding_model_model)}. Valid models are: {', '.join(models.keys())}"
model_info = models[binding_model_model]
# Validate and convert input data
try:
x_arr, y_arr = _validate_data(xdata, ydata)
except ValueError as e:
return f"Invalid input: {e}"
# Perform curve fitting
try:
p0 = model_info['guess'](x_arr, y_arr)
bounds = model_info.get('bounds', (-np.inf, np.inf))
if bounds == (-np.inf, np.inf):
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)
else:
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)
fitted_vals = [float(v) for v in popt]
for v in fitted_vals:
if math.isnan(v) or math.isinf(v):
return "Fitting produced invalid numeric values (NaN or inf)."
except ValueError as e:
return f"Initial guess error: {e}"
except Exception as e:
return f"curve_fit error: {e}"
# Calculate standard errors
std_errors = None
try:
if pcov is not None and np.isfinite(pcov).all():
std_errors = [float(v) for v in np.sqrt(np.diag(pcov))]
except Exception:
pass
return [model_info['params'], fitted_vals, std_errors] if std_errors else [model_info['params'], fitted_vals]