Skip to main content

ODR Bootstrap

Tests PyPI Python 3.11+ License: MIT

ODR Bootstrap fits linear calibration curves with rigorous uncertainty in both x and y — no ordinary least-squares assumptions needed.

When measurement errors exist in both the independent variable (x, e.g. concentration) and the dependent variable (y, e.g. signal intensity), ordinary least squares gives biased results. Orthogonal Distance Regression (ODR) handles errors in both directions, and bootstrap resampling turns those fits into honest confidence intervals without relying on analytical approximations.

In situations when potential outliers cannot be easily excluded—for example, when there is no independent evidence that they are bad measurements—it can be useful to retain them in the regression. Comparing results with and without these points helps quantify their influence on the fitted line and its uncertainty.

The package is used for SIMS (Secondary Ion Mass Spectrometry) calibration but applies to any field where both variables carry measurement uncertainty.

Installation

pip install odr-bootstrap
uv add odr-bootstrap

From source

git clone https://github.com/whtowbin/odr_bootstrap_package.git
cd odr_bootstrap_package
uv sync

Documentation

Full documentation at Read the Docs — including a Tutorial and API Reference.

Quick Start

import numpy as np
import matplotlib.pyplot as plt
from odr_bootstrap import odr_bootstrap, fit_defaults, plot_regression

# Your calibration data
x_standards = np.array([0.1, 0.5, 1.0, 2.0, 5.0])      # known concentrations
y_intensity  = np.array([45, 200, 350, 700, 1450])       # measured signal
x_uncertainty = np.array([0.01, 0.05, 0.1, 0.2, 0.5])   # uncertainty in x
y_uncertainty = np.array([5, 20, 35, 60, 120])           # uncertainty in y

# Derive starting parameters automatically
defaults = fit_defaults(x_standards, y_intensity)

# Fit with 2000 bootstrap resamples
confidence_data, best_fit_params, points, all_params, _ = odr_bootstrap(
    x=x_standards,
    y=y_intensity,
    x_err=x_uncertainty,
    y_err=y_uncertainty,
    resample_draws=2000,
    initial_guess=defaults["initial_guess"],
    line_max=defaults["line_max"],
    line_interval=defaults["line_interval"],
)

slope, intercept = best_fit_params
print(f"y = {slope:.2f}x + {intercept:.2f}")

# Plot the fit with a 95% confidence band
fig, ax = plt.subplots(figsize=(8, 5))
plot_regression(confidence_data, datapoints=points, ax=ax,
                ecolor="lightblue", line_color="darkblue", linewidth=2)
ax.set_xlabel("Concentration (ppm)")
ax.set_ylabel("Signal Intensity (counts)")
plt.tight_layout()
plt.savefig("calibration_curve.png", dpi=150)
plt.show()

Visualising parameter uncertainty

from odr_bootstrap import gaussian_aggregate, plot_density

all_params_array = np.asarray(all_params, dtype=float)
slopes     = all_params_array[:, 0]
intercepts = all_params_array[:, 1]

slope_dist,     slope_stats     = gaussian_aggregate(slopes,     np.full_like(slopes,     slopes.std()))
intercept_dist, intercept_stats = gaussian_aggregate(intercepts, np.full_like(intercepts, intercepts.std()))

fig, axes = plt.subplots(1, 2, figsize=(12, 5))
plot_density(slope_dist,     slope_stats,     ax=axes[0])
plot_density(intercept_dist, intercept_stats, ax=axes[1])
axes[0].set_title("Slope Distribution")
axes[1].set_title("Intercept Distribution")
plt.tight_layout()
plt.savefig("calibration_estimates.png", dpi=150)
plt.show()

Example Output

Running python examples/example.py produces four figures. The first two show the clean calibration fit; the second two repeat the analysis with synthetic outliers so you can see how they affect the result.

Clean calibration fit

Calibration curve with 68% and 95% bootstrap confidence intervals

The shaded bands are the 68% (inner) and 95% (outer) bootstrap confidence intervals. Narrower bands indicate a more precisely constrained calibration.

