# Michaelis–Menten kinetics: fitting Km and Vmax by nonlinear regression and comparing Km between enzymes

> Estimate Km and Vmax by nonlinear regression of rate on substrate, not from a Lineweaver–Burk line, and compare Km between enzymes or conditions with one estimate per independent experiment. Python and R code.

Canonical: https://www.licklider.ai/guides/enzyme-kinetics-km-vmax/
Section: Guides › Correlation, regression and survival
Author: Licklider · Published 2026-11-12 · Last reviewed 2026-11-12 (UTC)

> **Quick answer.** Fit the **Michaelis–Menten equation, rate = Vmax × [S] / (Km + [S]), directly to the measured rates by nonlinear regression**, and report Km and Vmax with 95% confidence intervals. Use a Lineweaver–Burk (double-reciprocal) plot only to display data: taking 1/rate magnifies the noise at low substrate concentrations, and in our simulation of single experiments with the worked example's concentrations (true Km 10 µM), the Lineweaver–Burk Km was within 20% of the true value in only 54.1% of experiments, against 98.5% for nonlinear regression. To **compare Km** between a mutant and wild type, or with and without an inhibitor or activator, fit each enzyme **in each independent experiment**, and compare the per-experiment estimates, with n = the number of independent experiments (not wells): a paired t-test on log Km when the enzymes were assayed side by side in each experiment, otherwise Welch's t-test on log Km. A global fit that shares one Km across experiments ignores how Km varies from one preparation to the next, and in our simulation gave a false positive in 20.0% of studies.

**Terms used below.** *[S]*: substrate concentration. *Km (Michaelis constant)*: the substrate concentration giving half the maximal rate. *Vmax*: the maximal rate at saturating substrate. *kcat*: Vmax divided by the enzyme concentration (turnovers per second); *kcat/Km* measures catalytic efficiency. *Welch's t-test*: compares two groups without assuming equal spread. *Initial rate*: the rate measured before substrate depletion or product build-up, from the early, straight part of the *progress curve* (product against time). *Residual*: the difference between a measured rate and the fitted curve. *Geometric mean*: the back-transformed mean of the logarithms, the usual average for ratios and constants such as Km. *Nonlinear regression*: fitting a curve by least squares (making the squared differences between data and curve as small as possible) without transforming the data. *Lineweaver–Burk plot*: 1/rate plotted against 1/[S], with a straight line whose intercept gives 1/Vmax and slope Km/Vmax. *Independent experiment*: a separate enzyme preparation assayed on its own occasion. *Technical replicate*: a repeated well from the same preparation and run. *Global fit*: one model fitted to several data sets at once with some parameters shared. *Extra-sum-of-squares F-test*: compares the fit of a model with a shared parameter against one with separate parameters. *95% confidence interval (CI)*: the range of values compatible with the data. *Power*: the chance of detecting an effect that is really there. *False positive*: a "significant" result when there is no real effect.

## Which analysis for your question

| Question | Analysis | Report |
| --- | --- | --- |
| What are Km and Vmax? | Nonlinear regression of rate on [S] in each experiment | Km and Vmax with 95% CIs; the geometric mean across experiments with n |
| Does Km differ between enzymes or conditions? | One Km per enzyme per independent experiment; paired t-test on log Km if run side by side, otherwise Welch's t-test on log Km (or a nonlinear mixed model with a random Km per experiment) | Km ratio with 95% CI, n and p |
| A single experiment with two enzymes | The extra-sum-of-squares F-test describes that experiment only; repeat the experiment before claiming a difference | Both Km values with CIs, labelled as one experiment |
| Does Vmax (or kcat) differ? | The same comparison as for Km, on the log scale, using Vmax normalised to enzyme amount | Ratio with 95% CI |
| Does catalytic efficiency differ? | kcat/Km per experiment, compared on the log scale | Ratio with 95% CI |
| Several substrates, inhibitors or binding sites | Fit the specific model (for example competitive inhibition) by nonlinear regression | Parameter estimates with CIs |

