Using the 4 parameter logistic model (4PL) to study a dose-response curve and calculate the IC50/ED50/EC50 dose is often cumbersome in Excel or requires using commercial software.
Here shows how to use an R package drc to analyze the dose-response curve of the drug CB83 in a clonogenic assay.
The formula of the 4PL is given as \[y=\frac{a-d}{1+(x/c)^b}+d\] where y is the response and x is the concentration. The lower asymptote is a, the bottom of the curve or lower plateau (commonly referred to as the min) and the upper asymptote is d, the top of the curve or upper plateau (commonly referred to as the max). The steepness of the linear portion of the curve is described by the slope factor (also called the Hill slope), b . The parameter c is the concentration corresponding to the response midway between a and d. .
Let’s look at the raw data.
dose | surviving.fraction | cell.line |
---|---|---|
0.0 | 1.0000000 | SCC-61 |
0.5 | 1.1126761 | SCC-61 |
1.0 | 1.1971831 | SCC-61 |
1.5 | 0.9859155 | SCC-61 |
2.0 | 1.0704225 | SCC-61 |
2.5 | 0.9436620 | SCC-61 |
5.0 | 0.1760563 | SCC-61 |
7.5 | 0.0028169 | SCC-61 |
0.0 | 1.0000000 | rSCC-61 |
0.5 | 0.3636364 | rSCC-61 |
1.0 | 0.8961039 | rSCC-61 |
1.5 | 0.4350649 | rSCC-61 |
2.0 | 0.5930736 | rSCC-61 |
2.5 | 0.1515152 | rSCC-61 |
5.0 | 0.0025974 | rSCC-61 |
7.5 | 0.0000000 | rSCC-61 |
There are two options we can choose to fit the curve:
#fit in a 4PL model with set lower and upper limits
model1 <- drm(surviving.fraction ~ dose,cell.line,
fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50")),
data = data,
lowerl = c(NA,0,NA,NA), #set the lower limit
upperl = c(NA,NA,1,NA)) #set the upper limit
#fit in a 4PL model with common lower and upper limits
model2 <- drm(surviving.fraction ~ dose,cell.line,
fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50")),
data = data,
pmodels = data.frame(cell.line, 1, 1, cell.line)) #common lower and upper limits
But here we don’t want to choose neither of those options. So we run another model.
model3<- drm(surviving.fraction ~ dose,cell.line,
fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50")),
data = data)
Quickly check the model summary and plot
##
## Model fitted: Log-logistic (ED50 as parameter) (4 parms)
##
## Parameter estimates:
##
## Estimate Std. Error t-value p-value
## Slope:SCC-61 5.2843125 4.0852318 1.2935 0.231929
## Slope:rSCC-61 5.1255797 7.2096275 0.7109 0.497325
## Lower:SCC-61 0.0015613 0.2659521 0.0059 0.995460
## Lower:rSCC-61 -0.0011548 0.1497806 -0.0077 0.994037
## Upper:SCC-61 1.0829242 0.1053360 10.2807 6.901e-06 ***
## Upper:rSCC-61 0.7229636 0.1489842 4.8526 0.001268 **
## ED50:SCC-61 3.6345600 1.1125919 3.2668 0.011412 *
## ED50:rSCC-61 2.1719768 0.4822210 4.5041 0.001991 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error:
##
## 0.2060725 (8 degrees of freedom)
So the ED50 of CB83 is 3.63456 \(\mu\)M in SCC-61 and 2.1719768 \(\mu\)M in rSCC-61.
We can also obtain other effective doses using ED()
ED(model3, c(10, 90), interval = "delta")
##
## Estimated effective doses
##
## Estimate Std. Error Lower Upper
## e:rSCC-61:10 1.41476 1.01821 -0.93324 3.76276
## e:rSCC-61:90 3.33448 1.84987 -0.93133 7.60028
## e:SCC-61:10 2.39812 0.92984 0.25392 4.54233
## e:SCC-61:90 5.50848 2.71951 -0.76272 11.77969
The following is to produce more sophisticated figures with ggplot2
# make new dose levels to calculate the predicted values based on the model
newdata <- data.frame(dose = rep(exp(seq(log(0.00001), log(10), length = 100)),2), cell.line = rep(c("SCC-61","rSCC-61"),each = 100))
# predictions and confidence intervals based on model3
pm <- predict(model3, newdata=newdata, interval = "confidence")
# append predictions to newdata
newdata$prediction <- pm[,1]
newdata$prediction.lower <- pm[,2]
newdata$prediction.upper <- pm[,3]
# plot with confidence intervals
ggplot(data, aes(x = dose, y = surviving.fraction,shape = cell.line)) +
geom_point() +
geom_ribbon(data = newdata, aes(x = dose, y = prediction,
ymin = prediction.lower,
ymax = prediction.upper),
alpha = 0.2) +
geom_line(data = newdata, aes(x = dose, y = prediction)) +
xlab(expression(paste("Dose (",mu,"M)"))) +
ylab("Surviving fraction") +
theme(legend.title=element_blank())
Normally we don’t plot that, so
p <- ggplot(data, aes(x = dose, y = surviving.fraction, shape = cell.line)) +
geom_point(size = I(3)) +
geom_line(data = newdata, aes(x = dose, y = prediction)) +
xlab(expression(paste("Dose (",mu,"M)"))) +
ylab("Surviving fraction")
q<- p + theme(legend.position = c(1,1), # place legend in the upper right corner
legend.justification = c(1,1),
legend.title = element_blank(),
legend.background = element_rect(color = "black"), # add a box around the legend
# remove panel background and grid
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
axis.line = element_line(colour = "black")) # add back axis lines
q
If we want to use a logarithmic scale for the dose: (note the warning message about taking the log of 0)
q+scale_x_log10()
## Warning: Transformation introduced infinite values in continuous x-axis