The {drc} package has the functionality you are looking for. However your dataset is too limited to be able to calculate any confidence interval on the model parameters. Essentially you need your curve to plateau at the bottom so the model can converge on a confidence interval for the fit. The method below will work if you have a more complete dataset in the future though.
Since you included a control
value at each time point that seems to change substantially, I calculated a relative_survival
by normalizing to this value to help standardize the top of the curves. Also, I constrained some of the fit parameters based on basic understanding of what the LD50 should be, the curve should start around 1
, eventually reach 0
and the slope should be negative.
I started with a quick visualization of the whole dataset to help set expectations about what kind of modeling should be possible with this data.
Note for some reason in the {drc} package the sign of the Hill slope is reversed. So for decreasing curves the value is reported as positive.
library(tidyverse)
library(drc)
# set up data
d <- tibble(
control = c(100, 100, 100, 96, 96, 96, 96, 72, 60, 60, 60, 60, 60, 52, 48, 48, 40, 40),
ng_300 = c(100.00, 100.00, 100.00, 96.30, 96.30, 92.59, 92.59,70.37, 62.96, 44.44, 40.74, 37.04, 29.63, 25.93, 25.93,22.22, 11.11, 11.11),
ng_30 = c(100.00, 96.30, 96.30, 96.30, 96.30, 96.30, 96.30, 85.19, 81.48, 77.78, 74.07, 74.07, 74.07, 70.37, 70.37, 70.37, 70.37, 62.96),
ng_3 = c(100.00, 100.00, 100.00, 100.00, 100.00, 96.30, 85.19, 74.07, 70.37, 66.67, 66.67, 66.67, 66.67, 59.26, 59.26, 59.26, 59.26, 55.56),
time_hr = c(0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68))
# reformat data to facilitate analysis
e <- d %>%
pivot_longer(starts_with("ng"), names_prefix = "ng_", names_to = "dose_ng", values_to = "survival") %>%
mutate(relative_survival = survival/control,
dose_ng = as.numeric(dose_ng))
# visualize to understand dose-response trend over time
e %>%
ggplot(aes(dose_ng, relative_survival, color = time_hr)) +
geom_point() +
stat_smooth(method = "drm",
method.args = list(fct = LL.4(names = c("hill", "bottom", "top", "EC50")),
type = "continuous",
upperl = c(10, 0.1, Inf, Inf),
lowerl = c(0.1, -0.1, -Inf, -Inf)
),
se = F) +
scale_x_log10() +
ylim(0, NA) +
facet_wrap(~time_hr) +
scale_color_viridis_c(option = "A", end = 0.8) +
theme_bw() +
theme(axis.text.x = element_text(angle = 90))

# build model
drc_mod <- e %>%
filter(time_hr %in% c(24, 48)) %>%
mutate(time_hr = factor(time_hr)) %>%
drm(
formula = relative_survival ~ dose_ng,
curveid = time_hr,
data = .,
fct = LL.4(names = c("hill", "bottom", "top", "EC50")),
type = "continuous",
upperl = c(10, 0.1, Inf, Inf),
lowerl = c(0.1, -0.1, -Inf, -Inf)
)
# visualize model
plot(drc_mod, ylim = c(0, 1.5), xlim = c(0, 300))

# report coefficients for each time point
drc_mod$coefficients
#> hill:24 hill:48 bottom:24 bottom:48 top:24 top:48
#> 1.000000e-01 7.418135e+00 2.000000e-01 8.080277e-02 1.184212e+00 1.172833e+00
#> EC50:24 EC50:48
#> 3.490536e+06 2.805554e+02
# assess confidence interval on each model parameter
drc_mod %>% confint()
#> 2.5 % 97.5 %
#> hill:24 NaN NaN
#> hill:48 NaN NaN
#> bottom:24 NaN NaN
#> bottom:48 NaN NaN
#> top:24 NaN NaN
#> top:48 NaN NaN
#> EC50:24 NaN NaN
#> EC50:48 NaN NaN
Created on 2022-10-21 by the reprex package (v2.0.1)