GraphPad's curve-fitting guide explains the problem with transforms: "the transformation distorts the experimental error" ([GraphPad, transforms](https://www.graphpad.com/guides/prism/latest/curve-fitting/avoidscatchard_lineweaver_burkeandsimilartransforms.htm)), and "If you create a Lineweaver-Burk plot, use it only to display your data. Don't use the slope and intercept of a linear regression line to determine values for Vmax and Km" ([GraphPad, Michaelis–Menten](https://www.graphpad.com/guides/prism/latest/curve-fitting/reg_michaelis_menten_enzyme.htm)). Ritchie and Prvan report that simulations showed "the errors of Km and Vmax determined using double reciprocal and Hofstee plots were often very large and heavily biased" ([Ritchie & Prvan 1996](https://doi.org/10.1016/S0307-4412(96)00089-1)), and Aledo states that "those methods that do not transform the original variables are preferable" ([Aledo 2022](https://doi.org/10.1186/s12859-022-04729-4)). On replicates, the *British Journal of Pharmacology* guidance is that "one sample run five times is n = 1, not n = 5", and the journal has "set 5 as the minimum 'n' required for datasets subjected to statistical analysis" ([Curtis et al. 2018](https://doi.org/10.1111/bph.14153)).

## Worked example: a mutant with a higher Km

Constructed data: initial rates at 8 substrate concentrations (2 to 50 µM, 0.2 to 5 times the wild-type Km), in duplicate wells, from **5 independent experiments**; in each, fresh preparations of wild type (WT) and mutant were assayed side by side. By design, the true Km is 10 µM for WT and 15 µM for the mutant (a ratio of 1.5). Km varies between days and preparations (each a standard deviation of 0.10 on the log scale), Vmax between preparations (0.15 on the log scale), and wells scatter with a constant standard deviation of 3 rate units. The random seed was the first one tried and was not chosen for its result. Stored code and data: [kinetics.csv](/guides/enzyme-kinetics-km-vmax/kinetics.csv), [model.py](/guides/enzyme-kinetics-km-vmax/model.py), [make_data.py](/guides/enzyme-kinetics-km-vmax/make_data.py), [analysis.py](/guides/enzyme-kinetics-km-vmax/analysis.py), [analysis.R](/guides/enzyme-kinetics-km-vmax/analysis.R) and [the analysis output](/guides/enzyme-kinetics-km-vmax/analysis_output.txt); analysis.py and analysis.R print the same output.

Rates are in nmol/min/mg. In experiment 1, nonlinear regression gave WT Km 10.77 µM (95% CI 9.62 to 11.93) and Vmax 103.1 (95% CI 98.6 to 107.5); the Lineweaver–Burk line through the same wells gave Km 13.76 µM and Vmax 118.0.

| Experiment | WT Km, nonlinear | WT Km, Lineweaver–Burk | Mutant Km, nonlinear | Mutant Km, Lineweaver–Burk |
| --- | --- | --- | --- | --- |
| 1 | 10.77 | 13.76 | 16.14 | 14.49 |
| 2 | 8.99 | 12.59 | 15.05 | 24.26 |
| 3 | 12.12 | 18.27 | 14.88 | **91.48** |
| 4 | 9.78 | 9.59 | 15.90 | 15.30 |
| 5 | 10.67 | 12.43 | 19.08 | 16.64 |

| Comparison of Km, mutant vs WT | Result |
| --- | --- |
| **One Km ratio per experiment (paired t-test on log Km, n = 5)** | **1.55 (95% CI 1.29 to 1.86), p = 0.003** |
| Global fit of all 160 wells (a Vmax for each preparation, one Km per enzyme shared across experiments; not a valid comparison, see below) | WT 10.3 µM (95% CI 9.6 to 11.0), mutant 15.9 µM (14.7 to 17.2); F(1, 148) = 73.88, p = 1.1 × 10⁻¹⁴ |

Nonlinear regression gave a ratio close to the true 1.5; in experiment 3, one low reading at the lowest substrate concentration pushed the Lineweaver–Burk Km to 91 µM, an extreme case of the scatter shown below. Both comparisons found the higher Km, but the global fit's intervals and p-value rest on 148 residual degrees of freedom from wells, whereas the evidence about preparation-to-preparation variation in Km comes from only 5 experiments. In 10,000 simulated single experiments, and 10,000 simulated five-experiment studies, of this design ([simulation.py](/guides/enzyme-kinetics-km-vmax/simulation.py), [the simulation output](/guides/enzyme-kinetics-km-vmax/simulation_output.txt)):

| Km from one experiment (true Km 10 µM) | Nonlinear regression | Lineweaver–Burk |
| --- | --- | --- |
| Median estimate | 10.00 µM | 10.34 µM |
| Middle 95% of estimates | 8.49 to 11.71 µM | 6.61 to 22.57 µM |
| Within 20% of the true value | **98.5%** | **54.1%** |

