Skip to main content

BayesCurveFit: Enhancing Biological Data Analysis with Robust Curve Fitting and FDR Detection

Project description

BayesCurveFit: Enhancing Curve Fitting in Drug Discovery Data Using Bayesian Inference

PyPI version License Python Versions Build Status Publish Status

Overview

BayesCurveFit is a Python package designed to apply Bayesian inference for curve fitting, especially tailored for undersampled and outlier-contaminated data. It supports advanced model fitting and uncertainty estimation for biological data, such as dose-response curves in drug discovery.

Manuscript and Datasets

A preprint can be found at: https://www.biorxiv.org/content/10.1101/2025.02.23.639769v1

Data and supplementary data used in the manuscript can be found at https://zenodo.org/records/14948538

The BayesCurveFit pipeline for processing Kinobeads data is available and can be found in the examples/pipeline directory of the repository.

Technical Details

  • Bayesian Model Averaging: Uses Gaussian Mixture Models with proper law of total variance for covariance estimation
  • MCMC Sampling: Emcee-based sampling with convergence diagnostics (R-hat, effective sample size)
  • Parameter Uncertainty: Full covariance matrix estimation with proper correlation handling
  • Model Comparison: Posterior Error Probability (PEP) for model selection

Installation Guide

Prerequisites

  • Python 3.11 or higher
  • uv package manager (recommended)

Install from PyPI (Recommended)

To install the latest stable release from PyPI:

pip install bayescurvefit

Or with uv (faster):

uv pip install bayescurvefit

Install from Source

To install the latest development version:

git clone https://github.com/ndu-bioinfo/BayesCurveFit.git
cd BayesCurveFit
uv sync

Development Setup

For development with all dependencies:

git clone https://github.com/ndu-bioinfo/BayesCurveFit.git
cd BayesCurveFit
uv sync --dev
make test  # Run tests

Quick Start

Basic Usage

In this example, we use the log-logistic 4-parameter model to fit dose-response data.

import numpy as np
from bayescurvefit.execution import BayesFitModel

# Define the curve fitting function
def log_logistic_4p(x: np.ndarray, pec50: float, slope: float, front: float, back: float) -> np.ndarray:
    with np.errstate(over="ignore", under="ignore", invalid="ignore"):
        y = (front - back) / (1 + 10 ** (slope * (x + pec50))) + back
        return y

# Input data
x_data = np.array([-9.0, -8.3, -7.6, -6.9, -6.1, -5.4, -4.7, -4.0])
y_data = np.array([1.12, 0.74, 1.03, 1.08, 0.76, 0.61, 0.39, 0.38])
params_range = [(5, 8), (0.01, 10), (0.28, 1.22), (0.28, 1.22)]  # Parameter bounds
param_names = ["pec50", "slope", "front", "back"]

# Run Bayesian curve fitting
run = BayesFitModel(
    x_data=x_data,
    y_data=y_data,
    fit_function=log_logistic_4p,
    params_range=params_range,
    param_names=param_names,
)

Retrieve Results

Get the fitting results with parameter estimates and uncertainties:

# Get results
results = run.get_result()
print(results)

Output:

fit_pec50             5.855744
fit_slope             1.088774
fit_front             1.063355
fit_back              0.376912
std_pec50             0.184996
std_slope             0.399462
std_front             0.061905
std_back              0.050635
est_std               0.089587
null_mean             0.762365
rmse                  0.122646
pep                   0.062768
convergence_warning   False

Key Results:

  • fit_*: Best-fit parameter estimates
  • std_*: Parameter uncertainties (standard deviations)
  • rmse: Root mean square error
  • pep: Posterior Error Probability (model comparison)
  • convergence_warning: MCMC convergence status

Visualization

Plot the fitted curve and parameter distributions:

import matplotlib.pyplot as plt

# Plot fitted curve
fig, ax = plt.subplots()
run.analysis.plot_fitted_curve(ax=ax)
ax.set_xlabel("Concentration [M]")
ax.set_ylabel("Relative Site Response")
plt.show()
Text(0, 0.5, 'Relative Site Response')

png

Parameter Analysis

Visualize parameter correlations and sampling diagnostics:

# Parameter pairwise comparisons
run.analysis.plot_pairwise_comparison(figsize=(10, 10))

# Parameter distributions
run.analysis.plot_param_dist()

png

png

Advanced Usage

Other Curve Types

BayesCurveFit supports various curve types. Here's an example with the Michaelis-Menten model:

def michaelis_menten_func(x, vamx, km):
    return (vamx * x) / (km + x)


x_data = np.array(
    [
        0.5,
        2.0,
        4.0,
        5.0,
        6.0,
        8.0,
        10.0,
    ]
)
y_data = np.array(
    [
        0.3,
        0.6,
        0.8,
        np.nan,
        1.2,
        0.85,
        0.9,
    ]
)

params_range = [(0.01, 5), (0, 5)]
param_names = ["vmax", "km"]

run_mm = BayesFitModel(
    x_data=x_data,
    y_data=y_data,
    fit_function=michaelis_menten_func,
    params_range=params_range,
    param_names=param_names,
)
run_mm.get_result()
fit_vmax               1.075649
fit_km                 1.599924
std_vmax               0.125697
std_km                 0.662758
est_std                0.085374
null_mean              0.77585
rmse                   0.146565
pep                    0.097757
convergence_warning    False
f,ax = plt.subplots()
run_mm.analysis.plot_fitted_curve(ax=ax)
ax.set_xlabel("Substrate Concentration [S] (M)")
ax.set_ylabel("Relative Response (V/V_max)")

png

Parallel Processing

BayesCurveFit supports parallel execution for batch processing and pipeline integration:

