Calculations in ecotoxicology. The data used here are summary vaules taken from the Bliss data used by Finney in his description of probit analysis. Data are in 2 forms - individual live/dead values, and summary proportions for each dose.

The data files are here and here

Preliminaries

First, load the required packages (vcd)

source("../R/libraries.R")   #This is the common library
library(performance)
library(vcd)

Disaggregated data file

finndisagg <- read_csv("../data/finneydisagg.csv")
Rows: 292 Columns: 4── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (4): dose, dead, indiv, ldose
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(finndisagg)
plot(dead~dose, data=finndisagg)

Fit GLM with binomial response variable

fdisagg.lm<-glm(dead~dose,family=binomial, data=finndisagg)
tidy(fdisagg.lm, conf.int=TRUE)
plot(fdisagg.lm$fitted.values~finndisagg$dose)

coef(fdisagg.lm)
(Intercept)        dose 
 -3.2798094   0.6117899 
summary(fdisagg.lm) #get model fit

Call:
glm(formula = dead ~ dose, family = binomial, data = finndisagg)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.4539  -0.6157  -0.2718   0.6548   1.8742  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -3.27981    0.37646  -8.712   <2e-16 ***
dose         0.61179    0.06897   8.870   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 402.11  on 291  degrees of freedom
Residual deviance: 249.19  on 290  degrees of freedom
AIC: 253.19

Number of Fisher Scoring iterations: 5

Calc LC50 Use parameter values from the dose-response, solve for 50%

3.28/.612
[1] 5.359477

Original analysis used log 10 transform of dose

fdisagg.lm3<-glm(dead~ldose,family=binomial, data=finndisagg, na.action=na.omit)
tidy(fdisagg.lm3, conf.int=TRUE)
summary(fdisagg.lm3) #get model fit

Call:
glm(formula = dead ~ ldose, family = binomial, data = finndisagg, 
    na.action = na.omit)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.2191  -0.8573   0.4221   0.6445   1.9749  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -5.1177     0.6648  -7.698 1.38e-14 ***
ldose         7.4269     0.9248   8.031 9.70e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 335.05  on 242  degrees of freedom
Residual deviance: 240.14  on 241  degrees of freedom
  (49 observations deleted due to missingness)
AIC: 244.14

Number of Fisher Scoring iterations: 4

solve for y=0; use coefficients to calculate LC50

coef(fdisagg.lm3)
(Intercept)       ldose 
  -5.117703    7.426909 
5.12/7.43
[1] 0.6890983

backtransform to get LC50 on raw dose scale

10^0.689
[1] 4.886524

Fit same model as probit, rather than logit

fdisagg.lm4<-glm(dead~ldose,family=binomial(link="probit"), data=finndisagg, na.action=na.omit)
tidy(fdisagg.lm4, conf.int=TRUE)
summary(fdisagg.lm4) #get model fit

Call:
glm(formula = dead ~ ldose, family = binomial(link = "probit"), 
    data = finndisagg, na.action = na.omit)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.2347  -0.8651   0.4145   0.6614   1.9755  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -3.0308     0.3648  -8.308   <2e-16 ***
ldose         4.3827     0.4994   8.775   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 335.05  on 242  degrees of freedom
Residual deviance: 240.48  on 241  degrees of freedom
  (49 observations deleted due to missingness)
AIC: 244.48

Number of Fisher Scoring iterations: 4

Use coefficients to calculate LC50

coef(fdisagg.lm4)
(Intercept)       ldose 
  -3.030785    4.382655 
3.03/4.38
[1] 0.6917808
10^0.692
[1] 4.920395

For comparison to original analyses, we’ll use aggregated values, generate probit and logit values for each dose and fit linear regression.

finney <- read_csv("../data/finneyagg.csv")
Rows: 6 Columns: 8── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (8): dose, n, responding, propkill, l10dose, probit_finney, probit_raw, logit
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(finney)

Fit logit model

finn.lm<-lm(logit~l10dose, data=finney, na.action=na.omit)
tidy(finn.lm)
#Use coefficients to calculate LC50

coef(finn.lm)
(Intercept)     l10dose 
  -4.805097    7.003509 
4.805/7.004
[1] 0.6860366
# Back-transform to untransformed dose

10^.686
[1] 4.852885

Probit transformed values