| Comparing Km, 5 experiments | One Km ratio per experiment, p < 0.05 | Global fit sharing Km across experiments, p < 0.05 |
| --- | --- | --- |
| Same true Km in both enzymes | 4.8% (false positives) | **20.0%** (false positives) |
| Mutant Km 1.5 times as high (15 vs 10 µM) | 93.6% (power) | 100.0% |

The global fit's extra power is bought with a false-positive rate four times the intended 5%; with one ratio per experiment, the false-positive rate stayed near 5% and five experiments detected a 1.5-fold difference 94% of the time. That power depends on how much Km varies between preparations: with more variable preparations, plan more experiments.

The code expects one row per well with columns experiment, enzyme, substrate_uM and rate (other columns are ignored); rename your columns and enzyme labels to match, and run the code in the folder that holds the CSV file.

```python
# Condensed: analysis.py also gives each fit's confidence intervals, the Lineweaver-Burk estimates and the global fit
import numpy as np, pandas as pd
from scipy import stats
from scipy.optimize import curve_fit

d = pd.read_csv("kinetics.csv")                      # one row per well: experiment, enzyme, substrate_uM, well, rate
mm = lambda s, vmax, km: vmax * s / (km + s)
def km(x): return curve_fit(mm, x.substrate_uM, x.rate, p0=[x.rate.max(), x.substrate_uM.median()])[0][1]
k = d.groupby(["experiment", "enzyme"]).apply(km).unstack()   # one Km per enzyme per experiment
lr = np.log(k["mutant"] / k["WT"])
print(np.exp(np.log(k).mean()))                      # geometric mean Km per enzyme
t = stats.ttest_1samp(lr, 0)                         # paired t-test = one-sample t-test on the log ratios
print(k.round(2), np.exp(lr.mean()), np.exp(t.confidence_interval()), t.pvalue)   # Km ratio, 95% CI, p
# not side by side: stats.ttest_ind(np.log(k["mutant"]), np.log(k["WT"]), equal_var=False)
# if scatter grows with the rate, weight each fit by 1/rate^2: add sigma=x.rate to curve_fit
```

```r
d <- read.csv("kinetics.csv")
km <- function(x) coef(nls(rate ~ vmax * substrate_uM / (km + substrate_uM), data = x,
                           start = list(vmax = max(x$rate), km = median(x$substrate_uM))))[["km"]]
k <- sapply(split(d, d$experiment), function(e) c(WT = km(subset(e, enzyme == "WT")), mutant = km(subset(e, enzyme == "mutant"))))
lr <- log(k["mutant", ] / k["WT", ])
exp(rowMeans(log(k)))                                 # geometric mean Km per enzyme
tt <- t.test(lr)                                      # paired t-test = one-sample t-test on the log ratios
round(k, 2); exp(mean(lr)); exp(tt$conf.int); tt$p.value   # Km ratio, 95% CI, p (analysis.R also prints a fit's CIs)
# not side by side: t.test(log(k["mutant", ]), log(k["WT", ]))
# if scatter grows with the rate, weight each fit by 1/rate^2: add weights = 1 / rate^2 to nls
```

## Before you analyse

