There is an excellent R tutorial on fitting the 4 parameter logistic model for calibration purposes (e.g. on ELISA data) here:
http://weightinginbayesianmodels.github.io/poctcalibration/over_tutorials.html
http://weightinginbayesianmodels.github.io/poctcalibration/calib_tut4_curve_ocon.html
http://weightinginbayesianmodels.github.io/poctcalibration/calib_tut5_precision_ocon.html
http://weightinginbayesianmodels.github.io/poctcalibration/AMfunctions.html#sdXhat
They just use the nlsLM
function in the minpack.lm package.
I.e. they fit a a model of the form
x <- c(478, 525, 580, 650, 700, 720, 780, 825, 850, 900, 930, 980, 1020, 1040, 1050, 1075, 1081, 1100, 1160, 1180, 1200)
y <- c(1.70, 1.45, 1.50, 1.42, 1.39, 1.90, 2.49, 2.21, 2.57, 2.90, 3.55, 3.80, 4.27, 4.10, 4.60, 4.42, 4.30, 4.52, 4.40, 4.50, 4.15)
M.4pl <- function(x, lower.asymp, upper.asymp, inflec, hill){
f <- lower.asymp + ((upper.asymp - lower.asymp)/
(1 + (x / inflec)^-hill))
return(f)
}
require(minpack.lm)
nlslmfit = nlsLM(y ~ M.4pl(x, lower.asymp, upper.asymp, inflec, hill),
data = data.frame(x=x, y=y),
start = c(lower.asymp=min(y)+1E-10, upper.asymp=max(y)-1E-10, inflec=mean(x), hill=1),
control = nls.control(maxiter=1000, warnOnly=TRUE) )
summary(nlslmfit)
# Parameters:
# Estimate Std. Error t value Pr(>|t|)
# lower.asymp 1.5371 0.1080 14.24 7.06e-11 ***
# upper.asymp 4.5508 0.1497 30.40 2.93e-16 ***
# inflec 889.1543 14.0924 63.09 < 2e-16 ***
# hill 13.1717 2.5475 5.17 7.68e-05 ***
require(investr)
xvals=seq(min(x),max(x),length.out=100)
predintervals = data.frame(x=xvals,predFit(nlslmfit, newdata=data.frame(x=xvals), interval="prediction"))
confintervals = data.frame(x=xvals,predFit(nlslmfit, newdata=data.frame(x=xvals), interval="confidence"))
require(ggplot2)
qplot(data=predintervals, x=x, y=fit, ymin=lwr, ymax=upr, geom="ribbon", fill=I("red"), alpha=I(0.2)) +
geom_ribbon(data=confintervals, aes(x=x, ymin=lwr, ymax=upr), fill=I("blue"), alpha=I(0.2)) +
geom_line(data=confintervals, aes(x=x, y=fit), colour=I("blue"), lwd=2) +
geom_point(data=data.frame(x=x,y=y), aes(x=x, y=y, ymin=NULL, ymax=NULL), size=5, col="blue") +
ylab("y")

They also show how one could use weights and iteratively refitted least squares to allow for non-homogeneous variance. And it also covers how to do inverse prediction and calculating derived statistics like determining the limit of detection, limit of quantification and working range.
Myself I had more luck using a constrained strictly monotone P spline fit though, fitted using the scam
package, to do calibration curves, as that resulted in much narrower 95% confidence intervals and prediction intervals than using the four parameter logistic model... I.e. a model of the form
require(scam)
nknots = 20 # desired nr of knots
fit = scam(y~s(conc,k=nknots,bs="mpi",m=2), family=gaussian, data=data)
If your data are discrete counts as opposed to some continuous measure you could also use family=poisson
with a log link, or work with a log(y+1)
transformed dependent variable. You could also use log(conc+1)
in your formula as well, as concentration can never go negative.