Standard curves, detection limits and omics · Choose a method
CFU counts and bacterial burden: log10 scale, dilution series, limit of detection and mice as n
Convert colonies to CFU per organ, analyse log10 burden with mice as n, and record mice with no colonies on the least dilute plate as below the detection limit. Python and R code with a simulation.
On this page
Quick answer. Convert each mouse’s plate count to CFU per organ (colonies × dilution factor ÷ volume plated × homogenate volume), analyse log10 CFU with the mouse as the unit (the cage, if whole cages were treated), and record a mouse with no colonies on the least dilute plate as below the limit of detection (LOD), not as zero or a made-up number. Estimate the drop in burden, with its confidence interval, from a censored (Tobit) model, which uses the fact that undetectable mice are below the LOD without inventing a value; with small groups (in our simulation 8 per group; treat fewer than about 15 per group as small) take the p-value from a Mann–Whitney test, which ties undetectable mice at the bottom (valid when all undetectable mice share one LOD), because the censored model’s test is slightly liberal. Substituting LOD/2 and running a t-test kept false positives near 5% but understated the effect, so use it only as a sensitivity check. In a simulation of 8 vs 8 mice where a treatment lowered burden by 1.5 log10, LOD/2 substitution estimated −1.32 log10 on average and the censored model −1.45; a t-test on raw CFU detected the effect in only 4.3% of studies.
Terms used below. CFU (colony-forming unit): a viable bacterium (or clump) that grows into one colony on a plate. Bacterial burden: CFU per organ (or per gram of tissue or per mL of blood). Homogenate: the organ ground up in a known volume of liquid. Dilution series: 10-fold dilutions of the homogenate, each plated so that one plate has a countable number of colonies. Limit of detection (LOD): the burden that one colony on the least dilute plate represents; here 1 colony in 100 µL of a 1 mL homogenate = 10 CFU per organ. A zero on a diluted plate means a higher limit (10 × the dilution factor). Dilution exponent: d in a 10⁻ᵈ dilution; the dilution factor is 10ᵈ (10 for the 10⁻¹ plate). Liberal test: one giving more than 5% false positives. Sensitivity check: a second analysis run to see whether the conclusion depends on a choice. Fisher’s exact test: compares two proportions in a 2 × 2 table. Kruskal–Wallis test: a rank test for three or more groups. log10 CFU: the base-10 logarithm, so 1,000 CFU is 3 and each step of 1 is a 10-fold change. Censored: known only to be below a value (the LOD), not exactly. Tobit (censored regression) model: a regression that uses the measured values and the fact that some values are below the LOD, without inventing numbers for them. Mann–Whitney test: a rank test; mice below the LOD are tied at the bottom. Log-normal: a skewed distribution whose logarithm is normal. Likelihood-ratio test: compares how well a model fits with and without the group term. Random effect: a term giving each mouse its own baseline when it contributes several values. Fold reduction: 10 to the power of the drop in log10 CFU (a drop of 2 is a 100-fold reduction). Welch t-test: a two-group t-test that does not assume equal spread. 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 |
|---|---|---|
| Does a treatment lower bacterial burden? | log10 CFU per mouse; difference and 95% CI from a censored (Tobit) model; p-value from a Mann–Whitney test with small groups (or the Tobit likelihood-ratio test with larger groups) | Difference in log10 CFU with 95% CI, n mice, number below the LOD per group |
| Many mice below the LOD (more than about half in a group; not simulated here) | Mann–Whitney test, and the proportion of mice with detectable bacteria compared in a 2 × 2 table (detectable or not, by group) with Fisher’s exact test | Proportion detectable per group and the rank test |
| Several organs per mouse | Analyse each organ separately, or one censored model with mouse as a random effect; organs are not extra mice | Results per organ |
| Mice housed together in the same treatment | Cage is the unit if a whole cage gets one treatment: average log10 CFU of the mice in each cage (with undetectable mice at the LOD, as a conservative summary) and compare cages | n cages and n mice |
| Several groups | log10 CFU in a censored regression with group as a factor, or Kruskal–Wallis with pairwise comparisons | Differences with adjusted CIs |
| The same mice over time (blood, faeces) | A censored model with mouse as a random effect, or one summary per mouse; time points are not independent | Differences with 95% CI |
Christen and Parker note that “in most situations the abundance of microbes in the original sample spans several orders of magnitude and therefore it is common to estimate a log abundance that is the log10-transform of the CFU count in dilution 0”; they go on to propose a binomial model of the dilution series for extreme low or high counts (Christen and Parker 2020). The FDA Bacteriological Analytical Manual gives the “colony counting range (per plate) using pour plate method” as “30-300 by USDA (14) and AOAC (3). 25-250 by APHA (1) and APHA (2) and 15-300 by ISO (11)” (FDA BAM chapter 3). On values below the detection limit, Helsel and Lee write that “the typical solution of substituting one-half the detection limit and proceeding with regression, t-tests, etc., has repeatedly been shown to be inaccurate”, recommending survival-analysis (censored-data) methods instead (Helsel and Lee 2008); Lorimer and Kiermeier apply censored regression to microbial counts below the LOD (Lorimer and Kiermeier 2007). ARRIVE 2.0 reminds that “measurements from 50 individual cells from a single mouse represent n = 1 when the experimental unit is the mouse” (ARRIVE 2.0); plates and organs from one mouse are subsamples in the same way.
Worked example: an antibiotic that lowers burden by 1.5 log10
Constructed data: spleens from 8 control and 8 treated mice, each homogenised in 1 mL; 10-fold dilutions were made and 100 µL of each plated, and the most dilute plate with at least 30 colonies was counted (or the undiluted plate if none had 30). True burden is log-normal, about 3.0 log10 (1,000 CFU) in controls with a standard deviation of 1.2 log10, and the treatment lowers it by 1.5 log10. The data’s random seed was the first one tried; with no treatment effect, the same seed gave no mouse below the LOD (that version is not stored), so the example uses a real effect to show what the LOD does. Stored code and data: cfu.csv, model.py, make_data.py, analysis.py, analysis.R and the analysis output; analysis.py and analysis.R print the same output.
Control mice ranged from 100 to 300,000 CFU per spleen, none below the LOD of 10 CFU; in treated mice 4 of 8 were below the LOD and the other 4 ranged from 20 to 42,000 CFU (with half the group undetectable, the median is only known to be near the LOD). For example, 30 colonies on the 10⁻¹ plate is 30 × 10 ÷ 0.1 mL × 1 mL = 3,000 CFU per spleen (3.48 log10).
| Analysis, treated vs control | Result |
|---|---|
| A. Raw CFU, Welch t-test (not valid: skewed over 4 orders of magnitude) | mean 5,284 vs 39,388, p = 0.39 |
| B. (Sensitivity check only) log10 CFU, below-LOD set to LOD/2, Welch t-test | difference −1.64 log10 (95% CI −2.97 to −0.30), p = 0.02 |
| C. Mann–Whitney test on CFU (p-value for small groups) | p = 0.017 |
| D. Censored (Tobit) model of log10 CFU (effect size) | difference −1.99 log10 (95% CI −3.42 to −0.56); its likelihood-ratio p = 0.0095 is slightly liberal at this size, so the p-value to report is C’s |
The raw-CFU test was dominated by two mice with over 40,000 CFU and missed the effect. On the log10 scale, B and C found it; the censored model, which does not pretend the four undetectable mice had exactly 5 CFU, estimated a larger drop in this one data set (−1.99 against −1.64; the true drop was −1.5); across many simulated studies it was the less biased of the two. Across 5,000 simulated studies of this design (simulation.py, the simulation output):
| Scenario (mice below the LOD) | A. Raw CFU (invalid) | B. log10, LOD/2 | C. Mann–Whitney | D. Censored model | Average estimate, B | Average estimate, D |
|---|---|---|---|---|---|---|
| No effect, burden 3.0 log10 (4%) | 0.5% | 4.6% | 3.8% | 7.2% | 0.00 | 0.00 |
| No effect, burden 1.8 log10 (21%) | 0.5% | 4.8% | 4.2% | 7.5% | −0.01 | −0.02 |
| Burden lowered by 1.5 log10 (16%) | 4.3% | 60.9% | 55.9% | 68.2% | −1.32 | −1.45 |
These percentages describe this simulated design (8 mice per group, the burden and spread in model.py), not every infection study. On raw CFU the t-test almost never rejected, because a few heavily infected mice dominate the means. LOD/2 substitution and the Mann–Whitney test kept false positives near 5%, but LOD/2 shrank the estimated effect (−1.32 instead of −1.5), because every undetectable treated mouse was placed just below the LOD. The censored model estimated the effect almost without bias (−1.45) but its likelihood-ratio test gave 7.2–7.5% false positives with 8 mice per group, so with small groups use C for the p-value and D for the size of the effect and its interval. The censored model assumes a similar spread of log10 burden in both groups; if treated mice vary much more, add strata(group) to the R model to let each group have its own spread.
The Python snippet gives the censored estimate; the stored analysis.py also prints its 95% CI and the likelihood-ratio test. The code expects one row per mouse with columns group (control or treated), mouse, dilution (the exponent d of the 10⁻ᵈ dilution counted) and colonies, with 100 µL plated and a 1 mL homogenate (change the volumes in the code for other designs; for CFU per gram divide both CFU and the LOD by organ weight). Each mouse’s LOD follows the plate counted, so a zero on a diluted plate gets a higher limit. For cage-level analysis, add a cage column and average log10 CFU per cage before testing. Rename your columns and labels to match, and run the code in the folder that holds the CSV file. Python needs NumPy, pandas and SciPy; the R code uses the survival package that ships with R.
import numpy as np, pandas as pd
from scipy import stats
from scipy import optimize
d = pd.read_csv("cfu.csv"); VOL_ML, HOMOG_ML = 0.1, 1.0
d["cfu"] = d.colonies * 10.0 ** d.dilution / VOL_ML * HOMOG_ML # CFU per organ
lod = 10.0 ** d.dilution / VOL_ML * HOMOG_ML; below = (d.colonies == 0).to_numpy() # LOD of the plate read
y = np.log10(np.where(below, lod, d.cfu)); x = (d.group == "treated").to_numpy().astype(float)
nll = lambda p: -(stats.norm.logpdf(y[~below], (p[0] + p[1] * x)[~below], np.exp(p[2])).sum()
+ stats.norm.logcdf((y[below] - (p[0] + p[1] * x)[below]) / np.exp(p[2])).sum()) # censored (Tobit)
fit = optimize.minimize(nll, [y.mean(), 0, 0], method="BFGS"); assert fit.success and (~below).sum() > 1
u = stats.mannwhitneyu(d.cfu[x == 1], d.cfu[x == 0], alternative="two-sided", method="asymptotic")
print(f"{below[x == 1].sum()} treated mice below the LOD; Tobit difference {fit.x[1]:.2f} log10; Mann-Whitney p = {u.pvalue:.2g}")
# 4 treated mice below the LOD; Tobit difference -1.99 log10; Mann-Whitney p = 0.017
library(survival)
VOL_ML <- 0.1; HOMOG_ML <- 1.0
d <- read.csv("cfu.csv"); d$cfu <- d$colonies * 10^d$dilution / VOL_ML * HOMOG_ML # CFU per organ
d$lod <- 10^d$dilution / VOL_ML * HOMOG_ML; d$below <- d$colonies == 0 # LOD of the plate read
d$y <- log10(ifelse(d$below, d$lod, d$cfu))
fit <- survreg(Surv(y, !below, type = "left") ~ group, data = d, dist = "gaussian") # censored (Tobit) model
b <- coef(fit)[["grouptreated"]]; se <- sqrt(vcov(fit)["grouptreated", "grouptreated"])
round(c(diff = b, lower = b - 1.96 * se, upper = b + 1.96 * se), 2) # -1.99 (-3.42 to -0.56)
Before you analyse
- Record the dilution, volume plated, homogenate volume and organ weight for every mouse, and compute the LOD from them.
- Count plates in the countable range your method states (for example 30 to 300 colonies for USDA and AOAC; the current FDA BAM protocol uses 15 to 300); if every plate is too dense to count, re-plate further dilutions rather than estimating and use the undiluted plate for low counts.
- Treat a mouse with no colonies on its least dilute plate as below the LOD, report how many mice per group were below it, and say how they were analysed.
- Use the mouse (or the cage, if whole cages were treated) as n; average technical plating replicates first.
- Analyse log10 CFU, and report the effect as a difference in log10 CFU (a fold reduction), not as a difference in raw means.
Check what an AI assistant ran
- The assistant ran a t-test on raw CFU. Ask for log10 CFU.
- The assistant set undetectable mice to 0 and took the log, or dropped them. Ask how values below the LOD were handled.
- The assistant used plates or organs as n. Ask for one value per mouse per organ.
- The assistant reported only the p-value. Ask for the difference in log10 CFU with its interval and the number of mice below the LOD.
Report it
Spleens were homogenised in [1] mL, and 100 µL of 10-fold dilutions was plated; CFU per spleen was calculated from the plate with [30 to 300] colonies (LOD [10] CFU per spleen). The difference in log10 CFU was estimated with a censored (Tobit) regression treating mice below the LOD as censored, and groups were compared with a Mann–Whitney test (R [4.3.3], survival).
In the constructed example, in which the treatment lowered burden by 1.5 log10 by design, the treatment lowered splenic burden (Tobit estimate [−1.99] log10, 95% CI [−3.42] to [−0.56]; Mann–Whitney p = [0.017]; n = [8] mice per group; [4] treated and [0] control mice below the LOD).
Replace the bracketed values with your own.
References
- Christen JA, Parker AE (2020). Systematic statistical analysis of microbial data from dilution series. Journal of Agricultural, Biological and Environmental Statistics 25:339–364. doi:10.1007/s13253-020-00397-0
- US Food and Drug Administration. Bacteriological Analytical Manual, chapter 3: Aerobic plate count. PDF
- Helsel DR, Lee L (2008). Analysis of environmental data with nondetects (NADA for R). useR! 2008 abstract. PDF
- Lorimer MF, Kiermeier A (2007). Analysing microbiological data: Tobit or not Tobit? International Journal of Food Microbiology 116:313–318. doi:10.1016/j.ijfoodmicro.2007.02.001
- Percie du Sert N, Ahluwalia A, Alam S, et al. (2020). Reporting animal research: explanation and elaboration for the ARRIVE guidelines 2.0. PLOS Biology 18:e3000411. Experimental unit explanation
How this page was made
- Written and published by Licklider. Recommendations cite peer-reviewed methods papers, official methods and reporting guidelines 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 with survival 3.5-8), 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.