1. **Measure initial rates**, from the linear part of each progress curve, before substrate depletion or product inhibition bends it: fit a straight line to the early time points and keep the window in which the slope is stable. Subtract the no-enzyme or zero-substrate blank rather than fitting it as a data point.
2. **Span substrate concentrations below and above Km**, roughly 0.2 to 5 times the expected Km, and higher if Vmax matters (at 5 × Km the rate is still only 83% of Vmax); Murphy and colleagues show that efficient designs depend on "some prior knowledge of K(M)" ([Murphy et al. 2003](https://doi.org/10.1016/S0165-022X(02)00183-5)). A Km above the highest concentration tested is poorly determined. If Km is unknown, run a pilot across a wide range (for example in 3-fold steps) first.
3. **Count independent experiments, not wells.** Duplicate or triplicate wells improve each fit; the n for comparing enzymes is the number of independent preparations or occasions ([what counts as n](/guides/what-counts-as-n/)). With fewer than 5, report the ratio and interval as preliminary ([a t-test with n = 3](/guides/t-test-with-n-3/)).
4. **Look at every fit and its residuals.** Plot each experiment's rates with the fitted curve, and decide in advance what counts as a failed fit (for example a Km above the highest concentration tested, or a fit that does not converge from starting values of Vmax = highest rate and Km = median [S]). Leave failed experiments out of the comparison by that rule, and report how many. The unweighted fit assumes similar scatter at every rate; if the scatter grows with the rate, weight the fit (for example by 1/rate²) or fit on the log scale.
5. **Run the enzymes you compare side by side** in each experiment, so the comparison can be paired ([paired vs unpaired](/guides/paired-vs-unpaired-t-test/)).
6. **Compare Km on the log scale** and report the ratio, as for IC50 values ([comparing IC50 values](/guides/compare-ic50-dose-response-curves/)).

## Check what an AI assistant ran

1. **The assistant calculated Km from a Lineweaver–Burk regression.** Ask for nonlinear regression on the untransformed rates.
2. **The assistant pooled wells from all experiments, or shared one Km across experiments, and reported very narrow intervals or a tiny p-value.** Set that p-value aside, and ask for one estimate per experiment and a comparison across experiments.
3. **The assistant reported Km without a confidence interval or the number of experiments.** Ask for both.
4. **The assistant compared Km values with overlapping or non-overlapping error bars.** Ask for a test on the ratio.

## Report it

> Initial rates were fitted to the Michaelis–Menten equation by nonlinear least squares, separately for each enzyme in each of [5] independent experiments ([8] substrate concentrations, [2]–[50] µM, duplicate wells). Km values were compared as the ratio mutant/WT by a paired t-test on log Km, pairing enzymes assayed in the same experiment (R [4.3.3]).

> The mutant's Km was [1.55] times that of the wild type (95% CI [1.29] to [1.86]; paired t-test, n = [5] experiments, p = [0.003]); geometric mean Km was [10.4] µM for WT and [16.1] µM for the mutant.

Replace the bracketed values with your own.

## References

- GraphPad Software. Fitting enzyme kinetics data with nonlinear regression (Michaelis–Menten). *GraphPad Prism Curve Fitting Guide* (checked 11 November 2026). [graphpad.com](https://www.graphpad.com/guides/prism/latest/curve-fitting/reg_michaelis_menten_enzyme.htm)
- GraphPad Software. Avoid Scatchard, Lineweaver–Burk and similar transforms. *GraphPad Prism Curve Fitting Guide* (checked 11 November 2026). [graphpad.com](https://www.graphpad.com/guides/prism/latest/curve-fitting/avoidscatchard_lineweaver_burkeandsimilartransforms.htm)
- Ritchie RJ, Prvan T (1996). Current statistical methods for estimating the Km and Vmax of Michaelis–Menten kinetics. *Biochemical Education* 24:196–206. [doi:10.1016/S0307-4412(96)00089-1](https://doi.org/10.1016/S0307-4412(96)00089-1)
- Aledo JC (2022). renz: an R package for the analysis of enzyme kinetic data. *BMC Bioinformatics* 23:182. [doi:10.1186/s12859-022-04729-4](https://doi.org/10.1186/s12859-022-04729-4)
- Curtis MJ, Alexander S, Cirino G, et al. (2018). Experimental design and analysis and their reporting II: updated and simplified guidance for authors and peer reviewers. *British Journal of Pharmacology* 175:987–993. [doi:10.1111/bph.14153](https://doi.org/10.1111/bph.14153)
- Murphy EF, Gilmour SG, Crabbe MJC (2003). Efficient and accurate experimental design for enzyme kinetics: Bayesian studies reveal a systematic approach. *Journal of Biochemical and Biophysical Methods* 55:155–178. [doi:10.1016/S0165-022X(02)00183-5](https://doi.org/10.1016/S0165-022X(02)00183-5)

## Related

- [Comparing IC50 values](https://www.licklider.ai/guides/compare-ic50-dose-response-curves/)
- [What counts as n?](https://www.licklider.ai/guides/what-counts-as-n/)
- [Paired vs unpaired t-test](https://www.licklider.ai/guides/paired-vs-unpaired-t-test/)
- [A t-test with n = 3](https://www.licklider.ai/guides/t-test-with-n-3/)
- [Saturation binding (Kd, Bmax)](https://www.licklider.ai/guides/saturation-binding-kd-bmax/)

## How this page was made

- Written and published by Licklider. Recommendations cite peer-reviewed methods papers, journal guidance and official software documentation listed under References; cited passages were checked against the source text.
- The worked example uses constructed data. Its numbers were produced by stored code (Python 3.13 with SciPy 1.18.1; R 4.3.3, base R), and the two languages print identical output; the simulation was run in Python.
- Separate review passes checked the statistics, the citations and readability before publication.
