Eliciting probabilities from experts

R
decision analysis
Bayesian
ecology tutorial
Simulate expert elicitation in R: the four-point question, overconfidence against nominal coverage, linear and logarithmic pooling, and Cooke weights.
Author

Tidy Ecology

Published

2026-07-29

A reintroduction programme has to state the probability that the founded population is still there in twenty years. There is no time series, because the population does not exist yet. There is no comparable release close enough to borrow from. The number goes into the business case anyway, and it comes from asking people who have worked with the species.

That is elicitation, and it is a procedure with a measurable output rather than a shrug. It has a literature, protocols with names, and failure modes that are known and repeatable. What matters about those failures is that they are not noise. They point in a direction: intervals come out too narrow, almost every time, from almost everybody, and the narrowness survives being pointed out.

This is a different job from the one an earlier post did. Structured decision making in R elicits weights: how much a unit of one objective is worth against a unit of another, taken from a single decision maker by a swing exchange rate. That is a value judgement, and there is no fact of the matter to check it against. Here the quantity is a belief about the world, so there is a right answer somewhere, the elicitation can be scored, and the quality control is entirely different. Weights get audited by sensitivity analysis; beliefs get audited by asking questions whose answers you already know.

Everything below runs on simulated experts. Each one has a genuine internal belief and a set of habits that damage it on the way out, and because the simulation knows both the truth and the belief, it can separate the two. That separation is the only reason any of this is measurable. Five things get measured: whether the question order changes the width of the answer, how far below nominal a stated 90 per cent interval actually falls, what changes when you combine experts by averaging densities rather than by multiplying them, whether performance weights earn their keep, and whether any of it reaches the decision the numbers were collected for.

library(ggplot2)

te_pal <- list(forest = "#275139", green = "#2f8f63", sage = "#93a87f",
               clay = "#b5534e", gold = "#cda23f", line = "#dad9ca",
               ink = "#16241d", paper = "#f5f4ee")

theme_te <- function() {
  theme_minimal(base_size = 12) +
    theme(panel.grid.minor = element_blank(),
          panel.grid.major = element_line(colour = "#e7e6dc"),
          plot.background = element_rect(fill = "#f5f4ee", colour = NA),
          panel.background = element_rect(fill = "#f5f4ee", colour = NA),
          plot.title = element_text(face = "bold", colour = te_pal$ink),
          axis.title = element_text(colour = "#2c3a31"),
          axis.text = element_text(colour = "#2c3a31"),
          legend.position = "bottom")
}

An expert with a belief and a set of habits

The simulated expert has two layers. The inner layer is an honest belief about the quantity: a centre and a spread. The centre sits off the true value by an amount drawn from a normal with that same spread, which is the definition of being calibrated. If this expert could report the inner layer directly, their stated intervals would cover at exactly the nominal rate, forever.

The outer layer is what the questioning does to it. Two habits are modelled. The first is a standing bias: some people run optimistic about the species and some run pessimistic, and the offset is a property of the person, not the question. The second is anchoring and adjustment. Ask for a best guess and then for bounds, and the best guess becomes the anchor; the adjustment outward from it is systematically too small, by a factor that varies between people but is consistently below one.

The seed questions are quantities the expert does not know and the analyst does: a count from a survey that has not been published yet, a survival rate from a report in a drawer, the year a population last bred at a site. Values sit on a standardised scale so the units drop out. Twenty four experts, forty questions.

set.seed(20260730)
n_exp <- 24
n_item <- 40

s_int  <- exp(rnorm(n_exp, log(0.55), 0.35))
bias_e <- rnorm(n_exp, 0, 0.45)
phi_e  <- runif(n_exp, 0.35, 0.85)
psi_e  <- phi_e + runif(n_exp, 0.10, 0.35)
conf_e <- runif(n_exp, 0.70, 0.95)

theta <- rnorm(n_item, 0, 1)
mu_mat <- matrix(theta, n_exp, n_item, byrow = TRUE) + bias_e * s_int +
  matrix(rnorm(n_exp * n_item), n_exp) * s_int

zq <- function(q) qnorm(1 - (1 - q) / 2)
err <- abs(mu_mat - matrix(theta, n_exp, n_item, byrow = TRUE))
cover_at <- function(sg, q) rowMeans(err < zq(q) * sg)

print(round(c(experts = n_exp, questions = n_item,
              nominal_level_percent = 90,
              internal_sd_median = median(s_int),
              internal_sd_min = min(s_int), internal_sd_max = max(s_int),
              bias_sd_in_own_units = sd(bias_e),
              adjust_fraction_min = min(phi_e),
              adjust_fraction_max = max(phi_e)), 4))
              experts             questions nominal_level_percent 
              24.0000               40.0000               90.0000 
   internal_sd_median       internal_sd_min       internal_sd_max 
               0.5947                0.3008                1.1439 
 bias_sd_in_own_units   adjust_fraction_min   adjust_fraction_max 
               0.4159                0.4333                0.8279 
print(round(c(honest_coverage_90 = mean(cover_at(s_int, 0.90)),
              honest_coverage_50 = mean(cover_at(s_int, 0.50))), 4))
honest_coverage_90 honest_coverage_50 
            0.9000             0.5042 

If the inner belief were reported without damage, the coverage of a nominal 90 per cent interval across all 960 expert-question pairs would be 0.9, and of a nominal fifty per cent interval 0.5042. The biases are real and forty questions is a small sample, so the agreement is luckier than it looks, but it is close enough that everything below can be read as damage inflicted by the protocol rather than as a property of the people.

The four-point question against the direct one

The direct question is the one everybody asks first: give me a 90 per cent interval for this quantity. The expert forms a best guess, then widens outward by a fraction of the distance they should have gone, so the implied standard deviation of the reported interval is that fraction times their real one.

The four-point method of Speirs-Bridge and colleagues changes the order and adds a step. Ask for the realistically lowest value first, then the realistically highest, then the best guess, and then, last, ask how confident the expert is that the interval they just gave contains the truth. The first three questions remove the central anchor, because there is no best guess on the table when the bounds are set. The fourth turns the interval into something the analyst can rescale: if the expert puts their confidence in the bounds they drew below the level you asked for, then what they drew is a narrower interval than the one you wanted, and widening it to the level you wanted is a matter of the ratio of two normal quantiles.

Both steps are modelled here and both can be switched off, which is the point. The middle row of the table below is the four-point ordering with the confidence question thrown away, so the raw bounds are read as if they were a 90 per cent interval.

sig_direct <- phi_e * s_int
sig_bounds <- psi_e * s_int
sig_four   <- psi_e * zq(0.90) * s_int / zq(conf_e)

