1 Setup

library(glue)
library(tidyverse)
M <- 4
N <- 30

2 Functions

#' Sample the distribution of information retrieval metrics under the null hypothesis 
#'
#' @param m Number of positive examples (= number of replicates - 1)
#' @param n Number of negative examples (= number of controls, or number of non-replicates)
#' @param nn Number of simulations (default = 1000)
#'
#' @return sampling 
#'
retrieval_metrics_null_sampling <- function(m, n, nn = 1000) {

  y_rank <- 1 - (seq(m + n) / (m + n))
  
  y_boolean <- as.factor(c(rep(TRUE, m), rep(FALSE, n)))
  
  map_df(seq(nn), function(i) {
      x <- as.factor(sample(c(rep(FALSE, n), rep(TRUE, m))))
      
      ap <- yardstick::average_precision_vec(x, y_rank, event_level = "second")
      
      pr <- yardstick::precision_vec(x, y_boolean, event_level = "second")
      
      data.frame(ap, pr)
      
    }) %>%
    mutate(m = m, n = n)
  
}

3 Sample

  • Sample AP and PR distribution under the null hypothesis
  • Keep the ratio of M (number of positive examples) to N (number of negative examples) fixed
  • Sample for many different M, N values
future::plan(future::multisession, workers = 14)

retrieval_metrics_null <- seq(50) %>% 
  furrr::future_map_dfr(function(i)
    retrieval_metrics_null_sampling(M * i, N * i, 10000),
    .options = furrr::furrr_options(seed = TRUE))

retrieval_metrics_null %>% 
  write_csv("retrieval_metrics_null.csv.gz")
retrieval_metrics_null <- 
  read_csv("retrieval_metrics_null.csv.gz", show_col_types = FALSE)

4 Plot

Plot the densities of AP and PR

4.1 Density

retrieval_metrics_null %>% 
  ggplot(aes(ap, group = (m + n), color = (m + n))) + 
  geom_density() +
  ggtitle("Average Precision", subtitle = glue("M = {M} N = {N}"))

retrieval_metrics_null %>% 
  ggplot(aes(pr, group = (m + n), color = (m + n))) + 
  geom_density() +
  ggtitle("Precision@R", subtitle = glue("M = {M} N = {N}"))

4.2 Statistics

Plot summary statistics of AP and PR

summaries <-
  retrieval_metrics_null %>% 
  pivot_longer(-one_of(c("m", "n")), names_to = "metric_name") %>%
  group_by(m, n, metric_name) %>%
  summarize(q95 = quantile(value, 0.95, names = FALSE),
            q90 = quantile(value, 0.90, names = FALSE),
            q75 = quantile(value, 0.75, names = FALSE),
            avg = mean(value),
            .groups = "keep"
            ) %>%
  ungroup() %>%
  pivot_longer(-one_of(c("m", "n", "metric_name")), names_to = "statistic")
summaries %>%
  pivot_wider(id_cols = all_of(c("m", "n", "metric_name")),
              names_from = "statistic",
              values_from = "value") %>%
  head()
summaries %>%
  ggplot(aes(m, value, color = metric_name, linetype = statistic)) + 
  geom_line() +
  geom_hline(yintercept = M / (M+N), alpha = 0.2, linewidth = 2) +
  theme_bw() +
  scale_x_continuous(breaks = scales::pretty_breaks(n = 10)) +
  scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) +
  ggtitle("Average Precision and Precision@R summary statistics", subtitle = glue("M = {M} N = {N}")) +
  labs(caption = "Horizonal gray line is M / (M+N)")

pow <- 1.3
max_value <- 300
break_point <- (seq(1, (max_value)^(1/pow), 1)**(pow))

data.frame(break_point) %>%
  mutate(break_width = break_point - lag(break_point)) %>% 
  na.omit() %>%
  ggplot(aes(break_point, break_width)) + 
  geom_line() + 
  geom_point() + 
  scale_x_continuous(breaks = scales::pretty_breaks(n = 10)) +
  scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) +
  theme_bw() +
  ggtitle("Nonlinear breakpoints", subtitle = glue("seq(1, {max_value}^(1/{pow}), 1)**({pow})"))

