Load packages

library(ggplot2)
library(dplyr)
library(purrr)
library(tidyr)

Calculations

Grid approximation is easy in this example, or indeed in any case with a single parameter in a bounded range. Simply calculate posteriors along a grid from 0 to 1, for a sequence of 6 W in 9 tosses.

This borrows the tidyverse code ideas from Kurz, \S 2.4.3, https://bookdown.org/content/4857/small-worlds-and-large-worlds.html#grid-approximation.

set.seed(1224)
n_points <- 10
(
  d <-
  tibble(p_grid = seq(from=0, to=1, length.out=n_points),      # define grid
         prior  = 1) |>                                        # define prior
  mutate(likelihood = dbinom(6, size = 9, prob = p_grid)) |>   # compute likelihood at each value
  mutate(unstd_posterior = likelihood * prior) |>              # product of likelihood and prior
  mutate(posterior = unstd_posterior / sum(unstd_posterior))   # standardize posterior, so it sums to 1
)
## # A tibble: 10 x 5
##    p_grid prior likelihood unstd_posterior posterior
##     <dbl> <dbl>      <dbl>           <dbl>     <dbl>
##  1  0         1   0               0         0       
##  2  0.111     1   0.000111        0.000111  0.000123
##  3  0.222     1   0.00476         0.00476   0.00528 
##  4  0.333     1   0.0341          0.0341    0.0379  
##  5  0.444     1   0.111           0.111     0.123   
##  6  0.556     1   0.217           0.217     0.241   
##  7  0.667     1   0.273           0.273     0.303   
##  8  0.778     1   0.204           0.204     0.227   
##  9  0.889     1   0.0568          0.0568    0.0631  
## 10  1         1   0               0         0

Find the maximum posterior (~ MLE)

From the table, you can see that the posterior is a maximum at \(p = 0.667\) (unsurprising, with 6 / 9 waters). In the plot below, I add a dashed line at the p_grid value corresponding to the maximum posterior. Finding this in code:

(maxrow <- which.max(d$posterior))
## [1] 7
d[maxrow, ]
## # A tibble: 1 x 5
##   p_grid prior likelihood unstd_posterior posterior
##    <dbl> <dbl>      <dbl>           <dbl>     <dbl>
## 1  0.667     1      0.273           0.273     0.303

ggplot it

p1 <-
  d |> 
  ggplot(aes(x = p_grid, y = posterior)) +
  geom_point(size=3) +
  geom_line() +
  geom_vline(aes(xintercept = p_grid[which.max(posterior)]), 
             linetype="dashed") +
  labs(subtitle = "20 points",
       x = "probability of water",
       y = "posterior probability") +
  theme_bw(base_size = 16) +
  theme(panel.grid = element_blank())
p1

Generalize this to various number of points

Make a dataset, stacking the observations for each number of points. I’ll use n_points = c(5, 10, 15, 20) here.

The rest uses some fancy tidyr and purrr tools, perhaps worth explaining step by step. In the first step, create a tibble with one row for each number of points and a list of that number of p_grid values.

tibble(n_points = c(5, 10, 15, 20)) |> 
  mutate(p_grid = map(n_points, ~seq(from = 0, to = 1, length.out = .)))
## # A tibble: 4 x 2
##   n_points p_grid    
##      <dbl> <list>    
## 1        5 <dbl [5]> 
## 2       10 <dbl [10]>
## 3       15 <dbl [15]>
## 4       20 <dbl [20]>

Then unnest the lists and add the prior. This gives a tibble with sum(c(5, 10, 15, 20)) = 50 rows.

tibble(n_points = c(5, 10, 15, 20)) |> 
  mutate(p_grid = map(n_points, ~seq(from = 0, to = 1, length.out = .))) |> 
  unnest(p_grid) |> 
  expand(nesting(n_points, p_grid),
         prior = 1) 
## # A tibble: 50 x 3
##    n_points p_grid prior
##       <dbl>  <dbl> <dbl>
##  1        5  0         1
##  2        5  0.25      1
##  3        5  0.5       1
##  4        5  0.75      1
##  5        5  1         1
##  6       10  0         1
##  7       10  0.111     1
##  8       10  0.222     1
##  9       10  0.333     1
## 10       10  0.444     1
## # ... with 40 more rows

The remaining step is to calculate the likelihood and posterior for each row.

dat <- tibble(n_points = c(5, 10, 15, 20)) |> 
  mutate(p_grid = map(n_points, ~seq(from = 0, to = 1, length.out = .))) |> 
  unnest(p_grid) |> 
  expand(nesting(n_points, p_grid),
         prior = 1) |>
  mutate(likelihood = dbinom(6, size = 9, prob = p_grid)) |> 
  mutate(posterior = likelihood * prior / sum(likelihood * prior))
