Binomial regression and grid sampling for Bioassay data (BDA3 p. 74-)

ggplot2, and gridExtra are used for plotting, tidyr for manipulating data frames

library(ggplot2)
theme_set(theme_minimal())
library(gridExtra)
library(tidyr)
library(dplyr)
library(purrr)

Data

df1 <- data.frame(
  x = c(-0.86, -0.30, -0.05, 0.73),
  n = c(5, 5, 5, 5),
  y = c(0, 1, 3, 5)
)

Plot data

ggplot(df1, aes(x=x, y=y)) +
    geom_point(size=2, color='red') +
    scale_x_continuous(breaks = df1$x, minor_breaks=NULL, limits = c(-1.5, 1.5)) +
    scale_y_continuous(breaks = 0:5, minor_breaks=NULL) +
    labs(title = 'Bioassay', x = 'Dose (log g/ml)', y = 'Number of deaths') +
    theme(panel.grid.major = element_blank())

Compute the posterior density in grid

A = seq(-4, 8, length.out = 50)
B = seq(-10, 40, length.out = 50)

Make vectors that contain all pairwise combinations of A and B

cA <- rep(A, each = length(B))
cB <- rep(B, length(A))

Make a helper function to calculate the log likelihood given a dataframe with x, y, and n and evaluation points a and b. For the likelihood see BDA3 p. 75
log1p(x) computes log(x+1) in numerically more stable way.

logl <- function(df, a, b)
  df['y']*(a + b*df['x']) - df['n']*log1p(exp(a + b*df['x']))

Calculate likelihoods: apply logl function for each observation ie. each row of data frame of x, n and y

p <- apply(df1, 1, logl, cA, cB) %>%
  # sum the log likelihoods of observations
  # and exponentiate to get the joint likelihood
  rowSums() %>% exp()

Sample from the grid (with replacement)

nsamp <- 1000
samp_indices <- sample(length(p), size = nsamp,
                       replace = T, prob = p/sum(p))
samp_A <- cA[samp_indices[1:nsamp]]
samp_B <- cB[samp_indices[1:nsamp]]

Add random jitter, see BDA3 p. 76

samp_A <- samp_A + runif(nsamp, (A[1] - A[2])/2, (A[2] - A[1])/2)
samp_B <- samp_B + runif(nsamp, (B[1] - B[2])/2, (B[2] - B[1])/2)

Create data frame

samps <- data_frame(ind = 1:nsamp, alpha = samp_A, beta = samp_B) %>%
  mutate(ld50 = - alpha/beta)
## Warning: `data_frame()` was deprecated in tibble 1.1.0.
## Please use `tibble()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

Plot draws of logistic curves

invlogit <- plogis
xr <- seq(-1.5, 1.5, length.out = 100)
dff <- pmap_df(samps[1:100,], ~ data_frame(x = xr, id=..1,
                                   f = invlogit(..2 + ..3*x)))
ppost <- ggplot(df1, aes(x=x, y=y/n)) +
  geom_line(data=dff, aes(x=x, y=f, group=id), linetype=1, color='blue', alpha=0.2) +
  geom_point(size=2, color='red') +
  scale_x_continuous(breaks = df1$x, minor_breaks=NULL, limits = c(-1.5, 1.5)) +
  scale_y_continuous(breaks = seq(0,1,length.out=3), minor_breaks=NULL) +
  labs(title = 'Bioassay', x = 'Dose (log g/ml)', y = 'Proportion of deaths') +
  theme(panel.grid.major = element_blank())

add 50% deaths line and LD50 dots

ppost + geom_hline(yintercept = 0.5, linetype = 'dashed', color = 'gray') +
  geom_point(data=samps[1:100,], aes(x=ld50, y=0.5), color='blue', alpha=0.2)

Create a plot of the posterior density

# limits for the plots
xl <- c(-2, 8)
yl <- c(-2, 40)
pos <- ggplot(data = data.frame(cA ,cB, p), aes(cA, cB)) +
  geom_raster(aes(fill = p, alpha = p), interpolate = T) +
  geom_contour(aes(z = p), colour = 'black', size = 0.2) +
  coord_cartesian(xlim = xl, ylim = yl) +
  labs(title = 'Posterior density evaluated in grid', x = 'alpha', y = 'beta') +
  scale_fill_gradient(low = 'yellow', high = 'red', guide = F) +
  scale_alpha(range = c(0, 1), guide = F)