5 Conclusion

  • We have been using the mean of AP (and PR) as a null baseline, but it’s not a good choice for a threshold because at low (m, n), the variance is high and we will get high false positives
  • We should use a higher percentile like 90 or 95 for the null threshold
  • However, it’s wiser to move away from the idea of a subtracting a null threshold and instead compute p-values using the empirical null distributions of AP and PR.
    • if we do this, every AP value will be accompanied by a p-value, and the corresponding mAPs will have an average of the p-values; there’s a more principled way to do it by directly sampling the null mAP but that’s a lot of work!
    • we should sample many points from the null - at least 10000
    • it will be too expensive to generate a null for every m, n combination. We should consider binning the n’s (typically n >> m). Consider using the binning strategy above
    • An important consequence: the same AP value but with higher m,n will have more significant p-value. This means that, for example, having more replicates may result in the same AP but more significance. This is obvious, but we’ve been concerned about the dependence on the sample size in the past, so this is something to keep in mind. There’s really no way around it - having more samples means that you are more confident in the result. For example, a COVID test of specificity of 60% when reported with a sample size of 10000 is more significant that with a sample size of 100. The specificity is the same but confidence in the result is different.
LS0tCnRpdGxlOiAiRGlzdHJpYnV0aW9uIG9mIHJhbmRvbSBBdmVyYWdlIFByZWNpc2lvbiBhbmQgUHJlY2lzaW9uQFIiCm91dHB1dDogaHRtbF9ub3RlYm9vawpkYXRlOiAiMjAyMi0xMi0xNCIKLS0tCgojIFNldHVwCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGdsdWUpCmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCmBgYHtyfQpNIDwtIDQKTiA8LSAzMApgYGAKCiMgRnVuY3Rpb25zCgpgYGB7cn0KIycgU2FtcGxlIHRoZSBkaXN0cmlidXRpb24gb2YgaW5mb3JtYXRpb24gcmV0cmlldmFsIG1ldHJpY3MgdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcyAKIycKIycgQHBhcmFtIG0gTnVtYmVyIG9mIHBvc2l0aXZlIGV4YW1wbGVzICg9IG51bWJlciBvZiByZXBsaWNhdGVzIC0gMSkKIycgQHBhcmFtIG4gTnVtYmVyIG9mIG5lZ2F0aXZlIGV4YW1wbGVzICg9IG51bWJlciBvZiBjb250cm9scywgb3IgbnVtYmVyIG9mIG5vbi1yZXBsaWNhdGVzKQojJyBAcGFyYW0gbm4gTnVtYmVyIG9mIHNpbXVsYXRpb25zIChkZWZhdWx0ID0gMTAwMCkKIycKIycgQHJldHVybiBzYW1wbGluZyAKIycKcmV0cmlldmFsX21ldHJpY3NfbnVsbF9zYW1wbGluZyA8LSBmdW5jdGlvbihtLCBuLCBubiA9IDEwMDApIHsKCiAgeV9yYW5rIDwtIDEgLSAoc2VxKG0gKyBuKSAvIChtICsgbikpCiAgCiAgeV9ib29sZWFuIDwtIGFzLmZhY3RvcihjKHJlcChUUlVFLCBtKSwgcmVwKEZBTFNFLCBuKSkpCiAgCiAgbWFwX2RmKHNlcShubiksIGZ1bmN0aW9uKGkpIHsKICAgICAgeCA8LSBhcy5mYWN0b3Ioc2FtcGxlKGMocmVwKEZBTFNFLCBuKSwgcmVwKFRSVUUsIG0pKSkpCiAgICAgIAogICAgICBhcCA8LSB5YXJkc3RpY2s6OmF2ZXJhZ2VfcHJlY2lzaW9uX3ZlYyh4LCB5X3JhbmssIGV2ZW50X2xldmVsID0gInNlY29uZCIpCiAgICAgIAogICAgICBwciA8LSB5YXJkc3RpY2s6OnByZWNpc2lvbl92ZWMoeCwgeV9ib29sZWFuLCBldmVudF9sZXZlbCA9ICJzZWNvbmQiKQogICAgICAKICAgICAgZGF0YS5mcmFtZShhcCwgcHIpCiAgICAgIAogICAgfSkgJT4lCiAgICBtdXRhdGUobSA9IG0sIG4gPSBuKQogIAp9CmBgYAoKIyBTYW1wbGUKCi0gU2FtcGxlIEFQIGFuZCBQUiBkaXN0cmlidXRpb24gdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcwotIEtlZXAgdGhlIHJhdGlvIG9mIE0gKG51bWJlciBvZiBwb3NpdGl2ZSBleGFtcGxlcykgdG8gTiAobnVtYmVyIG9mIG5lZ2F0aXZlIGV4YW1wbGVzKSBmaXhlZAotIFNhbXBsZSBmb3IgbWFueSBkaWZmZXJlbnQgTSwgTiB2YWx1ZXMKCmBgYHtyIGV2YWw9RkFMU0V9CmZ1dHVyZTo6cGxhbihmdXR1cmU6Om11bHRpc2Vzc2lvbiwgd29ya2VycyA9IDE0KQoKcmV0cmlldmFsX21ldHJpY3NfbnVsbCA8LSBzZXEoNTApICU+JSAKICBmdXJycjo6ZnV0dXJlX21hcF9kZnIoZnVuY3Rpb24oaSkKICAgIHJldHJpZXZhbF9tZXRyaWNzX251bGxfc2FtcGxpbmcoTSAqIGksIE4gKiBpLCAxMDAwMCksCiAgICAub3B0aW9ucyA9IGZ1cnJyOjpmdXJycl9vcHRpb25zKHNlZWQgPSBUUlVFKSkKCnJldHJpZXZhbF9tZXRyaWNzX251bGwgJT4lIAogIHdyaXRlX2NzdigicmV0cmlldmFsX21ldHJpY3NfbnVsbC5jc3YuZ3oiKQpgYGAKCgpgYGB7cn0KcmV0cmlldmFsX21ldHJpY3NfbnVsbCA8LSAKICByZWFkX2NzdigicmV0cmlldmFsX21ldHJpY3NfbnVsbC5jc3YuZ3oiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQpgYGAKCiMgUGxvdAoKUGxvdCB0aGUgZGVuc2l0aWVzIG9mIEFQIGFuZCBQUgoKIyMgRGVuc2l0eQoKYGBge3J9CnJldHJpZXZhbF9tZXRyaWNzX251bGwgJT4lIAogIGdncGxvdChhZXMoYXAsIGdyb3VwID0gKG0gKyBuKSwgY29sb3IgPSAobSArIG4pKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArCiAgZ2d0aXRsZSgiQXZlcmFnZSBQcmVjaXNpb24iLCBzdWJ0aXRsZSA9IGdsdWUoIk0gPSB7TX0gTiA9IHtOfSIpKQpgYGAKCmBgYHtyfQpyZXRyaWV2YWxfbWV0cmljc19udWxsICU+JSAKICBnZ3Bsb3QoYWVzKHByLCBncm91cCA9IChtICsgbiksIGNvbG9yID0gKG0gKyBuKSkpICsgCiAgZ2VvbV9kZW5zaXR5KCkgKwogIGdndGl0bGUoIlByZWNpc2lvbkBSIiwgc3VidGl0bGUgPSBnbHVlKCJNID0ge019IE4gPSB7Tn0iKSkKYGBgCiMjIFN0YXRpc3RpY3MKClBsb3Qgc3VtbWFyeSBzdGF0aXN0aWNzIG9mIEFQIGFuZCBQUgoKYGBge3J9CnN1bW1hcmllcyA8LQogIHJldHJpZXZhbF9tZXRyaWNzX251bGwgJT4lIAogIHBpdm90X2xvbmdlcigtb25lX29mKGMoIm0iLCAibiIpKSwgbmFtZXNfdG8gPSAibWV0cmljX25hbWUiKSAlPiUKICBncm91cF9ieShtLCBuLCBtZXRyaWNfbmFtZSkgJT4lCiAgc3VtbWFyaXplKHE5NSA9IHF1YW50aWxlKHZhbHVlLCAwLjk1LCBuYW1lcyA9IEZBTFNFKSwKICAgICAgICAgICAgcTkwID0gcXVhbnRpbGUodmFsdWUsIDAuOTAsIG5hbWVzID0gRkFMU0UpLAogICAgICAgICAgICBxNzUgPSBxdWFudGlsZSh2YWx1ZSwgMC43NSwgbmFtZXMgPSBGQUxTRSksCiAgICAgICAgICAgIGF2ZyA9IG1lYW4odmFsdWUpLAogICAgICAgICAgICAuZ3JvdXBzID0gImtlZXAiCiAgICAgICAgICAgICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHBpdm90X2xvbmdlcigtb25lX29mKGMoIm0iLCAibiIsICJtZXRyaWNfbmFtZSIpKSwgbmFtZXNfdG8gPSAic3RhdGlzdGljIikKYGBgCgoKYGBge3J9CnN1bW1hcmllcyAlPiUKICBwaXZvdF93aWRlcihpZF9jb2xzID0gYWxsX29mKGMoIm0iLCAibiIsICJtZXRyaWNfbmFtZSIpKSwKICAgICAgICAgICAgICBuYW1lc19mcm9tID0gInN0YXRpc3RpYyIsCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSAidmFsdWUiKSAlPiUKICBoZWFkKCkKYGBgCgoKYGBge3J9CnN1bW1hcmllcyAlPiUKICBnZ3Bsb3QoYWVzKG0sIHZhbHVlLCBjb2xvciA9IG1ldHJpY19uYW1lLCBsaW5ldHlwZSA9IHN0YXRpc3RpYykpICsgCiAgZ2VvbV9saW5lKCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IE0gLyAoTStOKSwgYWxwaGEgPSAwLjIsIGxpbmV3aWR0aCA9IDIpICsKICB0aGVtZV9idygpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSAxMCkpICsKICBnZ3RpdGxlKCJBdmVyYWdlIFByZWNpc2lvbiBhbmQgUHJlY2lzaW9uQFIgc3VtbWFyeSBzdGF0aXN0aWNzIiwgc3VidGl0bGUgPSBnbHVlKCJNID0ge019IE4gPSB7Tn0iKSkgKwogIGxhYnMoY2FwdGlvbiA9ICJIb3Jpem9uYWwgZ3JheSBsaW5lIGlzIE0gLyAoTStOKSIpCmBgYAoKYGBge3J9CnBvdyA8LSAxLjMKbWF4X3ZhbHVlIDwtIDMwMApicmVha19wb2ludCA8LSAoc2VxKDEsIChtYXhfdmFsdWUpXigxL3BvdyksIDEpKioocG93KSkKCmRhdGEuZnJhbWUoYnJlYWtfcG9pbnQpICU+JQogIG11dGF0ZShicmVha193aWR0aCA9IGJyZWFrX3BvaW50IC0gbGFnKGJyZWFrX3BvaW50KSkgJT4lIAogIG5hLm9taXQoKSAlPiUKICBnZ3Bsb3QoYWVzKGJyZWFrX3BvaW50LCBicmVha193aWR0aCkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSAxMCkpICsKICB0aGVtZV9idygpICsKICBnZ3RpdGxlKCJOb25saW5lYXIgYnJlYWtwb2ludHMiLCBzdWJ0aXRsZSA9IGdsdWUoInNlcSgxLCB7bWF4X3ZhbHVlfV4oMS97cG93fSksIDEpKiooe3Bvd30pIikpCmBgYAoKCiMgQ29uY2x1c2lvbgoKLSBXZSBoYXZlIGJlZW4gdXNpbmcgdGhlIG1lYW4gb2YgQVAgKGFuZCBQUikgYXMgYSBudWxsIGJhc2VsaW5lLCBidXQgaXQncyBub3QgYSBnb29kIGNob2ljZSBmb3IgYSB0aHJlc2hvbGQgYmVjYXVzZSBhdCBsb3cgKG0sIG4pLCB0aGUgdmFyaWFuY2UgaXMgaGlnaCBhbmQgd2Ugd2lsbCBnZXQgaGlnaCBmYWxzZSBwb3NpdGl2ZXMKLSBXZSBzaG91bGQgdXNlIGEgaGlnaGVyIHBlcmNlbnRpbGUgbGlrZSA5MCBvciA5NSBmb3IgdGhlIG51bGwgdGhyZXNob2xkCi0gSG93ZXZlciwgaXQncyB3aXNlciB0byBtb3ZlIGF3YXkgZnJvbSB0aGUgaWRlYSBvZiBhIHN1YnRyYWN0aW5nIGEgbnVsbCB0aHJlc2hvbGQgYW5kIGluc3RlYWQgY29tcHV0ZSBwLXZhbHVlcyB1c2luZyB0aGUgZW1waXJpY2FsIG51bGwgZGlzdHJpYnV0aW9ucyBvZiBBUCBhbmQgUFIuIAogIC0gaWYgd2UgZG8gdGhpcywgZXZlcnkgQVAgdmFsdWUgd2lsbCBiZSBhY2NvbXBhbmllZCBieSBhIHAtdmFsdWUsIGFuZCB0aGUgY29ycmVzcG9uZGluZyBtQVBzIHdpbGwgaGF2ZSBhbiBhdmVyYWdlIG9mIHRoZSBwLXZhbHVlczsgdGhlcmUncyBhIG1vcmUgcHJpbmNpcGxlZCB3YXkgdG8gZG8gaXQgYnkgZGlyZWN0bHkgc2FtcGxpbmcgdGhlIG51bGwgbUFQIGJ1dCB0aGF0J3MgYSBsb3Qgb2Ygd29yayEKICAtIHdlIHNob3VsZCBzYW1wbGUgbWFueSBwb2ludHMgZnJvbSB0aGUgbnVsbCAtIGF0IGxlYXN0IDEwMDAwIAogIC0gaXQgd2lsbCBiZSB0b28gZXhwZW5zaXZlIHRvIGdlbmVyYXRlIGEgbnVsbCBmb3IgZXZlcnkgbSwgbiBjb21iaW5hdGlvbi4gV2Ugc2hvdWxkIGNvbnNpZGVyIGJpbm5pbmcgdGhlIG4ncyAodHlwaWNhbGx5IG4gPj4gbSkuIENvbnNpZGVyIHVzaW5nIHRoZSBiaW5uaW5nIHN0cmF0ZWd5IGFib3ZlCiAgLSBBbiBpbXBvcnRhbnQgY29uc2VxdWVuY2U6IHRoZSBzYW1lIEFQIHZhbHVlIGJ1dCB3aXRoIGhpZ2hlciBtLG4gd2lsbCBoYXZlIG1vcmUgc2lnbmlmaWNhbnQgcC12YWx1ZS4gVGhpcyBtZWFucyB0aGF0LCBmb3IgZXhhbXBsZSwgaGF2aW5nIG1vcmUgcmVwbGljYXRlcyBtYXkgcmVzdWx0IGluIHRoZSBzYW1lIEFQIGJ1dCBtb3JlIHNpZ25pZmljYW5jZS4gVGhpcyBpcyBvYnZpb3VzLCBidXQgd2UndmUgYmVlbiBjb25jZXJuZWQgYWJvdXQgdGhlIGRlcGVuZGVuY2Ugb24gdGhlIHNhbXBsZSBzaXplIGluIHRoZSBwYXN0LCBzbyB0aGlzIGlzIHNvbWV0aGluZyB0byBrZWVwIGluIG1pbmQuIFRoZXJlJ3MgcmVhbGx5IG5vIHdheSBhcm91bmQgaXQgLSBoYXZpbmcgbW9yZSBzYW1wbGVzIG1lYW5zIHRoYXQgeW91IGFyZSBtb3JlIGNvbmZpZGVudCBpbiB0aGUgcmVzdWx0LiBGb3IgZXhhbXBsZSwgYSBDT1ZJRCB0ZXN0IG9mIHNwZWNpZmljaXR5IG9mIDYwJSB3aGVuIHJlcG9ydGVkIHdpdGggYSBzYW1wbGUgc2l6ZSBvZiAxMDAwMCBpcyBtb3JlIHNpZ25pZmljYW50IHRoYXQgd2l0aCBhIHNhbXBsZSBzaXplIG9mIDEwMC4gVGhlIHNwZWNpZmljaXR5IGlzIHRoZSBzYW1lIGJ1dCBjb25maWRlbmNlIGluIHRoZSByZXN1bHQgaXMgZGlmZmVyZW50LgoKICA=