2
$\begingroup$

I want to use the boot package to calculate bootstrap confidence intervals for the mean. Sure, I could do this by inverting a t-test, but I want to see what happens when I use a bootstrap approach.

I do this by simulating some data and then passing the data to the boot::boot function in R, with the statistic set to the usual mean function.

library(boot)
set.seed(2024)
N <- 100 # Sample size
B <- 999 # Number of bootstrap samples drawn

# Simulate some data, distributed N(0, 1)
#
x <- rnorm(N, 0, 1)

# Try to bootstrap with just "mean" as the statistic
#
boot::boot(x, mean, R = B)

# Oops, that failed!

This code fails.

From the documentation, ?boot::boot.

A function which when applied to data returns a vector containing the statistic(s) of interest. When sim = "parametric", the first argument to statistic must be the data. For each replicate a simulated dataset returned by ran.gen will be passed. In all other cases statistic must take at least two arguments. The first argument passed will always be the original data. The second will be a vector of indices, frequencies or weights which define the bootstrap sample. Further, if predictions are required, then a third argument is required which would be a vector of the random indices used to generate the bootstrap predictions. Any further arguments can be passed to statistic through the ... argument.

The statistic argument to boot::boot is a function that has an index argument, so instead of passing mean <- function(x){n <- length(x); return(sum(x)/n)}, I must pass something more like mean_boot <- function(x, i){y <- x[i]; return(mean(y))}. Indeed, this works.

# Define a new function to calculate means within boot::boot
#
mean_boot <- function(x, i){
  
  y <- x[i] # Select values with index i
  return(mean(y))
}

# Try the bootstrap again
#
result <- boot::boot(x, mean_boot, R = B)

# Calculate confidence intervals
#
boot::boot.ci(result)
#
# Bootstap confidence intervals calculated here are:
#     Normal:     (-0.2758,  0.1167)
#     Basic:      (-0.2702,  0.1249)
#     Percentile: (-0.2948,  0.1003)
#     BCa:        (-0.2911,  0.1068)


# Inverting a t-test gives a 95% confidence interval of 
# (-0.2878159,  0.1179510). With all of the 95% bootstrap intervals being quite 
# close to ]than this, it seems that mean_boot is the correct syntax to 
# calculate the usual sample mean within boot::boot.
#
t.test(x)

Why should boot::boot be written this way, requiring the statistic function to use an index argument instead of just performing the resampling within boot::boot and passing the resampled data into whatever is passed as the statistic? Does this allow for easier syntax when the statistic being calculated is more complex than a sample mean, perhaps something from a GLM or machine learning model? Does it allow for easier parallelization of the bootstrapping when the complexity is great enough to require that (more than 100 observations, more than calculating a sample mean)? Something related to running a double bootstrap?

I have always found this to make work harder when I use the boot package. Are there situations where it makes work easier?

$\endgroup$
6
  • 2
    $\begingroup$ I would assume that this is hard to answer, unless someone comes by who was involved in the original coding of boot(). Stuff like this is very hard to get out of software once it has been released... $\endgroup$ Commented Jul 25 at 10:59
  • 1
    $\begingroup$ @StephanKolassa An answer from a boot developer would be appreciated, but I think someone who has been in a situation where this i argument made it easier to use the package could describe that for a totally acceptable answer. $\endgroup$
    – Dave
    Commented Jul 25 at 11:03
  • 2
    $\begingroup$ @StephanKolassa You have the same, "You're killin' me, developer," that I have every time you revisit boot::boot and forget the i in the statistic argument? $//$ I would be disappointed as a statistician but rather content as a human if all this question generates are comments along the lines of, "I hate this." $\endgroup$
    – Dave
    Commented Jul 25 at 11:27
  • 1
    $\begingroup$ People finding this question acceptable and relevant here should find themselves thinking that the same kind of question would be acceptable about any statistical software that they don't use. I use R occasionally and am not hostile to it, but I can't see why this is thought a good question and on-topic here. I have some expertise in other software but would vote to close any such question nearer home. $\endgroup$
    – Nick Cox
    Commented Jul 25 at 14:41
  • 1
    $\begingroup$ I agree with Nick; this is more about software design than anything. FWIW, I can see how the current design allows for more flexibility (at a small cost) than the alternative. Also recall that R doesn't exactly provide much type safety: boot has no way of knowing how it should subsample whatever object you provide it with. $\endgroup$
    – PBulls
    Commented Jul 25 at 16:24

1 Answer 1

6
$\begingroup$

I generally agree with you that it's more of an annoyance than anything, but here's a simple example:

Think about how you would do bootstrap for something like correlation between two vectors. All of these calls will fail to do what we want:

x <- rnorm(30)
y <- x + rnorm(30, 0, 0.1)

boot(x, cor, R=1000)
boot(x, y, cor, R=1000)
boot(cbind(x, y), cor, R=1000)

Instead, we need something like:

my_cor <- function(x, i){
   x <- x[i,]
   cor(x[,1], x[,2])
}
boot(cbind(x, y), my_cor, R=1000)

While there are other solutions, like coercing x to be a matrix and internally setting x_boot = x[i,], this may not be ideal in all cases. What if my data is structured so that I want to take samples across columns rather than rows? Or what if my data is organized as a list? This extra requirement gives us flexibility in the end (but is just annoying 99% of the time).

$\endgroup$
3
  • 3
    $\begingroup$ +1, though I would have said that a default behavior of resampling row-wise for a data frame and componentwise for a list would have made sense, so boot(cbind(x, y), cor, R=1000) should DWIW. But your point about possibly wanting to resample across columns makes sense. $\endgroup$ Commented Jul 25 at 11:37
  • 2
    $\begingroup$ @StephanKolassa I agree with you completely! One of my R packages contains code for the accelerated bootstrap, and that was my choice for the default settings. In the rare case where you need something more flexible, the software allows you to handle this with the "dynamic get" (dynGet()) function in R. $\endgroup$
    – knrumsey
    Commented Jul 25 at 11:41
  • 1
    $\begingroup$ I should have know to think of something bivariate! I’ve used boot to bootstrap regressions. $\endgroup$
    – Dave
    Commented Jul 25 at 16:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.