lv <- c(0.50, 0.60, 0.70, 0.80, 0.90, 0.95, 0.99)
cov_tab <- data.frame(
  nominal = lv,
  direct  = sapply(lv, function(q) mean(cover_at(sig_direct, q))),
  bounds  = sapply(lv, function(q) mean(cover_at(sig_bounds, q))),
  four    = sapply(lv, function(q) mean(cover_at(sig_four, q))))
print(round(cov_tab, 4))
  nominal direct bounds   four
1    0.50 0.3188 0.4281 0.4979
2    0.60 0.3917 0.5042 0.5865
3    0.70 0.4708 0.5990 0.7010
4    0.80 0.5448 0.7042 0.7781
5    0.90 0.6792 0.8104 0.8760
6    0.95 0.7562 0.8823 0.9302
7    0.99 0.8625 0.9552 0.9760
rescale <- zq(0.90) / zq(conf_e)
print(round(c(width_ratio_four_over_direct = mean(sig_four / sig_direct),
              fraction_of_experts_widened = mean(sig_four > sig_direct),
              mean_rescale_factor = mean(rescale),
              min_rescale_factor = min(rescale),
              max_rescale_factor = max(rescale)), 4))
width_ratio_four_over_direct  fraction_of_experts_widened 
                      1.7005                       1.0000 
         mean_rescale_factor           min_rescale_factor 
                      1.2306                       0.8556 
          max_rescale_factor 
                      1.5763 
c90d <- cover_at(sig_direct, 0.90)
c90b <- cover_at(sig_bounds, 0.90)
c90f <- cover_at(sig_four, 0.90)
print(round(c(gain_from_question_order = mean(c90b) - mean(c90d),
              gain_from_rescaling = mean(c90f) - mean(c90b),
              total_gain = mean(c90f) - mean(c90d)), 4))
gain_from_question_order      gain_from_rescaling               total_gain 
                  0.1312                   0.0656                   0.1969 

The four-point intervals are wider than the direct ones for 100 per cent of the experts, by an average factor of 1.7005. The answer to the first question is that the order matters and it matters in the direction the protocol was designed for.

The decomposition is more useful than the total. Reordering the questions lifts coverage at the nominal 90 per cent level by 0.1312, and the confidence rescaling adds a further 0.0656. Two thirds of the repair comes from not asking for the best guess first, which costs nothing, and one third from a question that most elicitation forms do not contain. The rescale factor itself ranges from 0.8556 to 1.5763 across the panel: an expert who claims 95 per cent confidence in their bounds gets those bounds narrowed, not widened.

How far below nominal the intervals fall

Coverage at one nominal level is a single number and it hides the shape. Sweep the level from 0.5 to 0.99 and the whole calibration curve appears, the diagnostic the elicitation literature borrows from forecast verification: nominal level on the horizontal axis, realised hit rate on the vertical, and a perfectly calibrated panel on the diagonal.

There is an analytic form to check the simulation against. If an expert shrinks their interval by a factor \(k\) and carries a bias of \(b\) in units of their own standard deviation, the realised coverage at nominal level \(q\) is \(\Phi(k z_q - b) - \Phi(-k z_q - b)\) where \(z_q\) is the two-sided normal quantile. Averaging that over the panel should reproduce the simulated curve to within the noise of forty questions.

analytic <- function(sg, q) {
  k <- sg / s_int
  mean(pnorm(k * zq(q) - bias_e) - pnorm(-k * zq(q) - bias_e))
}
cov_tab$direct_theory <- sapply(lv, function(q) analytic(sig_direct, q))
cov_tab$four_theory   <- sapply(lv, function(q) analytic(sig_four, q))
print(round(cov_tab[, c("nominal", "direct", "direct_theory",
                        "four", "four_theory")], 4))
  nominal direct direct_theory   four four_theory
1    0.50 0.3188        0.2980 0.4979      0.4715
2    0.60 0.3917        0.3661 0.5865      0.5649
3    0.70 0.4708        0.4411 0.7010      0.6585
4    0.80 0.5448        0.5279 0.7781      0.7534
5    0.90 0.6792        0.6393 0.8760      0.8533
6    0.95 0.7562        0.7193 0.9302      0.9093
7    0.99 0.8625        0.8344 0.9760      0.9668
max_gap <- max(abs(c(cov_tab$direct - cov_tab$direct_theory,
                     cov_tab$four - cov_tab$four_theory)))
print(round(c(largest_simulation_minus_theory = max_gap,
              surprise_rate_direct = 1 - mean(c90d),
              surprise_rate_four = 1 - mean(c90f), nominal_surprise_rate = 0.10,
              worst_expert_direct = min(c90d), best_expert_direct = max(c90d),
              worst_expert_four = min(c90f), best_expert_four = max(c90f),
              mean_abs_deviation_direct = mean(abs(c90d - 0.9)),
              mean_abs_deviation_four = mean(abs(c90f - 0.9))), 4))
largest_simulation_minus_theory            surprise_rate_direct 
                         0.0425                          0.3208 
             surprise_rate_four           nominal_surprise_rate 
                         0.1240                          0.1000 
            worst_expert_direct              best_expert_direct 
                         0.4000                          0.9250 
              worst_expert_four                best_expert_four 
                         0.6250                          1.0000 
      mean_abs_deviation_direct         mean_abs_deviation_four 
                         0.2229                          0.0719 
print(c(experts_below_half_direct = sum(c90d < 0.5),
        experts_above_nominal_direct = sum(c90d > 0.90),
        experts_above_nominal_four = sum(c90f > 0.90),
        experts_made_worse_by_four = sum(abs(c90f - 0.9) > abs(c90d - 0.9))))
   experts_below_half_direct experts_above_nominal_direct 
                           3                            1 
  experts_above_nominal_four   experts_made_worse_by_four 
                           9                            2 
lab_p <- c("direct 90 per cent question", "four-point order, no rescaling",
           "four-point order plus rescaling")
cal_long <- data.frame(
  nominal = rep(lv, 3),
  realised = c(cov_tab$direct, cov_tab$bounds, cov_tab$four),
  protocol = factor(rep(lab_p, each = length(lv)), levels = lab_p))

ggplot(cal_long, aes(nominal, realised, colour = protocol, shape = protocol)) +
  geom_abline(intercept = 0, slope = 1, linetype = "22", colour = "#9a9a8c") +
  geom_line(linewidth = 0.7) +
  geom_point(size = 2.2) +
  scale_colour_manual(values = c(te_pal$clay, te_pal$gold, te_pal$forest),
                      name = NULL) +
  scale_shape_manual(values = c(17, 15, 16), name = NULL) +
  guides(colour = guide_legend(nrow = 2), shape = guide_legend(nrow = 2)) +
  coord_cartesian(xlim = c(0.45, 1), ylim = c(0.25, 1)) +
  labs(x = "nominal credible level", y = "realised hit rate",
       title = "Three protocols, one panel of experts") +
  theme_te()