dat
## # A tibble: 50 x 5
##    n_points p_grid prior likelihood posterior
##       <dbl>  <dbl> <dbl>      <dbl>     <dbl>
##  1        5  0         1   0        0        
##  2        5  0.25      1   0.00865  0.00188  
##  3        5  0.5       1   0.164    0.0356   
##  4        5  0.75      1   0.234    0.0507   
##  5        5  1         1   0        0        
##  6       10  0         1   0        0        
##  7       10  0.111     1   0.000111 0.0000241
##  8       10  0.222     1   0.00476  0.00103  
##  9       10  0.333     1   0.0341   0.00741  
## 10       10  0.444     1   0.111    0.0241   
## # ... with 40 more rows

Make the plot

Plot posterior ~ p_grid, faceting by n_points

Define a tiny function to be used to label the facets

appender <- function(string, suffix = "points") paste(string, suffix)

ggplot(dat, aes(x = p_grid, y = posterior)) +
  geom_line() +
  geom_point(size=2) +
  geom_vline(aes(xintercept = p_grid[which.max(posterior)]), linetype="dashed") +
  labs(x = "probability of water",
       y = "posterior probability") +
  theme(panel.grid = element_blank()) +
  facet_wrap(~n_points, labeller = as_labeller(appender)) +
  theme_bw(base_size = 16)

Extensions

Different priors

McElrath suggests trying out other, non-uniform priors. Here’s one way to do that. Kurz has a fancier version that uses these in a grid approximation. Here, I just plot the priors.

n_points <- 10
p_grid <- seq(from=0, to=1, length.out=n_points)
prior1 <- ifelse(p_grid < 0.5, 0, 1)
prior2 <- exp(-5 * abs( p_grid - 0.5))

Make this stuff into a stacked tibble

pdat <- bind_rows(
          bind_cols(prior = "step", p_grid = p_grid, prob = prior1),
          bind_cols(prior = "exp",  p_grid = p_grid, prob = prior2),
)
pdat
## # A tibble: 20 x 3
##    prior p_grid   prob
##    <chr>  <dbl>  <dbl>
##  1 step   0     0     
##  2 step   0.111 0     
##  3 step   0.222 0     
##  4 step   0.333 0     
##  5 step   0.444 0     
##  6 step   0.556 1     
##  7 step   0.667 1     
##  8 step   0.778 1     
##  9 step   0.889 1     
## 10 step   1     1     
## 11 exp    0     0.0821
## 12 exp    0.111 0.143 
## 13 exp    0.222 0.249 
## 14 exp    0.333 0.435 
## 15 exp    0.444 0.757 
## 16 exp    0.556 0.757 
## 17 exp    0.667 0.435 
## 18 exp    0.778 0.249 
## 19 exp    0.889 0.143 
## 20 exp    1     0.0821

Plot them, overlaid using color=prior

ggplot(pdat, aes(p_grid, prob, color=prior)) +
  geom_point(size=4) + 
  geom_line(size = 1.5) +
  theme_bw(base_size = 16) +
  theme(legend.position = c(0.15, .80))

  • The ifelse prior might correspond to a strong belief that the world must have at least 50% water.
  • The exp prior: ???
