The MPN package
includes the apc()
function to estimate the Aerobic Plate
Count (APC) point estimate and confidence interval. The
APC estimates the average density of colony forming units
(CFUs) per milliliter. Although other techniques exist (1) to
handle agar plate counts that are too-numerous-to-count (TNTC),
apc()
uses the maximum likelihood technique of Haas &
Heller (2) and Haas et al. (3).
As with the Most Probable Number, the Aerobic Plate Count is estimated by maximizing likelihood. Using a modified version of the notation of Haas et al. (3), we write the likelihood function as:
$$L = \prod_{i=1}^m \frac{{({\lambda}{V_i})} ^ {N_i}}{N_i!} {e ^ {-{\lambda}{V_i}}} \prod_{j=m+1}^n [1 - \Gamma(N_{L,j}-1, \lambda{V_j})]$$
where
As an R function:
L <- function(lambda, count, amount_scor, amount_tntc = NULL, tntc_limit = 100) {
#likelihood
scorable <- prod(dpois(count, lambda = lambda * amount_scor))
if (length(amount_tntc) > 0) {
incomplete_gamma <- pgamma(tntc_limit - 1, lambda * amount_tntc)
tntc <- prod(1 - incomplete_gamma)
return(scorable * tntc)
} else {
return(scorable)
}
}
L_vec <- Vectorize(L, "lambda")
apc()
actually maximizes the log-likelihood function to
solve for λ̂, the maximum
likelihood estimate (MLE) of λ
(i.e., the point estimate of APC). However, let’s demonstrate
what is happening in terms of the likelihood function itself. Assume we
start with four plates and 1 ml of undiluted inoculum. For the first two
plates we use a 100-fold dilution; for the other two plates we use a
1,000-fold dilution. The first two plates were TNTC with limits of 300
and 250. The other plates had CFU counts of 28 and 20:
#APC calculation
library(MPN)
my_count <- c(28, 20) #Ni
my_amount_scor <- 1 * c(.001, .001) #Vi
my_amount_tntc <- 1 * c(.01, .01) #Vj
my_tntc_limit <- c(300, 250) #NLj
(my_apc <- apc(my_count, my_amount_scor, my_amount_tntc, my_tntc_limit))
#> $APC
#> [1] 30183.83
#>
#> $conf_level
#> [1] 0.95
#>
#> $LB
#> [1] 26792.25
#>
#> $UB
#> [1] 34963.34
If we plot the likelihood function, we see that λ̂ maximizes the likelihood:
my_apc$APC
#> [1] 30183.83
my_lambda <- seq(29000, 31500, length = 1000)
my_L <- L_vec(my_lambda, my_count, my_amount_scor, my_amount_tntc,
my_tntc_limit)
plot(my_lambda, my_L, type = "l",
ylab = "Likelihood", main = "Maximum Likelihood")
abline(v = my_apc$APC, lty = 2, col = "red")
If all of the plates have zero counts, the MLE is zero:
all_zero <- c(0, 0) #Ni
(apc_all_zero <- apc(all_zero, my_amount_scor)$APC)
#> [1] 0
my_lambda <- seq(0, 1000, length = 1000)
L_all_zero <- L_vec(my_lambda, all_zero, my_amount_scor)
plot(my_lambda, L_all_zero, type = "l", ylab = "Likelihood",
main = "All Zeroes")
abline(v = apc_all_zero, lty = 2, col = "red")
apc()
computes the confidence interval of λ using the likelihood ratio
approach described in Haas et al. (3). However, since this
approach relies on large-sample theory, the results are more reliable
for larger experiments.
my_count <- c(28, 20)
my_amount_scor <- 1 * c(.001, .001)
my_amount_tntc <- 1 * c(.01, .01)
my_tntc_limit <- c(300, 250)
(my_apc <- apc(my_count, my_amount_scor, my_amount_tntc, my_tntc_limit))
#> $APC
#> [1] 30183.83
#>
#> $conf_level
#> [1] 0.95
#>
#> $LB
#> [1] 26792.25
#>
#> $UB
#> [1] 34963.34
my_lambda <- seq(25000, 36000, length = 1000)
my_L <- L_vec(my_lambda, my_count, my_amount_scor, my_amount_tntc,
my_tntc_limit)
plot(my_lambda, my_L, type = "l",
ylab = "Likelihood", main = "Maximum Likelihood")
abline(v = my_apc$APC, lty = 2, col = "red")
abline(v = my_apc$LB, lty = 3, col = "blue")
abline(v = my_apc$UB, lty = 3, col = "blue")
Bacteriological Analytical Manual, 8th Edition, Chapter 3, https://www.fda.gov/food/laboratory-methods-food/bam-chapter-3-aerobic-plate-count
Haas CN, Heller B (1988). “Averaging of TNTC counts.” Applied and Environmental Microbiology, 54(8), 2069-2072.
Haas CN, Rose JB, Gerba CP (2014). “Quantitative microbial risk assessment, Second Ed.” John Wiley & Sons, Inc., ISBN 978-1-118-14529-6.