Line plot with a dashed grey diagonal running corner to corner. Three coloured curves rise from lower left to upper right below it. The dark green curve is highest: it sits on the diagonal at the left edge, sags a little below it through the middle, and rejoins it near the top right. A gold curve runs a roughly constant distance beneath the green one. A red curve is lowest everywhere, furthest below the diagonal in the right half of the panel, then turning up steeply at the very end.
Figure 1: Realised coverage against nominal level for three ways of asking the same panel the same forty questions. The dashed grey diagonal is perfect calibration. The direct 90 per cent question sits far below it at every level; the four-point ordering closes most of the gap; the confidence rescaling closes a little more and pushes the low nominal levels onto the line.

The simulated curves and the analytic ones agree to 0.0425 at worst, so the shape in the figure is the model and not sampling noise. The headline number is the surprise rate. A stated 90 per cent interval from the direct question missed the truth 32.08 per cent of the time, against a nominal 10 per cent. The four-point protocol brings that down to 12.4 per cent, which is better by a factor of about 2.59 and still not right. Overconfidence is the default state of the output, and a protocol that halves it has done well.

The spread across people is what a panel average hides. Under the direct question the worst expert covered 0.4 of the truths and the best covered 0.925; 3 of the 24 had a 90 per cent interval that worked less than half the time.

Here the measurement went against the story. The four-point protocol is not a uniform improvement, because it is a fixed widening applied to people who need different amounts of it. 9 of the 24 experts now cover above nominal rather than below, one of them at 1, and for 2 experts the absolute distance from nominal got worse rather than better. The panel mean deviation still improves, from 0.2229 to 0.0719, so the protocol is worth using. It just converts a panel of overconfident people into a panel of mostly overconfident people and a few underconfident ones, and the underconfident ones are the experts who needed no help.

What the pooling rule actually does

Now the panel has to become one number, or one distribution. Two rules dominate. The linear opinion pool averages the densities:

\[f_L(p) = \sum_{e} w_e f_e(p)\]

and the logarithmic pool multiplies them and renormalises:

\[f_G(p) \propto \prod_{e} f_e(p)^{w_e}\]

These are not two roads to the same place. The linear pool is a mixture, so it keeps every mode any expert had, and by the law of total variance its variance is the average within-expert variance plus the variance of the experts’ centres. Disagreement between experts makes it wider. The logarithmic pool behaves like a product of likelihoods, so for beliefs in the normal family it adds precisions, and disagreement makes it narrower rather than wider, because two people pointing at different places both rule out a great deal.

For beta beliefs about a probability there is a closed form worth knowing. A beta density is proportional to \(p^{a-1}(1-p)^{b-1}\), so the weighted product is proportional to \(p^{\sum w_e (a_e - 1)}(1 - p)^{\sum w_e (b_e - 1)}\), which is another beta with parameters averaged across the panel. That gives an exact answer to check the grid arithmetic against.

Take a panel that has split. Four experts have worked with the population in the drought years and think persistence is unlikely; four have worked with it since and think it is likely. Each holds a beta belief, and the two camps are mirror images.

pg <- seq(1e-4, 1 - 1e-4, length.out = 4001)
dp <- diff(pg)[1]

pool_summ <- function(dens) {
  dens <- dens / (sum(dens) * dp)
  m <- sum(pg * dens) * dp
  cdf <- cumsum(dens) * dp
  c(mean = m, sd = sqrt(sum((pg - m)^2 * dens) * dp),
    lo = pg[which(cdf >= 0.05)[1]], hi = pg[which(cdf >= 0.95)[1]],
    mode = pg[which.max(dens)])
}
n_modes <- function(dens) {
  k <- length(dens)
  mid <- dens[2:(k - 1)]
  sum(mid > dens[1:(k - 2)] & mid > dens[3:k] & mid > 0.02 * max(dens))
}
lin_pool <- function(D, w) as.vector(D %*% w)
log_pool <- function(D, w) {
  g <- exp(as.vector(log(pmax(D, 1e-300)) %*% w))
  g / (sum(g) * dp)
}

a_v <- c(rep(6, 4), rep(18, 4))
b_v <- c(rep(18, 4), rep(6, 4))
w_eq <- rep(1 / length(a_v), length(a_v))
D_split <- sapply(seq_along(a_v), function(i) dbeta(pg, a_v[i], b_v[i]))
L_split <- lin_pool(D_split, w_eq)
G_split <- log_pool(D_split, w_eq)

expert_modes <- (a_v - 1) / (a_v + b_v - 2)
a_bar <- 1 + sum(w_eq * (a_v - 1))
b_bar <- 1 + sum(w_eq * (b_v - 1))

band <- function(s) unname(s[["hi"]] - s[["lo"]])
print(round(rbind(linear = pool_summ(L_split), logarithmic = pool_summ(G_split)), 4))
            mean     sd     lo     hi   mode
linear       0.5 0.2646 0.1431 0.8569 0.2273
logarithmic  0.5 0.1000 0.3350 0.6650 0.5000
print(round(c(linear_width = band(pool_summ(L_split)),
              log_width = band(pool_summ(G_split)),
              width_ratio = band(pool_summ(L_split)) / band(pool_summ(G_split))), 4))
linear_width    log_width  width_ratio 
      0.7139       0.3299       2.1636 
print(c(modes_linear = n_modes(L_split), modes_log = n_modes(G_split)))
modes_linear    modes_log 
           2            1 
print(round(c(expert_mode_low = unique(expert_modes)[1],
              expert_mode_high = unique(expert_modes)[2],
              log_pool_mode = pool_summ(G_split)[["mode"]],
              closest_expert_mode_distance =
                min(abs(expert_modes - pool_summ(G_split)[["mode"]]))), 4))
             expert_mode_low             expert_mode_high 
                      0.2273                       0.7727 
               log_pool_mode closest_expert_mode_distance 
                      0.5000                       0.2727 
print(round(c(closed_form_a = a_bar, closed_form_b = b_bar,
              closed_form_mode = (a_bar - 1) / (a_bar + b_bar - 2),
              closed_form_sd = sqrt(a_bar * b_bar /
                ((a_bar + b_bar)^2 * (a_bar + b_bar + 1)))), 4))
   closed_form_a    closed_form_b closed_form_mode   closed_form_sd 
            12.0             12.0              0.5              0.1 

Both pools report a mean of 0.5, because the panel is symmetric and no averaging rule can escape that. Everything else differs. The linear pool gives a 90 per cent band from 0.1431 to 0.8569, a width of 0.7139, with 2 modes: it has reported the disagreement. The logarithmic pool gives 0.335 to 0.665, a width of 0.3299, narrower by a factor of 2.1636, with 1 mode.