finn.lm2<-lm(probit_raw~l10dose,data=finney, na.action=na.omit)
tidy(finn.lm2)
# use coefficients to calculate LC50
coef(finn.lm2)
(Intercept)     l10dose 
  -2.850557    4.150930 
2.85/4.15
[1] 0.686747
# Back-transform
10^0.687
[1] 4.864072

Note: Finney used probits with 5 added

finn.lm3<-lm(probit_finney~l10dose,data = finney, na.action=na.omit)
tidy(finn.lm3)
# solve for y=5
(5-2.14)/4.16
[1] 0.6875
10^.688
[1] 4.875285
LS0tCnRpdGxlOiAiUSAmIEsgQm94IDEzLjMiCm91dHB1dDoKICAgIGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCkNhbGN1bGF0aW9ucyBpbiBlY290b3hpY29sb2d5LiBUaGUgZGF0YSB1c2VkIGhlcmUgYXJlIHN1bW1hcnkgdmF1bGVzIHRha2VuIGZyb20gdGhlIEJsaXNzIGRhdGEgdXNlZCBieSBGaW5uZXkgaW4gaGlzIGRlc2NyaXB0aW9uIG9mIHByb2JpdCBhbmFseXNpcy4gRGF0YSBhcmUgaW4gMiBmb3JtcyAtIGluZGl2aWR1YWwgbGl2ZS9kZWFkIHZhbHVlcywgYW5kIHN1bW1hcnkgcHJvcG9ydGlvbnMgZm9yIGVhY2ggZG9zZS4KClRoZSBkYXRhIGZpbGVzIGFyZSBbaGVyZV0oLi4vZGF0YS9maW5kaXNhZ2cuY3N2KSBhbmQgW2hlcmVdKC4uL2RhdGEvZmlubmV5YWdnLmNzdikKCiMjIyBQcmVsaW1pbmFyaWVzCgpGaXJzdCwgbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMgKHZjZCkKCmBgYHtyIH0Kc291cmNlKCIuLi9SL2xpYnJhcmllcy5SIikgICAjVGhpcyBpcyB0aGUgY29tbW9uIGxpYnJhcnkKbGlicmFyeShwZXJmb3JtYW5jZSkKbGlicmFyeSh2Y2QpCmBgYAoKIyMgRGlzYWdncmVnYXRlZCBkYXRhIGZpbGUKCmBgYHtyfQpmaW5uZGlzYWdnIDwtIHJlYWRfY3N2KCIuLi9kYXRhL2Zpbm5leWRpc2FnZy5jc3YiKQpoZWFkKGZpbm5kaXNhZ2cpCnBsb3QoZGVhZH5kb3NlLCBkYXRhPWZpbm5kaXNhZ2cpCmBgYAoKRml0IEdMTSB3aXRoIGJpbm9taWFsIHJlc3BvbnNlIHZhcmlhYmxlCgpgYGB7cn0KZmRpc2FnZy5sbTwtZ2xtKGRlYWR+ZG9zZSxmYW1pbHk9Ymlub21pYWwsIGRhdGE9ZmlubmRpc2FnZykKdGlkeShmZGlzYWdnLmxtLCBjb25mLmludD1UUlVFKQpwbG90KGZkaXNhZ2cubG0kZml0dGVkLnZhbHVlc35maW5uZGlzYWdnJGRvc2UpCmNvZWYoZmRpc2FnZy5sbSkKc3VtbWFyeShmZGlzYWdnLmxtKSAjZ2V0IG1vZGVsIGZpdApgYGAKCkNhbGMgTEM1MCBVc2UgcGFyYW1ldGVyIHZhbHVlcyBmcm9tIHRoZSBkb3NlLXJlc3BvbnNlLCBzb2x2ZSBmb3IgNTAlCgpgYGB7ciB9CjMuMjgvLjYxMgpgYGAKCiMjIyBPcmlnaW5hbCBhbmFseXNpcyB1c2VkIGxvZyAxMCB0cmFuc2Zvcm0gb2YgZG9zZQoKYGBge3J9CmZkaXNhZ2cubG0zPC1nbG0oZGVhZH5sZG9zZSxmYW1pbHk9Ymlub21pYWwsIGRhdGE9ZmlubmRpc2FnZywgbmEuYWN0aW9uPW5hLm9taXQpCnRpZHkoZmRpc2FnZy5sbTMsIGNvbmYuaW50PVRSVUUpCnN1bW1hcnkoZmRpc2FnZy5sbTMpICNnZXQgbW9kZWwgZml0CmBgYAoKc29sdmUgZm9yIHk9MDsgdXNlIGNvZWZmaWNpZW50cyB0byBjYWxjdWxhdGUgTEM1MAoKYGBge3IgfQpjb2VmKGZkaXNhZ2cubG0zKQo1LjEyLzcuNDMKYGBgCgpiYWNrdHJhbnNmb3JtIHRvIGdldCBMQzUwIG9uIHJhdyBkb3NlIHNjYWxlCgpgYGB7ciB9CjEwXjAuNjg5CmBgYAoKIyMjIEZpdCBzYW1lIG1vZGVsIGFzIHByb2JpdCwgcmF0aGVyIHRoYW4gbG9naXQKCmBgYHtyIH0KZmRpc2FnZy5sbTQ8LWdsbShkZWFkfmxkb3NlLGZhbWlseT1iaW5vbWlhbChsaW5rPSJwcm9iaXQiKSwgZGF0YT1maW5uZGlzYWdnLCBuYS5hY3Rpb249bmEub21pdCkKdGlkeShmZGlzYWdnLmxtNCwgY29uZi5pbnQ9VFJVRSkKc3VtbWFyeShmZGlzYWdnLmxtNCkgI2dldCBtb2RlbCBmaXQKYGBgCgpVc2UgY29lZmZpY2llbnRzIHRvIGNhbGN1bGF0ZSBMQzUwCgpgYGB7ciB9CmNvZWYoZmRpc2FnZy5sbTQpCjMuMDMvNC4zOAoxMF4wLjY5MgpgYGAKCiMjIyBGb3IgY29tcGFyaXNvbiB0byBvcmlnaW5hbCBhbmFseXNlcywgd2UnbGwgdXNlIGFnZ3JlZ2F0ZWQgdmFsdWVzLCBnZW5lcmF0ZSBwcm9iaXQgYW5kIGxvZ2l0IHZhbHVlcyBmb3IgZWFjaCBkb3NlIGFuZCBmaXQgbGluZWFyIHJlZ3Jlc3Npb24uCgpgYGB7ciB9CmZpbm5leSA8LSByZWFkX2NzdigiLi4vZGF0YS9maW5uZXlhZ2cuY3N2IikKaGVhZChmaW5uZXkpCmBgYAoKIyMjIyBGaXQgbG9naXQgbW9kZWwKCmBgYHtyfQpmaW5uLmxtPC1sbShsb2dpdH5sMTBkb3NlLCBkYXRhPWZpbm5leSwgbmEuYWN0aW9uPW5hLm9taXQpCnRpZHkoZmlubi5sbSkKI1VzZSBjb2VmZmljaWVudHMgdG8gY2FsY3VsYXRlIExDNTAKCmNvZWYoZmlubi5sbSkKNC44MDUvNy4wMDQKCgojIEJhY2stdHJhbnNmb3JtIHRvIHVudHJhbnNmb3JtZWQgZG9zZQoKMTBeLjY4NgpgYGAKCiMjIyMgUHJvYml0IHRyYW5zZm9ybWVkIHZhbHVlcwoKYGBge3IgfQpmaW5uLmxtMjwtbG0ocHJvYml0X3Jhd35sMTBkb3NlLGRhdGE9ZmlubmV5LCBuYS5hY3Rpb249bmEub21pdCkKdGlkeShmaW5uLmxtMikKIyB1c2UgY29lZmZpY2llbnRzIHRvIGNhbGN1bGF0ZSBMQzUwCmNvZWYoZmlubi5sbTIpCjIuODUvNC4xNQojIEJhY2stdHJhbnNmb3JtCjEwXjAuNjg3CmBgYAoKKipOb3RlKio6IEZpbm5leSB1c2VkIHByb2JpdHMgd2l0aCA1IGFkZGVkCgpgYGB7cn0KZmlubi5sbTM8LWxtKHByb2JpdF9maW5uZXl+bDEwZG9zZSxkYXRhID0gZmlubmV5LCBuYS5hY3Rpb249bmEub21pdCkKdGlkeShmaW5uLmxtMykKIyBzb2x2ZSBmb3IgeT01Cig1LTIuMTQpLzQuMTYKMTBeLjY4OApgYGAK