Plot of the samples

sam <- ggplot(data = samps) +
  geom_point(aes(alpha, beta), color = 'blue') +
  coord_cartesian(xlim = xl, ylim = yl) +
  labs(title = 'Posterior draws', x = 'alpha', y = 'beta')

Combine the plots

grid.arrange(pos, sam, nrow=2)
## Warning: It is deprecated to specify `guide = FALSE` to remove a guide. Please use `guide = "none"` instead.
## It is deprecated to specify `guide = FALSE` to remove a guide. Please use `guide = "none"` instead.

Plot of the histogram of LD50

his <- ggplot(data = samps) +
  geom_histogram(aes(ld50), binwidth = 0.02,
                 fill = 'steelblue', color = 'black') +
  coord_cartesian(xlim = c(-0.5, 0.5)) +
  labs(x = 'LD50 = -alpha/beta')
his

LS0tCnRpdGxlOiAiQmF5ZXNpYW4gZGF0YSBhbmFseXNpcyBkZW1vIDMuNiIKYXV0aG9yOiAiQWtpIFZlaHRhcmksIE1hcmt1cyBQYWFzaW5pZW1pIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCkpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCiMjIEJpbm9taWFsIHJlZ3Jlc3Npb24gYW5kIGdyaWQgc2FtcGxpbmcgZm9yIEJpb2Fzc2F5IGRhdGEgKEJEQTMgcC4gNzQtKQoKZ2dwbG90MiwgYW5kIGdyaWRFeHRyYSBhcmUgdXNlZCBmb3IgcGxvdHRpbmcsIHRpZHlyIGZvciBtYW5pcHVsYXRpbmcgZGF0YSBmcmFtZXMKCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeSh0aWR5cikKbGlicmFyeShkcGx5cikKbGlicmFyeShwdXJycikKYGBgCgpEYXRhCgpgYGB7ciB9CmRmMSA8LSBkYXRhLmZyYW1lKAogIHggPSBjKC0wLjg2LCAtMC4zMCwgLTAuMDUsIDAuNzMpLAogIG4gPSBjKDUsIDUsIDUsIDUpLAogIHkgPSBjKDAsIDEsIDMsIDUpCikKYGBgCgpQbG90IGRhdGEKCmBgYHtyIH0KZ2dwbG90KGRmMSwgYWVzKHg9eCwgeT15KSkgKwogICAgZ2VvbV9wb2ludChzaXplPTIsIGNvbG9yPSdyZWQnKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gZGYxJHgsIG1pbm9yX2JyZWFrcz1OVUxMLCBsaW1pdHMgPSBjKC0xLjUsIDEuNSkpICsKICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAwOjUsIG1pbm9yX2JyZWFrcz1OVUxMKSArCiAgICBsYWJzKHRpdGxlID0gJ0Jpb2Fzc2F5JywgeCA9ICdEb3NlIChsb2cgZy9tbCknLCB5ID0gJ051bWJlciBvZiBkZWF0aHMnKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkNvbXB1dGUgdGhlIHBvc3RlcmlvciBkZW5zaXR5IGluIGdyaWQKCiAtIHVzdWFsbHkgc2hvdWxkIGJlIGNvbXB1dGVkIGluIGxvZ2FyaXRobXMhCiAtIHdpdGggYWx0ZXJuYXRpdmUgcHJpb3IsIGNoZWNrIHRoYXQgcmFuZ2UgYW5kIHNwYWNpbmcgb2YgQSBhbmQgQiBhcmUgc2Vuc2libGUKCmBgYHtyIH0KQSA9IHNlcSgtNCwgOCwgbGVuZ3RoLm91dCA9IDUwKQpCID0gc2VxKC0xMCwgNDAsIGxlbmd0aC5vdXQgPSA1MCkKYGBgCgpNYWtlIHZlY3RvcnMgdGhhdCBjb250YWluIGFsbCBwYWlyd2lzZSBjb21iaW5hdGlvbnMgb2YgQSBhbmQgQgoKYGBge3IgfQpjQSA8LSByZXAoQSwgZWFjaCA9IGxlbmd0aChCKSkKY0IgPC0gcmVwKEIsIGxlbmd0aChBKSkKYGBgCgpNYWtlIGEgaGVscGVyIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgbG9nIGxpa2VsaWhvb2QKZ2l2ZW4gYSBkYXRhZnJhbWUgd2l0aCB4LCB5LCBhbmQgbiBhbmQgZXZhbHVhdGlvbgpwb2ludHMgYSBhbmQgYi4gRm9yIHRoZSBsaWtlbGlob29kIHNlZSBCREEzIHAuIDc1PGJyPgpgbG9nMXAoeClgIGNvbXB1dGVzIGxvZyh4KzEpIGluIG51bWVyaWNhbGx5IG1vcmUgc3RhYmxlIHdheS4KCmBgYHtyIH0KbG9nbCA8LSBmdW5jdGlvbihkZiwgYSwgYikKICBkZlsneSddKihhICsgYipkZlsneCddKSAtIGRmWyduJ10qbG9nMXAoZXhwKGEgKyBiKmRmWyd4J10pKQpgYGAKCkNhbGN1bGF0ZSBsaWtlbGlob29kczogYXBwbHkgbG9nbCBmdW5jdGlvbiBmb3IgZWFjaCBvYnNlcnZhdGlvbgppZS4gZWFjaCByb3cgb2YgZGF0YSBmcmFtZSBvZiB4LCBuIGFuZCB5CgpgYGB7ciB9CnAgPC0gYXBwbHkoZGYxLCAxLCBsb2dsLCBjQSwgY0IpICU+JQogICMgc3VtIHRoZSBsb2cgbGlrZWxpaG9vZHMgb2Ygb2JzZXJ2YXRpb25zCiAgIyBhbmQgZXhwb25lbnRpYXRlIHRvIGdldCB0aGUgam9pbnQgbGlrZWxpaG9vZAogIHJvd1N1bXMoKSAlPiUgZXhwKCkKYGBgCgpTYW1wbGUgZnJvbSB0aGUgZ3JpZCAod2l0aCByZXBsYWNlbWVudCkKCmBgYHtyIH0KbnNhbXAgPC0gMTAwMApzYW1wX2luZGljZXMgPC0gc2FtcGxlKGxlbmd0aChwKSwgc2l6ZSA9IG5zYW1wLAogICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSBULCBwcm9iID0gcC9zdW0ocCkpCnNhbXBfQSA8LSBjQVtzYW1wX2luZGljZXNbMTpuc2FtcF1dCnNhbXBfQiA8LSBjQltzYW1wX2luZGljZXNbMTpuc2FtcF1dCmBgYAoKQWRkIHJhbmRvbSBqaXR0ZXIsIHNlZSBCREEzIHAuIDc2CgpgYGB7ciB9CnNhbXBfQSA8LSBzYW1wX0EgKyBydW5pZihuc2FtcCwgKEFbMV0gLSBBWzJdKS8yLCAoQVsyXSAtIEFbMV0pLzIpCnNhbXBfQiA8LSBzYW1wX0IgKyBydW5pZihuc2FtcCwgKEJbMV0gLSBCWzJdKS8yLCAoQlsyXSAtIEJbMV0pLzIpCmBgYAoKIENyZWF0ZSBkYXRhIGZyYW1lCgpgYGB7ciB9CnNhbXBzIDwtIGRhdGFfZnJhbWUoaW5kID0gMTpuc2FtcCwgYWxwaGEgPSBzYW1wX0EsIGJldGEgPSBzYW1wX0IpICU+JQogIG11dGF0ZShsZDUwID0gLSBhbHBoYS9iZXRhKQpgYGAKClBsb3QgZHJhd3Mgb2YgbG9naXN0aWMgY3VydmVzCgpgYGB7ciB9CmludmxvZ2l0IDwtIHBsb2dpcwp4ciA8LSBzZXEoLTEuNSwgMS41LCBsZW5ndGgub3V0ID0gMTAwKQpkZmYgPC0gcG1hcF9kZihzYW1wc1sxOjEwMCxdLCB+IGRhdGFfZnJhbWUoeCA9IHhyLCBpZD0uLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiA9IGludmxvZ2l0KC4uMiArIC4uMyp4KSkpCnBwb3N0IDwtIGdncGxvdChkZjEsIGFlcyh4PXgsIHk9eS9uKSkgKwogIGdlb21fbGluZShkYXRhPWRmZiwgYWVzKHg9eCwgeT1mLCBncm91cD1pZCksIGxpbmV0eXBlPTEsIGNvbG9yPSdibHVlJywgYWxwaGE9MC4yKSArCiAgZ2VvbV9wb2ludChzaXplPTIsIGNvbG9yPSdyZWQnKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGRmMSR4LCBtaW5vcl9icmVha3M9TlVMTCwgbGltaXRzID0gYygtMS41LCAxLjUpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDEsbGVuZ3RoLm91dD0zKSwgbWlub3JfYnJlYWtzPU5VTEwpICsKICBsYWJzKHRpdGxlID0gJ0Jpb2Fzc2F5JywgeCA9ICdEb3NlIChsb2cgZy9tbCknLCB5ID0gJ1Byb3BvcnRpb24gb2YgZGVhdGhzJykgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKYWRkIDUwJSBkZWF0aHMgbGluZSBhbmQgTEQ1MCBkb3RzCgpgYGB7ciB9CnBwb3N0ICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICdkYXNoZWQnLCBjb2xvciA9ICdncmF5JykgKwogIGdlb21fcG9pbnQoZGF0YT1zYW1wc1sxOjEwMCxdLCBhZXMoeD1sZDUwLCB5PTAuNSksIGNvbG9yPSdibHVlJywgYWxwaGE9MC4yKQpgYGAKCkNyZWF0ZSBhIHBsb3Qgb2YgdGhlIHBvc3RlcmlvciBkZW5zaXR5CgpgYGB7ciB9CiMgbGltaXRzIGZvciB0aGUgcGxvdHMKeGwgPC0gYygtMiwgOCkKeWwgPC0gYygtMiwgNDApCnBvcyA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEuZnJhbWUoY0EgLGNCLCBwKSwgYWVzKGNBLCBjQikpICsKICBnZW9tX3Jhc3RlcihhZXMoZmlsbCA9IHAsIGFscGhhID0gcCksIGludGVycG9sYXRlID0gVCkgKwogIGdlb21fY29udG91cihhZXMoeiA9IHApLCBjb2xvdXIgPSAnYmxhY2snLCBzaXplID0gMC4yKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSB4bCwgeWxpbSA9IHlsKSArCiAgbGFicyh0aXRsZSA9ICdQb3N0ZXJpb3IgZGVuc2l0eSBldmFsdWF0ZWQgaW4gZ3JpZCcsIHggPSAnYWxwaGEnLCB5ID0gJ2JldGEnKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAneWVsbG93JywgaGlnaCA9ICdyZWQnLCBndWlkZSA9IEYpICsKICBzY2FsZV9hbHBoYShyYW5nZSA9IGMoMCwgMSksIGd1aWRlID0gRikKYGBgCgpQbG90IG9mIHRoZSBzYW1wbGVzCgpgYGB7ciB9CnNhbSA8LSBnZ3Bsb3QoZGF0YSA9IHNhbXBzKSArCiAgZ2VvbV9wb2ludChhZXMoYWxwaGEsIGJldGEpLCBjb2xvciA9ICdibHVlJykgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0geGwsIHlsaW0gPSB5bCkgKwogIGxhYnModGl0bGUgPSAnUG9zdGVyaW9yIGRyYXdzJywgeCA9ICdhbHBoYScsIHkgPSAnYmV0YScpCmBgYAoKQ29tYmluZSB0aGUgcGxvdHMKCmBgYHtyIH0KZ3JpZC5hcnJhbmdlKHBvcywgc2FtLCBucm93PTIpCmBgYAoKUGxvdCBvZiB0aGUgaGlzdG9ncmFtIG9mIExENTAKCmBgYHtyIH0KaGlzIDwtIGdncGxvdChkYXRhID0gc2FtcHMpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMobGQ1MCksIGJpbndpZHRoID0gMC4wMiwKICAgICAgICAgICAgICAgICBmaWxsID0gJ3N0ZWVsYmx1ZScsIGNvbG9yID0gJ2JsYWNrJykgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygtMC41LCAwLjUpKSArCiAgbGFicyh4ID0gJ0xENTAgPSAtYWxwaGEvYmV0YScpCmhpcwpgYGAKCg==