That single mode is the thing to look at. It sits at 0.5, and the two camps had modes at 0.2273 and 0.7727. Nobody on this panel thought the most likely value was 0.5; the nearest anyone came was 0.2727 away on the probability scale. The logarithmic pool has manufactured a consensus that is not a summary of anybody’s opinion, and reported it with a 90 per cent band narrower than that of either camp. The closed form confirms the arithmetic: the weighted product is Beta(12, 12), whose mode is 0.5 and whose standard deviation is 0.1, matching the grid to four decimal places.

One panel is an anecdote. The sweep below draws 400 random panels of five to nine experts, each belief a beta with a random centre and concentration, and compares the pools on every one.

set.seed(20260731)
n_pan <- 400
sweep_pools <- t(sapply(seq_len(n_pan), function(k) {
  ne <- sample(5:9, 1)
  cen <- rbeta(ne, 2, 2)
  con <- exp(runif(ne, log(6), log(60)))
  D <- sapply(seq_len(ne), function(i) dbeta(pg, cen[i] * con[i],
                                             (1 - cen[i]) * con[i]))
  ww <- rep(1 / ne, ne)
  sL <- pool_summ(lin_pool(D, ww))
  sG <- pool_summ(log_pool(D, ww))
  emod <- (cen * con - 1) / (con - 2)
  c(width_lin = sL[["hi"]] - sL[["lo"]], width_log = sG[["hi"]] - sG[["lo"]],
    sd_lin = sL[["sd"]], sd_log = sG[["sd"]],
    mean_lin = sL[["mean"]], mean_log = sG[["mean"]],
    modes_lin = n_modes(lin_pool(D, ww)), modes_log = n_modes(log_pool(D, ww)),
    log_mode_outside = as.numeric(min(abs(emod - sG[["mode"]])) > 0.05))
}))
sweep_pools <- as.data.frame(sweep_pools)
mean_gap <- abs(sweep_pools$mean_lin - sweep_pools$mean_log)
w_ratio <- sweep_pools$width_lin / sweep_pools$width_log

print(round(c(panels = n_pan,
              fraction_linear_wider = mean(sweep_pools$width_lin >
                                             sweep_pools$width_log),
              fraction_linear_sd_larger = mean(sweep_pools$sd_lin >
                                                 sweep_pools$sd_log),
              median_width_ratio = median(w_ratio),
              min_width_ratio = min(w_ratio),
              max_width_ratio = max(w_ratio)), 4))
                   panels     fraction_linear_wider fraction_linear_sd_larger 
                 400.0000                    1.0000                    1.0000 
       median_width_ratio           min_width_ratio           max_width_ratio 
                   2.2018                    1.2212                    3.4213 
print(round(c(fraction_linear_multimodal = mean(sweep_pools$modes_lin > 1),
              fraction_log_multimodal = mean(sweep_pools$modes_log > 1),
              fraction_log_mode_no_expert = mean(sweep_pools$log_mode_outside),
              mean_absolute_gap_in_pool_mean = mean(mean_gap),
              largest_gap_in_pool_mean = max(mean_gap)), 4))
    fraction_linear_multimodal        fraction_log_multimodal 
                        0.7900                         0.0000 
   fraction_log_mode_no_expert mean_absolute_gap_in_pool_mean 
                        0.3650                         0.0381 
      largest_gap_in_pool_mean 
                        0.1489 
ind <- do.call(rbind, lapply(seq_along(a_v), function(i)
  data.frame(p = pg, d = D_split[, i], who = paste0("expert", i))))
pools_df <- rbind(
  data.frame(p = pg, d = L_split, rule = "linear pool"),
  data.frame(p = pg, d = G_split, rule = "logarithmic pool"))

ggplot() +
  geom_line(data = ind, aes(p, d, group = who), colour = "#b9b8a8",
            linewidth = 0.4) +
  geom_line(data = pools_df, aes(p, d, colour = rule, linetype = rule),
            linewidth = 0.9) +
  scale_colour_manual(values = c(te_pal$forest, te_pal$clay), name = NULL) +
  scale_linetype_manual(values = c("solid", "solid"), name = NULL) +
  labs(x = "probability that the population persists", y = "density",
       title = "Two rules for combining eight experts") +
  theme_te()
Density plot over probability from zero to one. Eight faint grey curves overlap into two tall peaks, one about a quarter of the way along the axis and the other symmetrically placed near three quarters. A dark green curve traces two lower humps of equal height under those peaks and dips almost to the axis between them. A red bell stands alone in that dip, centred at one half, rising well above the green humps and short of the grey ones.
Figure 2: The split panel of eight experts in pale grey, with the linear pool in dark green and the logarithmic pool in red. The linear pool keeps both camps as separate humps; the logarithmic pool is a single narrow spike in the gap between them, at a value no expert held as most likely.

Across all 400 random panels the linear pool was the wider of the two every time, and its standard deviation was the larger every time. The median width ratio was 2.2018 and the ratio ran from 1.2212 to 3.4213. The linear pool was multimodal in 79 per cent of panels and the logarithmic pool in 0 per cent. The split-panel effect is not a special case either: in 36.5 per cent of the random panels the logarithmic mode sat more than 0.05 away on the probability scale from every single expert’s mode.

The centres are not safe either, which is the part that surprises people who assume the pooling rule is a question about spread. The two pools disagreed about the mean by 0.0381 on average and by as much as 0.1489 in the worst panel. On a persistence probability that is the difference between two sentences in a recovery plan.

The rule that falls out of the arithmetic: average the densities if the report should show that the experts disagree, multiply them if you want a working prior for a model that will see data later and you are willing to treat the experts as roughly independent sources of evidence. Choosing the second because it produces a tidier picture is choosing to hide the disagreement.

Performance weights, and what they actually measure

Equal weights are a choice, not the absence of one. Cooke’s classical model replaces them with weights earned on seed questions: quantities with known answers, mixed into the elicitation without being flagged. Each expert’s weight is a product of two scores.

The calibration score asks whether the expert’s quantiles behave like quantiles. With 5, 50 and 95 per cent quantiles the real line splits into four bins, into which the truth should fall with probabilities 0.05, 0.45, 0.45 and 0.05. Compare the observed proportions with those, through twice the number of seeds times the Kullback-Leibler divergence, which is asymptotically chi-square on three degrees of freedom, and take the upper tail probability. Well behaved experts score near one; experts whose truths keep landing outside their 90 per cent range score near zero.

The information score asks how much the expert narrowed things down relative to a uniform background over the intrinsic range of each question. A tight set of quantiles scores high.

The product, with a significance cutoff that zeroes anybody whose calibration score falls below 0.05, is the weight. The decision maker is then the linear pool with those weights, which is what the classical model prescribes.