Bootstrap parameter distributions

Bootstrap slope and intercept distributions

Each histogram shows how the fitted slope and intercept vary across bootstrap resamples, giving you a direct view of parameter uncertainty.

Fit with synthetic outliers

Calibration fit with synthetic outliers and dual confidence bands

Including potential outliers in the regression widens the confidence bands and may shift the best-fit line. This illustrates why points that cannot be excluded objectively should be retained: the bootstrap distributions quantify their effect on the fitted parameters and prediction uncertainty.

Outlier-affected parameter distributions

Outlier-affected slope and intercept distributions

The potential outliers broaden and shift both distributions. Comparing these to the clean-data distributions makes their influence easy to quantify.

Module Functions

Defaults helper

  • fit_defaults(x, y, fit_intercept=True)
    Computes sensible starting parameters from your data using a least-squares pre-fit.
    Returns {"initial_guess": [...], "line_max": float, "line_interval": float}.

Core fitting

  • fit_odr_linear(x, y, x_err, y_err, fit_intercept=True, initial_guess=None)
    Single ODR fit. Returns (params, param_errors).

  • bootstrap_odr_fit(x, y, x_err, y_err, resample_draws, ...)
    Fits the ODR model to N random resamples of your data.
    Returns (all_fit_params, resampled_data).

Confidence intervals

  • evaluate_confidence(fit_params, line_max, line_interval, confidence_level=0.95)
    Computes confidence bounds from bootstrap parameter distributions.
    Returns a DataFrame indexed by x-values.

  • odr_bootstrap(...)
    Convenience wrapper: runs bootstrap_odr_fit + evaluate_confidence in one call.
    line_max, line_interval, and initial_guess are derived automatically when omitted.

Statistics

  • gaussian_aggregate(concentrations, errors)
    Aggregates bootstrap distributions into a smooth KDE estimate.
    Returns (distribution_dict, statistics_dict).

Plotting

  • plot_regression(confidence_df, datapoints=None, ax=None, ...)
    Plots the best-fit line with shaded confidence band(s).

  • plot_density(data, bounds, ax=None, ...)
    Plots a probability density curve from gaussian_aggregate output.

  • plot_calibration_estimates(fit_params, fit_error, title=...)
    Side-by-side slope and intercept distribution plots.

Requirements

  • Python >= 3.11
  • numpy >= 2.2.4
  • scipy >= 1.15.2
  • pandas >= 2.2.3
  • matplotlib >= 3.10.1

License

MIT — see LICENSE.

Citation

@software{towbin2025odr,
  title  = {ODR Bootstrap: Orthogonal Distance Regression with Bootstrap Resampling},
  author = {Towbin, Henry},
  year   = {2025},
  url    = {https://github.com/whtowbin/odr_bootstrap_package}
}

Metadata

Release files for odr-bootstrap 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odr-bootstrap 2.0.0
File Size Uploaded
odr_bootstrap-2.0.0.tar.gz 1.9 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for odr-bootstrap 2.0.0
File Interpreter ABI Platform
odr_bootstrap-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.9 MB

Release files / odr_bootstrap-2.0.0.tar.gz

Download URL odr_bootstrap-2.0.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
e6f2f7ae29e2c8ff597496cb7168f976060f29a13d608f59c8e128db04c2a023
BLAKE2b-256 checksum
How to use checksums
e3ccf2eda1f1d5f0feb117fc97e56953f2bdd2bb12c0672e4896c3dfbd6da67d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / odr_bootstrap-2.0.0-py3-none-any.whl

Download URL odr_bootstrap-2.0.0-py3-none-any.whl
Size 13.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6cc4aadcf80799caf0ac1569e2231f7ef16be699ec8b1c64b1a295dc2711af46
BLAKE2b-256 checksum
How to use checksums
388f5bc8320978ab7e959c319d0046b83dcefecb848e105e415bab1b395519f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

2.1.0

2 release files

This release

2.0.0 This release

2 release files

1.3.0

2 release files

1.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page