from joblib import Parallel, delayed
import pandas as pd
from bayescurvefit.simulation import Simulation
from bayescurvefit.distribution import GaussianMixturePDF
def run_bayescurvefit(df_input, x_data, output_csv, params_range, param_names = None):
    def process_sample(sample):
        y_data = df_input.loc[sample].values
        bayesfit_run = BayesFitModel(
            x_data=x_data,
            y_data=y_data,
            fit_function=michaelis_menten_func,
            params_range=params_range,
            param_names=param_names,
            run_mcmc=True,
        )
        return sample, bayesfit_run.get_result()

    samples = df_input.index.tolist()
    results = Parallel(n_jobs=-1)(
        delayed(process_sample)(sample) for sample in samples
    )
    dict_result = {sample: res for sample, res in results}
    df_output = pd.DataFrame(dict_result).T
    df_output.to_csv(output_csv)
    return df_output
# Generate simulation dataset
sample_size = 6 # Number of observations
params_bounds = [(1, 1.2), [0.5, 1]] # Bounds for fitting parameters in simulation data
pdf_params = [0.1, 0.3, 0.1] # PDF parameters [scale1, scale2, mix_frac of second Gaussian]

x_sample = np.linspace(0.5, 10, sample_size)
sim = Simulation(fit_function=michaelis_menten_func,
                X=x_sample,
                pdf_function=GaussianMixturePDF(pdf_params=pdf_params),
                sim_params_bounds=params_bounds,
                n_samples=10,
                n_replicates=1)
params_range = [(0.01, 5), (0, 5)] #The range of fitting parameters we estimated
param_names = ["vmax", "km"] #optional
df_output = run_bayescurvefit(df_input = sim.df_sim_data_w_error, x_data=sim.X, output_csv="test_output.csv", params_range= params_range, param_names = param_names)
df_output
fit_vmax fit_km std_vmax std_km est_std null_mean rmse pep convergence_warning
m001 1.05 0.53 0.07 0.27 0.09 0.88 0.09 0.08 False
m002 1.07 0.45 0.05 0.13 0.06 0.92 0.07 0.02 False
m003 0.99 0.42 0.03 0.08 0.04 0.85 0.04 0.00 False
m004 1.20 0.83 0.12 0.50 0.11 0.95 0.12 0.15 False
m005 1.11 0.86 0.08 0.30 0.09 0.86 0.09 0.02 False
m006 1.20 1.79 0.19 1.19 0.14 0.90 0.26 0.58 False
m007 1.05 1.49 0.26 1.16 0.20 0.67 0.33 0.92 False
m008 1.59 2.62 0.25 1.14 0.13 0.96 0.17 0.17 False
m009 1.23 1.57 0.09 0.46 0.06 0.85 0.06 0.00 False
m010 1.11 0.41 0.04 0.10 0.05 0.96 0.05 0.00 False

Development

Running Tests

# Install development dependencies
uv sync --dev

# Run all tests
make test

# Run specific test file
uv run pytest src/tests/test_utils.py -v

Building the Package

# Build package
make build

# Install locally
make install

Version Management

Single source of truth: src/bayescurvefit/__init__.py

To release a new version:

  1. Run the script: python scripts/bump_version.py patch (or minor/major)
    • Updates __init__.py
    • Commits the change
    • Creates and pushes git tag (e.g., v0.6.2)
  2. Create GitHub release: Go to GitHub Releases, create release with the existing tag
  3. Done! GitHub Actions automatically publishes to PyPI

Available commands:

python scripts/bump_version.py patch   # 0.6.1 → 0.6.2
python scripts/bump_version.py minor   # 0.6.1 → 0.7.0
python scripts/bump_version.py major   # 0.6.1 → 1.0.0

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature-name
  3. Make your changes and add tests
  4. Run tests: make test
  5. Commit your changes: git commit -m "Add feature"
  6. Push to the branch: git push origin feature-name
  7. Submit a pull request

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Citation

If you use BayesCurveFit in your research, please cite:

@article{du2025bayescurvefit,
  title={BayesCurveFit: Enhancing Curve Fitting in Drug Discovery Data Using Bayesian Inference},
  author={Du, Niu and others},
  journal={bioRxiv},
  year={2025},
  doi={10.1101/2025.02.23.639769}
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bayescurvefit-0.6.2.tar.gz (261.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bayescurvefit-0.6.2-py3-none-any.whl (29.0 kB view details)

Uploaded Python 3

File details

Details for the file bayescurvefit-0.6.2.tar.gz.

File metadata

  • Download URL: bayescurvefit-0.6.2.tar.gz
  • Upload date:
  • Size: 261.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for bayescurvefit-0.6.2.tar.gz
Algorithm Hash digest
SHA256 3c66a54d8cfc68f7c98b346b9613f5958ac345ca2af72b2d4a7f2e8c95f68f6b
MD5 e804ee596a731d69763612a193587271
BLAKE2b-256 c5d253847dfea067ef57b560e9bb69a6316bf80d49ec9ee32f189484754ec7e6

See more details on using hashes here.

File details

Details for the file bayescurvefit-0.6.2-py3-none-any.whl.

File metadata

  • Download URL: bayescurvefit-0.6.2-py3-none-any.whl
  • Upload date:
  • Size: 29.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for bayescurvefit-0.6.2-py3-none-any.whl
Algorithm Hash digest
SHA256 243351965904195a1dde13caa3754895253868746b27701cc50397d07b833d2b
MD5 6d8b4fb3e765aceb3eb7e90386af67fe
BLAKE2b-256 bbe7c313f15e2200c7f040c1a394c979554d61187d95dc78d61b723eabd49aa9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page