zqn <- function(q) qnorm(q)

make_panel <- function(seed, n_item, n_exp, world) {
  set.seed(seed)
  s_i <- switch(world,
                skill = exp(rnorm(n_exp, log(0.55), 0.50)),
                exp(rnorm(n_exp, log(0.55), 0.05)))
  ph <- switch(world,
               over = runif(n_exp, 0.30, 1.15),
               runif(n_exp, 0.70, 0.80))
  bi <- rnorm(n_exp, 0, 0.20)
  th <- rnorm(n_item, 0, 1)
  mu <- matrix(th, n_exp, n_item, byrow = TRUE) + bi * s_i +
    matrix(rnorm(n_exp * n_item), n_exp) * s_i
  list(theta = th, mu = mu, sg = ph * s_i, spread = s_i, adjust = ph,
       n_exp = n_exp, n_item = n_item)
}

cooke_scores <- function(pl, idx) {
  th <- pl$theta[idx]
  mu <- pl$mu[, idx, drop = FALSE]
  N <- length(idx)
  q05 <- mu + zqn(0.05) * pl$sg
  q95 <- mu + zqn(0.95) * pl$sg
  thm <- matrix(th, pl$n_exp, N, byrow = TRUE)
  bin <- (thm > q05) + (thm > mu) + (thm > q95) + 1L
  pref <- c(0.05, 0.45, 0.45, 0.05)
  lo_i <- apply(rbind(q05, thm), 2, min)
  hi_i <- apply(rbind(q95, thm), 2, max)
  rng <- hi_i - lo_i
  Lb <- lo_i - 0.1 * rng
  Ub <- hi_i + 0.1 * rng
  span <- matrix(Ub - Lb, 4, N, byrow = TRUE)
  Cs <- numeric(pl$n_exp)
  Is <- numeric(pl$n_exp)
  for (e in seq_len(pl$n_exp)) {
    obs <- tabulate(bin[e, ], 4) / N
    kl <- sum(ifelse(obs > 0, obs * log(obs / pref), 0))
    Cs[e] <- 1 - pchisq(2 * N * kl, df = 3)
    wid <- rbind(q05[e, ] - Lb, mu[e, ] - q05[e, ],
                 q95[e, ] - mu[e, ], Ub - q95[e, ])
    Is[e] <- mean(colSums(pref * log(pref / (wid / span))))
  }
  list(calibration = Cs, information = Is)
}

weights_of <- function(sc, mode, alpha = 0.05) {
  raw <- switch(mode,
                classical = sc$calibration * sc$information *
                  (sc$calibration >= alpha),
                calibration_only = sc$calibration * (sc$calibration >= alpha),
                information_only = sc$information)
  if (sum(raw) <= 0) raw <- rep(1, length(raw))
  raw / sum(raw)
}

log_score <- function(pl, idx, w) {
  thm <- matrix(pl$theta[idx], pl$n_exp, length(idx), byrow = TRUE)
  mean(log(colSums(dnorm(thm, pl$mu[, idx, drop = FALSE], pl$sg) * w)))
}

print(c(lower_quantile_percent = 5, median_percent = 50,
        upper_quantile_percent = 95, chisq_degrees_of_freedom = 3))
  lower_quantile_percent           median_percent   upper_quantile_percent 
                       5                       50                       95 
chisq_degrees_of_freedom 
                       3 
print(c(bin_probabilities = c(0.05, 0.45, 0.45, 0.05)))
bin_probabilities1 bin_probabilities2 bin_probabilities3 bin_probabilities4 
              0.05               0.45               0.45               0.05 
cor_over <- t(sapply(1:150, function(r) {
  pl <- make_panel(41000 + r, 160, 20, "over")
  sc <- cooke_scores(pl, 1:20)
  c(info_vs_adjust = cor(sc$information, pl$adjust),
    calib_vs_adjust = cor(sc$calibration, pl$adjust))
}))
cor_skill <- t(sapply(1:150, function(r) {
  pl <- make_panel(52000 + r, 160, 20, "skill")
  sc <- cooke_scores(pl, 1:20)
  c(info_vs_spread = cor(sc$information, pl$spread),
    calib_vs_spread = cor(sc$calibration, pl$spread))
}))
print(round(colMeans(cor_over), 4))
 info_vs_adjust calib_vs_adjust 
        -0.9769          0.5590 
print(round(colMeans(cor_skill), 4))
 info_vs_spread calib_vs_spread 
        -0.9531         -0.0162 

Before the weights are used for anything, it is worth asking what they correlate with. Two worlds are simulated. In the first, the experts differ only in how much they shrink their intervals: the inner beliefs are equally good, and the variation is pure overconfidence. In the second, the experts differ only in the quality of the inner belief, and everybody reports it with the same shrinkage. Twenty experts, twenty seed questions, 150 repeats of each.

The information score correlates -0.9769 with the shrinkage factor in the first world and -0.9531 with the true belief spread in the second. Those are the same number for two opposite reasons. A narrow set of quantiles earns a high information score whether it is narrow because the expert knows something or narrow because the expert is overconfident, and nothing in the score can separate them. The calibration score can, in principle, which is what the seed questions are for, and it correlates 0.559 with the shrinkage factor. Whether that is enough is a question about sample size.

n_exp_w <- 20
n_item_w <- 160
n_hold <- 50
seed_grid <- c(4, 8, 14, 20, 30, 45, 70, 95)
n_rep <- 60
worlds <- c("skill", "over", "alike")
world_lab <- c(skill = "experts differ in skill",
               over = "experts differ in overconfidence",
               alike = "experts alike")
world_off <- c(skill = 0, over = 3e5, alike = 6e5)

sweep_rows <- list()
for (wd in worlds) {
  for (md in c("classical", "information_only", "calibration_only")) {
    for (ns in seed_grid) {
      adv <- numeric(n_rep)
      eff <- numeric(n_rep)
      for (r in seq_len(n_rep)) {
        pl <- make_panel(1000 * ns + r + world_off[[wd]], n_item_w, n_exp_w, wd)
        set.seed(99000 + r)
        perm <- sample(n_item_w)
        seed_idx <- perm[1:ns]
        hold_idx <- perm[(n_item_w - n_hold + 1):n_item_w]
        w <- weights_of(cooke_scores(pl, seed_idx), md)
        eff[r] <- 1 / sum(w^2)
        adv[r] <- log_score(pl, hold_idx, w) -
          log_score(pl, hold_idx, rep(1 / n_exp_w, n_exp_w))
      }
      sweep_rows[[length(sweep_rows) + 1]] <- data.frame(
        world = wd, scheme = md, n_seed = ns, advantage = mean(adv),
        se = sd(adv) / sqrt(n_rep), win_rate = mean(adv > 0),
        effective_experts = mean(eff))
    }
  }
}
cooke_tab <- do.call(rbind, sweep_rows)
cl <- cooke_tab[cooke_tab$scheme == "classical", ]
show_cols <- c("n_seed", "advantage", "se", "win_rate", "effective_experts")
for (wd in worlds) print(round(cl[cl$world == wd, show_cols], 4))
  n_seed advantage     se win_rate effective_experts