IycgLS0tDQojJyB0aXRsZTogR3JpZCBhcHByb3hpbWF0aW9uIGV4YW1wbGUNCiMnIGF1dGhvcjogIk1pY2hhZWwgRnJpZW5kbHkiDQojJyBkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCkpYCINCiMnIG91dHB1dDoNCiMnICAgaHRtbF9kb2N1bWVudDoNCiMnICAgICB0aGVtZTogcmVhZGFibGUNCiMnICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQojJyAtLS0NCg0KIysgZWNobz1GQUxTRQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIFIub3B0aW9ucz1saXN0KGRpZ2l0cz00KSkNCg0KIycgTG9hZCBwYWNrYWdlcw0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocHVycnIpDQpsaWJyYXJ5KHRpZHlyKQ0KDQoNCiMnICMjIENhbGN1bGF0aW9ucw0KIycgR3JpZCBhcHByb3hpbWF0aW9uIGlzIGVhc3kgaW4gdGhpcyBleGFtcGxlLCBvciBpbmRlZWQgaW4gYW55IGNhc2Ugd2l0aCBhIHNpbmdsZSBwYXJhbWV0ZXIgaW4gYSBib3VuZGVkIHJhbmdlLg0KIycgU2ltcGx5IGNhbGN1bGF0ZSBwb3N0ZXJpb3JzIGFsb25nIGEgZ3JpZCBmcm9tIDAgdG8gMSwgZm9yIGEgc2VxdWVuY2Ugb2YgNiBXIGluIDkgdG9zc2VzLg0KIycgDQojJyAqIE9uZSBrZXkgdG8gdGhpcyBpcyB0aGF0IGBkYmlub20oKWAgaXMgdmVjdG9yaXplZCwgc28gd2UgY2FuIGNhbGN1bGF0ZSBsaWtlbGlob29kIGZvciBhIGdyaWQgb2YgYHByb2JgIHZhbHVlcyBhbGwgYXQgb25jZS4NCiMnICogVGhlIGBwcmlvcmAgaXMgVVswLCAxXSwgY29uc3RhbnQgb3ZlciAkXFByKFcpJCwgc28gYHByaW9yPTFgIHN1ZmZpY2VzLiBGb3Igb3RoZXIgcHJpb3JzLCBjYWxjdWxhdGUgdmFsdWVzIG92ZXIgdGhlIHNhbWUgZ3JpZC4NCiMnIA0KIycgVGhpcyBib3Jyb3dzIHRoZSB0aWR5dmVyc2UgY29kZSBpZGVhcyBmcm9tIEt1cnosIFxcUyAyLjQuMywgaHR0cHM6Ly9ib29rZG93bi5vcmcvY29udGVudC80ODU3L3NtYWxsLXdvcmxkcy1hbmQtbGFyZ2Utd29ybGRzLmh0bWwjZ3JpZC1hcHByb3hpbWF0aW9uLg0KIycNCiMnDQpzZXQuc2VlZCgxMjI0KQ0Kbl9wb2ludHMgPC0gMTANCigNCiAgZCA8LQ0KICB0aWJibGUocF9ncmlkID0gc2VxKGZyb209MCwgdG89MSwgbGVuZ3RoLm91dD1uX3BvaW50cyksICAgICAgIyBkZWZpbmUgZ3JpZA0KICAgICAgICAgcHJpb3IgID0gMSkgfD4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBkZWZpbmUgcHJpb3INCiAgbXV0YXRlKGxpa2VsaWhvb2QgPSBkYmlub20oNiwgc2l6ZSA9IDksIHByb2IgPSBwX2dyaWQpKSB8PiAgICMgY29tcHV0ZSBsaWtlbGlob29kIGF0IGVhY2ggdmFsdWUNCiAgbXV0YXRlKHVuc3RkX3Bvc3RlcmlvciA9IGxpa2VsaWhvb2QgKiBwcmlvcikgfD4gICAgICAgICAgICAgICMgcHJvZHVjdCBvZiBsaWtlbGlob29kIGFuZCBwcmlvcg0KICBtdXRhdGUocG9zdGVyaW9yID0gdW5zdGRfcG9zdGVyaW9yIC8gc3VtKHVuc3RkX3Bvc3RlcmlvcikpICAgIyBzdGFuZGFyZGl6ZSBwb3N0ZXJpb3IsIHNvIGl0IHN1bXMgdG8gMQ0KKQ0KDQojJyAjIyBGaW5kIHRoZSBtYXhpbXVtIHBvc3RlcmlvciAofiBNTEUpDQojJyBGcm9tIHRoZSB0YWJsZSwgeW91IGNhbiBzZWUgdGhhdCB0aGUgcG9zdGVyaW9yIGlzIGEgbWF4aW11bSBhdCAkcCA9IDAuNjY3JCAodW5zdXJwcmlzaW5nLCB3aXRoIDYgLyA5IHdhdGVycykuDQojJyBJbiB0aGUgcGxvdCBiZWxvdywgSSBhZGQgYSBkYXNoZWQgbGluZSBhdCB0aGUgYHBfZ3JpZGAgdmFsdWUgY29ycmVzcG9uZGluZyB0byB0aGUgbWF4aW11bSBwb3N0ZXJpb3IuDQojJyBGaW5kaW5nIHRoaXMgaW4gY29kZToNCiMnIA0KKG1heHJvdyA8LSB3aGljaC5tYXgoZCRwb3N0ZXJpb3IpKQ0KZFttYXhyb3csIF0NCg0KIycgIyMgZ2dwbG90IGl0DQpwMSA8LQ0KICBkIHw+IA0KICBnZ3Bsb3QoYWVzKHggPSBwX2dyaWQsIHkgPSBwb3N0ZXJpb3IpKSArDQogIGdlb21fcG9pbnQoc2l6ZT0zKSArDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IHBfZ3JpZFt3aGljaC5tYXgocG9zdGVyaW9yKV0pLCANCiAgICAgICAgICAgICBsaW5ldHlwZT0iZGFzaGVkIikgKw0KICBsYWJzKHN1YnRpdGxlID0gIjIwIHBvaW50cyIsDQogICAgICAgeCA9ICJwcm9iYWJpbGl0eSBvZiB3YXRlciIsDQogICAgICAgeSA9ICJwb3N0ZXJpb3IgcHJvYmFiaWxpdHkiKSArDQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArDQogIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpDQpwMQ0KDQojJyAjIyBHZW5lcmFsaXplIHRoaXMgdG8gdmFyaW91cyBudW1iZXIgb2YgcG9pbnRzDQoNCiMnIE1ha2UgYSBkYXRhc2V0LCBzdGFja2luZyB0aGUgb2JzZXJ2YXRpb25zIGZvciBlYWNoIG51bWJlciBvZiBwb2ludHMuIEknbGwgdXNlIGBuX3BvaW50cyA9IGMoNSwgMTAsIDE1LCAyMClgIGhlcmUuDQojJw0KIycgVGhlIHJlc3QgdXNlcyBzb21lIGZhbmN5IGB0aWR5cmAgYW5kIGBwdXJycmAgdG9vbHMsIHBlcmhhcHMgd29ydGggZXhwbGFpbmluZyBzdGVwIGJ5IHN0ZXAuDQojJyBJbiB0aGUgZmlyc3Qgc3RlcCwgY3JlYXRlIGEgdGliYmxlIHdpdGggb25lIHJvdyBmb3IgZWFjaCBudW1iZXIgb2YgcG9pbnRzIGFuZCBhIGxpc3Qgb2YgdGhhdCBudW1iZXIgb2YgYHBfZ3JpZGANCiMnIHZhbHVlcy4NCiMnIA0KdGliYmxlKG5fcG9pbnRzID0gYyg1LCAxMCwgMTUsIDIwKSkgfD4gDQogIG11dGF0ZShwX2dyaWQgPSBtYXAobl9wb2ludHMsIH5zZXEoZnJvbSA9IDAsIHRvID0gMSwgbGVuZ3RoLm91dCA9IC4pKSkNCg0KIycgVGhlbiBgdW5uZXN0YCB0aGUgbGlzdHMgYW5kIGFkZCB0aGUgYHByaW9yYC4gVGhpcyBnaXZlcyBhIHRpYmJsZSB3aXRoIA0KIycgYHN1bShjKDUsIDEwLCAxNSwgMjApKWAgPSBgciBzdW0oYyg1LCAxMCwgMTUsIDIwKSlgIHJvd3MuDQoNCnRpYmJsZShuX3BvaW50cyA9IGMoNSwgMTAsIDE1LCAyMCkpIHw+IA0KICBtdXRhdGUocF9ncmlkID0gbWFwKG5fcG9pbnRzLCB+c2VxKGZyb20gPSAwLCB0byA9IDEsIGxlbmd0aC5vdXQgPSAuKSkpIHw+IA0KICB1bm5lc3QocF9ncmlkKSB8PiANCiAgZXhwYW5kKG5lc3Rpbmcobl9wb2ludHMsIHBfZ3JpZCksDQogICAgICAgICBwcmlvciA9IDEpIA0KDQojJyBUaGUgcmVtYWluaW5nIHN0ZXAgaXMgdG8gY2FsY3VsYXRlIHRoZSBgbGlrZWxpaG9vZGAgYW5kIGBwb3N0ZXJpb3JgIGZvciBlYWNoIHJvdy4NCg0KZGF0IDwtIHRpYmJsZShuX3BvaW50cyA9IGMoNSwgMTAsIDE1LCAyMCkpIHw+IA0KICBtdXRhdGUocF9ncmlkID0gbWFwKG5fcG9pbnRzLCB+c2VxKGZyb20gPSAwLCB0byA9IDEsIGxlbmd0aC5vdXQgPSAuKSkpIHw+IA0KICB1bm5lc3QocF9ncmlkKSB8PiANCiAgZXhwYW5kKG5lc3Rpbmcobl9wb2ludHMsIHBfZ3JpZCksDQogICAgICAgICBwcmlvciA9IDEpIHw+DQogIG11dGF0ZShsaWtlbGlob29kID0gZGJpbm9tKDYsIHNpemUgPSA5LCBwcm9iID0gcF9ncmlkKSkgfD4gDQogIG11dGF0ZShwb3N0ZXJpb3IgPSBsaWtlbGlob29kICogcHJpb3IgLyBzdW0obGlrZWxpaG9vZCAqIHByaW9yKSkNCmRhdA0KDQojJyAjIyBNYWtlIHRoZSBwbG90DQojJyBQbG90IGBwb3N0ZXJpb3IgfiBwX2dyaWRgLCBmYWNldGluZyBieSBgbl9wb2ludHNgDQojJw0KIycgRGVmaW5lIGEgdGlueSBmdW5jdGlvbiB0byBiZSB1c2VkIHRvIGxhYmVsIHRoZSBmYWNldHMNCmFwcGVuZGVyIDwtIGZ1bmN0aW9uKHN0cmluZywgc3VmZml4ID0gInBvaW50cyIpIHBhc3RlKHN0cmluZywgc3VmZml4KQ0KDQpnZ3Bsb3QoZGF0LCBhZXMoeCA9IHBfZ3JpZCwgeSA9IHBvc3RlcmlvcikpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3BvaW50KHNpemU9MikgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gcF9ncmlkW3doaWNoLm1heChwb3N0ZXJpb3IpXSksIGxpbmV0eXBlPSJkYXNoZWQiKSArDQogIGxhYnMoeCA9ICJwcm9iYWJpbGl0eSBvZiB3YXRlciIsDQogICAgICAgeSA9ICJwb3N0ZXJpb3IgcHJvYmFiaWxpdHkiKSArDQogIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgZmFjZXRfd3JhcCh+bl9wb2ludHMsIGxhYmVsbGVyID0gYXNfbGFiZWxsZXIoYXBwZW5kZXIpKSArDQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KQ0KDQojJyAjIyBFeHRlbnNpb25zDQojJyANCiMnICMjIyBEaWZmZXJlbnQgcHJpb3JzDQojJyANCiMnIE1jRWxyYXRoIHN1Z2dlc3RzIHRyeWluZyBvdXQgb3RoZXIsIG5vbi11bmlmb3JtIHByaW9ycy4gSGVyZSdzIG9uZSB3YXkgdG8gZG8gdGhhdC4gS3VyeiBoYXMgYSBmYW5jaWVyIHZlcnNpb24gdGhhdA0KIycgdXNlcyB0aGVzZSBpbiBhIGdyaWQgYXBwcm94aW1hdGlvbi4gIEhlcmUsIEkganVzdCBwbG90IHRoZSBwcmlvcnMuDQojJyANCm5fcG9pbnRzIDwtIDEwDQpwX2dyaWQgPC0gc2VxKGZyb209MCwgdG89MSwgbGVuZ3RoLm91dD1uX3BvaW50cykNCnByaW9yMSA8LSBpZmVsc2UocF9ncmlkIDwgMC41LCAwLCAxKQ0KcHJpb3IyIDwtIGV4cCgtNSAqIGFicyggcF9ncmlkIC0gMC41KSkNCg0KIycgTWFrZSB0aGlzIHN0dWZmIGludG8gYSBzdGFja2VkIHRpYmJsZQ0KcGRhdCA8LSBiaW5kX3Jvd3MoDQogICAgICAgICAgYmluZF9jb2xzKHByaW9yID0gInN0ZXAiLCBwX2dyaWQgPSBwX2dyaWQsIHByb2IgPSBwcmlvcjEpLA0KICAgICAgICAgIGJpbmRfY29scyhwcmlvciA9ICJleHAiLCAgcF9ncmlkID0gcF9ncmlkLCBwcm9iID0gcHJpb3IyKSwNCikNCnBkYXQNCg0KIycgUGxvdCB0aGVtLCBvdmVybGFpZCB1c2luZyBgY29sb3I9cHJpb3JgDQpnZ3Bsb3QocGRhdCwgYWVzKHBfZ3JpZCwgcHJvYiwgY29sb3I9cHJpb3IpKSArDQogIGdlb21fcG9pbnQoc2l6ZT00KSArIA0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuNSkgKw0KICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuMTUsIC44MCkpDQoNCiMnICogVGhlIGBpZmVsc2VgIHByaW9yIG1pZ2h0IGNvcnJlc3BvbmQgdG8gYSBzdHJvbmcgYmVsaWVmIHRoYXQgdGhlIHdvcmxkIG11c3QgaGF2ZSBhdCBsZWFzdCA1MCUgd2F0ZXIuDQojJyAqIFRoZSBgZXhwYCBwcmlvcjogPz8/DQojJyA=