1      4    0.1374 0.0128   0.9500           12.1588
2      8    0.1428 0.0123   0.9667           11.3297
3     14    0.1230 0.0171   0.8333            9.5847
4     20    0.1067 0.0181   0.8000            8.0981
5     30    0.0965 0.0203   0.7167            6.9208
6     45    0.0375 0.0246   0.6167            5.0450
7     70   -0.0158 0.0302   0.5000            3.3697
8     95   -0.1831 0.0452   0.3500            3.2174
   n_seed advantage     se win_rate effective_experts
25      4   -0.0099 0.0037   0.3833           12.0028
26      8   -0.0392 0.0049   0.1500           10.1807
27     14   -0.0580 0.0061   0.0833            8.6837
28     20   -0.0705 0.0055   0.0167            7.6131
29     30   -0.0825 0.0072   0.0500            6.1073
30     45   -0.1161 0.0102   0.0167            5.4569
31     70   -0.1035 0.0071   0.0000            4.5052
32     95   -0.1390 0.0090   0.0000            3.7242
   n_seed advantage     se win_rate effective_experts
49      4   -0.0015 0.0019   0.4500           13.5902
50      8   -0.0002 0.0024   0.6000           13.0429
51     14   -0.0012 0.0024   0.5000           10.8299
52     20   -0.0112 0.0036   0.3500            9.1114
53     30   -0.0156 0.0046   0.3667            7.4460
54     45   -0.0203 0.0048   0.2500            5.4721
55     70   -0.0505 0.0106   0.2500            3.3925
56     95   -0.1435 0.0186   0.0833            2.2649
sk <- cl[cl$world == "skill", ]
cross_lo <- max(sk$n_seed[sk$advantage > 0])
cross_hi <- min(sk$n_seed[sk$n_seed > cross_lo])
pick <- function(s, wd) cooke_tab[cooke_tab$scheme == s & cooke_tab$world == wd, ]
io <- pick("information_only", "skill")
co <- pick("calibration_only", "skill")
co_any <- cooke_tab$advantage[cooke_tab$scheme == "calibration_only"]
print(round(c(skill_last_positive_n_seed = cross_lo,
              skill_first_negative_n_seed = cross_hi,
              info_only_mean_advantage = mean(io$advantage),
              info_only_win_rate = mean(io$win_rate),
              calib_only_max_advantage = max(co$advantage),
              calib_only_best_in_any_world = max(co_any),
              calib_only_at_95 = co$advantage[co$n_seed == 95],
              effective_experts_at_4 = sk$effective_experts[sk$n_seed == 4],
              effective_experts_at_95 = sk$effective_experts[sk$n_seed == 95],
              panel_size = n_exp_w), 4))
  skill_last_positive_n_seed  skill_first_negative_n_seed 
                     45.0000                      70.0000 
    info_only_mean_advantage           info_only_win_rate 
                      0.1571                       1.0000 
    calib_only_max_advantage calib_only_best_in_any_world 
                     -0.0047                      -0.0032 
            calib_only_at_95       effective_experts_at_4 
                     -0.2317                      12.1588 
     effective_experts_at_95                   panel_size 
                      3.2174                      20.0000 
cl$world_f <- factor(world_lab[cl$world], levels = unname(world_lab))

ggplot(cl, aes(n_seed, advantage)) +
  geom_hline(yintercept = 0, linetype = "22", colour = "#9a9a8c") +
  geom_errorbar(aes(ymin = advantage - se, ymax = advantage + se),
                width = 0.04, colour = te_pal$sage) +
  geom_line(colour = te_pal$forest, linewidth = 0.8) +
  geom_point(colour = te_pal$forest, size = 2.1) +
  facet_wrap(~world_f, nrow = 1) +
  scale_x_log10(breaks = c(4, 10, 30, 95)) +
  labs(x = "seed questions used to set the weights",
       y = "log score gain over equal weights",
       title = "When performance weights pay") +
  theme_te() +
  theme(legend.position = "none", plot.margin = margin(8, 14, 4, 8))
Three panels side by side, each with a dashed horizontal line at zero and a dark green line with error bars. In the left panel, labelled experts differ in skill, the line starts well above zero at the left, slopes down across the panel and ends clearly below zero at the right. In the middle panel the line starts just below zero and falls steadily further below it. In the right panel the line starts on the zero line and bends downward only at the far right, ending well below.
Figure 3: Out-of-sample log score of the performance-weighted decision maker minus that of the equal-weight decision maker, against the number of seed questions used to set the weights, in three simulated worlds. Positive means the weights helped. The vertical bars are one standard error over sixty repeats and the dashed line is the break-even level.

The left panel is the case the classical model was built for, and it delivers: with 4 seed questions the weighted decision maker beats the equal-weight one by 0.1374 in mean held-out log density, winning in 95 per cent of repeats. In the middle panel, where the only difference between experts is how overconfident they are, the weights lose at every seed count tested, from -0.0099 at 4 seeds to -0.139 at 95. The right panel, where the experts are interchangeable, starts at break-even and gets worse.

Then the result that goes the wrong way. The advantage in the skill world does not grow with the number of seed questions; it shrinks, crosses zero between 45 and 70 seeds, and is -0.1831 by 95 seeds. More evidence about who is good makes the weighting worse.

The decomposition says why. The information score, which never looks at a truth and therefore does not care how many seed questions there are, is doing all of the winning: on its own it gains 0.1571 in the skill world at every seed count, winning 100 per cent of the time. The calibration score, the only part that uses the answers, never has a positive mean advantage in that world at all: its best is -0.0047 and it reaches -0.2317 at 95 seeds. What the calibration factor does as the seeds accumulate is concentrate the panel. The effective number of experts, one over the sum of squared weights, falls from 12.1588 at 4 seeds to 3.2174 at 95, out of 20. A pool that rests on two or three people has the variance of two or three people.

So the threshold exists but runs backwards from the way it is usually described. It is not a minimum number of seed questions below which the weights are too noisy to trust; it is a maximum, past which the calibration score becomes decisive enough to collapse the panel onto its narrowest survivors. Cooke’s own practice of optimising the cutoff on the seed set answers the same problem from the other direction, and the version above, with the cutoff fixed at 0.05, is the version most people implement.

One caveat belongs here rather than at the end. The held-out score used above is the log density of a mixture, and a mixture is rewarded for having one sharp component near the truth. That is a real property of the linear pool, but it does mean the information score is being judged on the thing it optimises; change the target to one that punishes sharpness and the ranking would move.

Does any of this reach the decision

A number that changes nothing is not worth arguing about. The elicited quantity here is the mean annual growth rate on the log scale of a reintroduced population, and the decision is between translocating founders to a new site and spending the same money protecting the remnant where it is. Translocation succeeds if the founded population exceeds a threshold after twenty years, which under a simple diffusion happens with probability

\[V_A(r) = \Phi\!\left(\frac{\log n_0 + T r - \log N_{\text{crit}}} {\sigma_{\text{env}}\sqrt{T}}\right)\]

and protecting in place has a known value \(v_B\), because that population has been monitored for decades. Here the width of the elicited distribution matters on its own, separately from where it is centred, because \(V_A\) is not linear in \(r\). For a normal belief about \(r\) the expectation is available in closed form: the environmental variance and the belief variance add under the normal integral, so a narrower belief means a smaller denominator, which pushes \(\Phi\) further towards zero or one depending on the sign of the numerator.

That sign is the whole story. \(\Phi\) has its inflection where the argument is zero, which is where the panel thinks translocation is an even bet. Below that point the value function is convex and overconfidence makes the option look worse; above it the function is concave and overconfidence makes it look better. Bias moves the centre instead, and moving the centre moves the answer wherever you are.

set.seed(20260806)
n_panel <- 8000
n_expert <- 12
n0 <- 30
t_yr <- 20
n_crit <- 50
sig_env <- 0.18

ev_translocate <- function(mu, sd) {
  mean(pnorm((log(n0) + t_yr * mu - log(n_crit)) /
               sqrt(t_yr * sig_env^2 + t_yr^2 * sd^2)))
}

panels <- lapply(seq_len(n_panel), function(k) {
  r_true <- rnorm(1, 0.025, 0.035)
  s_i <- exp(rnorm(n_expert, log(0.030), 0.35))
  ph <- runif(n_expert, 0.35, 0.85)
  bi <- rnorm(n_expert, 0, 0.45)
  eps <- rnorm(n_expert) * s_i
  list(mu_clean = r_true + eps, mu_biased = r_true + bi * s_i + eps,
       sd_true = s_i, sd_narrow = ph * s_i)
})

ev <- as.data.frame(t(sapply(panels, function(p) c(
  calibrated = ev_translocate(p$mu_clean, p$sd_true),
  overconfident = ev_translocate(p$mu_clean, p$sd_narrow),
  biased = ev_translocate(p$mu_biased, p$sd_true),
  both = ev_translocate(p$mu_biased, p$sd_narrow)))))

print(round(c(panels = n_panel, experts_per_panel = n_expert,
              founders = n0, horizon_years = t_yr, threshold = n_crit,
              environmental_sd = sig_env), 4))
           panels experts_per_panel          founders     horizon_years 
          8000.00             12.00             30.00             20.00 
        threshold  environmental_sd 
            50.00              0.18 
print(round(c(mean_calibrated_value = mean(ev$calibrated),
              sd_across_panels = sd(ev$calibrated),
              shift_overconfident = mean(abs(ev$overconfident - ev$calibrated)),
              shift_biased = mean(abs(ev$biased - ev$calibrated)),
              shift_both = mean(abs(ev$both - ev$calibrated)),
              fraction_overconfidence_raised_value =
                mean(ev$overconfident > ev$calibrated)), 4))
               mean_calibrated_value                     sd_across_panels 
                              0.4995                               0.2119 
                 shift_overconfident                         shift_biased 
                              0.0137                               0.0191 
                          shift_both fraction_overconfidence_raised_value 
                              0.0226                               0.5018 

Overconfidence moves the expected value of translocation by 0.0137 on average, bias by 0.0191, and the two together by 0.0226. Overconfidence pushed the value up in 50.18 per cent of panels and down in the rest, which is the two-sided behaviour the inflection point predicts and the reason it never shows up as a bias in an average across projects.

Whether a shift of that size changes anything depends on how close the decision was. Sweeping the value of the in-place option across the range a real programme might occupy, and recording how often the flawed elicitation picks the other option, gives the rate directly. The second sweep restricts to close calls: panels where the calibrated expected value lands within 0.05 of the in-place option, which is where a decision analyst would already be nervous.

v_grid <- seq(0.25, 0.80, by = 0.025)
flip <- function(nm, v, keep) {
  mean((ev$calibrated[keep] > v) != (ev[[nm]][keep] > v))
}
switch_tab <- do.call(rbind, lapply(v_grid, function(v) {
  near <- abs(ev$calibrated - v) < 0.05
  data.frame(v_place = v, n_close = sum(near),
             overconfident_all = flip("overconfident", v, rep(TRUE, n_panel)),
             biased_all = flip("biased", v, rep(TRUE, n_panel)),
             overconfident_close = flip("overconfident", v, near),
             biased_close = flip("biased", v, near),
             both_close = flip("both", v, near))
}))
print(round(switch_tab[seq(1, nrow(switch_tab), by = 3), ], 4))
   v_place n_close overconfident_all biased_all overconfident_close
1    0.250     890            0.0229     0.0238              0.2056
4    0.325    1053            0.0171     0.0254              0.1301
7    0.400    1213            0.0156     0.0306              0.1031
10   0.475    1310            0.0058     0.0349              0.0351
13   0.550    1248            0.0066     0.0332              0.0425
16   0.625    1180            0.0179     0.0285              0.1212
19   0.700    1047            0.0235     0.0270              0.1796
22   0.775     811            0.0226     0.0192              0.2232
   biased_close both_close
1        0.2101     0.2258
4        0.1871     0.2118
7        0.1937     0.2168
10       0.2053     0.2107
13       0.2115     0.2099
16       0.1881     0.2220
19       0.2044     0.2311
22       0.1887     0.2256
oc <- switch_tab$overconfident_close
bc <- switch_tab$biased_close
v_min <- switch_tab$v_place[which.min(oc)]
print(round(c(overconfident_close_mean = mean(oc),
              overconfident_close_min = min(oc),
              overconfident_close_min_at = v_min,
              overconfident_close_max = max(oc),
              overconfident_close_max_at = switch_tab$v_place[which.max(oc)]), 4))
  overconfident_close_mean    overconfident_close_min 
                    0.1200                     0.0261 
overconfident_close_min_at    overconfident_close_max 
                    0.5250                     0.2252 
overconfident_close_max_at 
                    0.8000 
print(round(c(biased_close_mean = mean(bc), biased_close_min = min(bc),
              biased_close_max = max(bc),
              biased_close_range = max(bc) - min(bc),
              overconfident_close_range = max(oc) - min(oc)), 4))
        biased_close_mean          biased_close_min          biased_close_max 
                   0.1962                    0.1642                    0.2266 
       biased_close_range overconfident_close_range 
                   0.0625                    0.1991 
print(round(c(both_close_mean = mean(switch_tab$both_close),
              overconfident_all_mean = mean(switch_tab$overconfident_all),
              biased_all_mean = mean(switch_tab$biased_all)), 4))
       both_close_mean overconfident_all_mean        biased_all_mean 
                0.2174                 0.0154                 0.0279 
lab_d <- c("overconfidence only", "bias only")
sw_long <- rbind(
  data.frame(v_place = switch_tab$v_place, rate = oc, flaw = lab_d[1]),
  data.frame(v_place = switch_tab$v_place, rate = bc, flaw = lab_d[2]))
sw_long$flaw <- factor(sw_long$flaw, levels = lab_d)

ggplot(sw_long, aes(v_place, rate, colour = flaw, shape = flaw)) +
  geom_line(linewidth = 0.8) +
  geom_point(size = 2.1) +
  scale_colour_manual(values = c(te_pal$forest, te_pal$clay), name = NULL) +
  scale_shape_manual(values = c(16, 17), name = NULL) +
  labs(x = "value of protecting the remnant in place",
       y = "fraction of close calls that flip",
       title = "Which flaw changes the decision") +
  theme_te() +
  theme(plot.margin = margin(8, 14, 4, 8))
Line plot with two curves. A red curve for bias wobbles along near the top of the panel at a nearly constant height from one edge to the other. A dark green curve for overconfidence forms a deep U: it touches the red curve at the left edge, falls almost to the bottom axis a little past the middle of the horizontal range, then climbs steeply and finishes above the red curve at the right edge.
Figure 4: How often an elicitation flaw flips the choice between translocation and protection in place, among panels where the two options were within five percentage points of each other. The horizontal axis is the known value of the in-place option. Overconfidence has almost no effect where the panel thinks translocation is an even bet, and a large one at both ends; bias flips decisions at roughly the same rate everywhere.

Among close calls, overconfidence alone flipped the choice in 12 per cent of panels on average across the sweep, but the average is the least interesting number in the figure. The rate falls to 2.61 per cent when the in-place option is worth 0.525, near where the panel’s own expected value of translocation sits, and rises to 22.52 per cent at the right-hand end. The range across the sweep is 19.91 percentage points.

Bias behaves completely differently. It flips 19.62 per cent of close calls on average with a range of only 6.25 percentage points across the same sweep: a flat line where overconfidence is a U. Averaged over the sweep bias is the larger problem, 19.62 per cent against 12 per cent, and the two together flip 21.74 per cent.

The practical version: if the panel is genuinely torn about the option in front of them their overconfidence will not change what you decide and their standing bias will, while if the panel leans one way and the alternative is close in value anyway, overconfidence is what pushes you over the line, in whichever direction the panel already leaned. Widening everyone’s intervals by protocol does nothing for the first case and helps in the second.

What to take away

An elicited probability is an output of a procedure, and the procedure has measurable properties. Asking for bounds before the best guess lifted realised coverage by 0.1312 and adding the confidence question lifted it by a further 0.0656, leaving a surprise rate of 12.4 per cent against a nominal 10 per cent. Overconfidence is not eliminated by any of this; it is reduced by about a factor of 2.59.

Three of the measurements went against the direction I expected. The four-point protocol made 2 of 24 experts worse rather than better, because it applies a fixed widening to people who need different amounts of one, and it turned 9 of them from overconfident into underconfident. Performance weights did best with the fewest seed questions and crossed into losing between 45 and 70 of them, because the part of the weight that uses the answers concentrates the panel from 12.1588 effective experts down to 3.2174 without buying accuracy. And bias, not overconfidence, was the flaw that changed the most decisions: 19.62 per cent of close calls against 12 per cent, even though overconfidence is the failure the protocols are all built to fight.

The pooling result is the one to carry into a meeting. A logarithmic pool of a split panel put its mode at 0.5 when the two camps sat at 0.2273 and 0.7727, and reported a 90 per cent band 2.1636 times narrower than the linear pool of the same people. Across 400 random panels the logarithmic mode sat away from every expert’s mode 36.5 per cent of the time. If the pooled number is going into a document that says the experts agreed, the pooling rule needs to be in the document too.

The honest limit is the one this whole post is built on. Every measurement above knows the truth and knows each expert’s real internal belief, and separates protocol damage from ignorance by comparing the two. In a real elicitation neither is available: the quantity is unknown, which is why you asked, and the inner belief is unobservable in principle. Seed questions are the only bridge, and the fourth section measured what it is worth: the best mean advantage the calibration factor reached in any of the three worlds was -0.0032, and the part that carried the gains, the information score, correlated -0.9769 with overconfidence and -0.9531 with genuine sharpness, which means it cannot tell them apart. Nothing in this post identifies a real overconfident expert. It identifies what happens downstream if one is in the room.

References

Cooke RM 1991 Experts in Uncertainty: Opinion and Subjective Probability in Science (ISBN 978-0-19-506465-0)

Clemen RT, Winkler RL 1999 Risk Analysis 19(2):187-203 (10.1111/j.1539-6924.1999.tb00399.x)

Speirs-Bridge A, Fidler F, McBride M, Flander L, Cumming G, Burgman M 2010 Risk Analysis 30(3):512-523 (10.1111/j.1539-6924.2009.01337.x)

Martin TG, Burgman MA, Fidler F, Kuhnert PM, Low-Choy S, McBride M, Mengersen K 2012 Conservation Biology 26(1):29-38 (10.1111/j.1523-1739.2011.01806.x)

McBride MF, Garnett ST, Szabo JK, Burbidge AH, Butchart SHM, Christidis L, Dutson G, Ford HA, Loyn RH, Watson DM, Burgman MA 2012 Methods in Ecology and Evolution 3(5):906-920 (10.1111/j.2041-210X.2012.00221.x)

Morgan MG 2014 Proceedings of the National Academy of Sciences 111(20):7176-7184 (10.1073/pnas.1319946111)

Colson AR, Cooke RM 2017 Reliability Engineering and System Safety 163:109-120 (10.1016/j.ress.2017.02.003)

Hemming V, Burgman MA, Hanea AM, McBride MF, Wintle BC 2018 Methods in Ecology and Evolution 9(1):169-180 (10.1111/2041-210X.12857)

O’Hagan A 2019 The American Statistician 73(sup1):69-81 (10.1080/00031305.2018.1518265)

Newsletter

Get new tutorials by email

New R and QGIS tutorials for ecologists, straight to your inbox. No spam; unsubscribe anytime.

By subscribing you agree to receive these emails and confirm your address once. See the privacy policy.