Skip to main content

Exercises 8.9 Exercises

1. Exercise 8.1.

One of the strengths of resampling methods is that they are easy to extend to other statistics. In this chapter, we computed the sample mean of penguin weights and then used resampling to approximate the sampling distribution of the mean. Now let’s do the same for standard deviation.
Compute the sample standard deviation of weights for chinstrap penguins. Then use resample to approximate the sampling distribution of the standard deviation. Use the sampling distribution to compute the standard error of the estimate and a 90% confidence interval.

2. Exercise 8.2.

The Behavioral Risk Factor Surveillance System (BRFSS) dataset includes self-reported heights and weights for a sample of adults in the United States. Use this data to estimate the average height of male adults. Use resample to approximate the sampling distribution and compute a 90% confidence interval.
Because the sample size is very large, the confidence interval is very small, which means that variability due to random sampling is small. But other sources of error might be bigger -- what other sources of error do you think affect the results?
The following cells download the data, read it into a DataFrame, and select the heights of male respondents.
Listing 8.9.1. Python Code
$ download("https://github.com/AllenDowney/ThinkStats/raw/v3/data/CDBRFS08.ASC.gz")
Downloaded CDBRFS08.ASC.gz
Listing 8.9.2. Python Code
from thinkstats import read_brfss

brfss = read_brfss()
Listing 8.9.3. Python Code
$ male = brfss.query("sex == 1")
heights = male["htm3"]
heights.describe()
count    154407.000000
mean        178.066221
std           7.723563
min          61.000000
25%         173.000000
50%         178.000000
75%         183.000000
max         236.000000
Name: htm3, dtype: float64

3. Exercise 8.3.

In games like soccer and hockey, the time between goals tends to follow an exponential distribution (as we saw in Chapter 6). Suppose we observe a sample of times between goals. If we assume that the sample came from an exponential distribution, how can we estimate the actual mean of the distribution? We might consider using either the sample mean or the sample median. Let’s see if either of them is a consistent, unbiased estimator. For the experiments, we’ll assume that the actual mean time between goals is 10 minutes.
Listing 8.9.4. Python Code
actual_mean = 10
The following function generates a sample from an exponential distribution with this mean and the given sample size.
Listing 8.9.5. Python Code
def make_exponential(n):
    return np.random.exponential(actual_mean, size=n)
Use this function to generate samples with a range of sizes and compute the mean of each one. As n increases, do the sample means converge to the actual mean?
Next, generate samples with a range of sizes and compute the median of each one. Do the sample medians converge to the actual median?
Here’s the actual median of an exponential distribution with the given mean.
Listing 8.9.6. Python Code
$ actual_median = np.log(2) * actual_mean
actual_median
np.float64(6.931471805599453)
Next, generate many samples with size 10 and check whether the sample mean is an unbiased estimator of the population mean.
Finally, check whether the sample median is an unbiased estimator of the population median.

4. Exercise 8.4.

In this chapter we tested a biased estimator of variance and showed that it is, in fact, biased. And we showed that the unbiased estimator is unbiased. Now let’s try standard deviation.
To estimate the standard deviation of a population, we can compute the square root of the biased or unbiased estimator of variance, like this:
Listing 8.9.7. Python Code
def biased_std(sample):
    # Square root of the biased estimator of variance
    var = biased_var(sample)
    return np.sqrt(var)
Listing 8.9.8. Python Code
def unbiased_std(sample):
    # Square root of the unbiased estimator of variance
    var = unbiased_var(sample)
    return np.sqrt(var)
Use make_sample to compute many samples of size 10 from a normal distribution with mean 3.7 and standard deviation 0.46. Check whether either of these is an unbiased estimator of standard deviation.
Listing 8.9.9. Python Code
$ # Here's an example using `make_sample`

mu, sigma = 3.7, 0.46
make_sample(n=10)
array([4.5279695 , 3.75698359, 4.09347143, 3.56308034, 3.17123233,
       4.40734952, 3.70858308, 4.15706704, 4.06716703, 3.7203591 ])

5. Exercise 8.5.

This exercise is based on the German tank problem, which is a simplified version of an actual analysis performed by the Economic Warfare Division of the American Embassy in London during World War II.
Suppose you are an Allied spy and your job is to estimate how many tanks the Germans have built. As data, you have serial numbers recovered from k captured tanks.
If we assume that the Germans have N tanks numbered from 1 to N, and that all tanks in this range were equally likely to be captured, we can estimate N like this:
Listing 8.9.10. Python Code
def estimate_tanks(sample):
    m = np.max(sample)
    k = len(sample)
    return m + (m - k) / k
As an example, suppose N is 122.
Listing 8.9.11. Python Code
N = 122
tanks = np.arange(1, N + 1)
We can use the following function to generate a random sample of k tanks.
Listing 8.9.12. Python Code
def sample_tanks(k):
    return np.random.choice(tanks, replace=False, size=k)
Here’s an example.
Listing 8.9.13. Python Code
np.random.seed(17)
Listing 8.9.14. Python Code
$ sample = sample_tanks(5)
sample
array([74, 71, 95, 10, 17])
And here is the estimate based on this sample.
Listing 8.9.15. Python Code
$ estimate_tanks(sample)
np.float64(113.0)
Check whether this estimator is biased.
For more on this problem, see this Wikipedia page and Ruggles and Brodie, "An Empirical Approach to Economic Intelligence in World War II", Journal of the American Statistical Association, March 1947, available here.
For an explanation of how this estimator works, you might like this video.

6. Exercise 8.6.

In several sports -- especially basketball -- many players and fans believe in a phenomenon called the "hot hand", which implies that a player who has hit several consecutive shots is more likely to hit the next, and a player who has missed several times is more likely to miss.
A famous paper proposed a way to test whether the hot hand is real or an illusion, by looking at sequences of hits and misses from professional basketball games. For each player, the authors computed the overall probability of making a shot, and the conditional probability of making a shot after three consecutive hits. For eight out of nine players, they found that the probability of making a shot was lower after three hits. Based on this and other results, they concluded that there is "no evidence for a positive correlation between the outcomes of successive shots". And for several decades, many people believed that the hot hand had been debunked.
However, this conclusion is based on a statistical error, at least in part. A 2018 paper showed that the statistic used in the first paper -- the probability of making a shot after three hits -- is biased. Even if the probability of making every shot is exactly 0.5, and there is actually no correlation between the outcomes, the probability of making a shot after three hits is less than 0.5.
It is not obvious why that’s true, which is why the error went undetected for so long, and I won’t try to explain it here. But we can use the methods from this chapter to check it. We’ll use the following function to generate a sequence of 0s and 1s with probability 0.5 and no correlation.
Listing 8.9.16. Python Code
def make_hits_and_misses(n):
    # Generate a random sequence of 0s and 1s
    return np.random.choice([0, 1], size=n)
In the notebook for this chapter, I provide a function that finds all subsequences of three hits (1s) and returns the element of the sequence that follows.
Listing 8.9.17. Python Code
import numpy as np


def get_successors(seq, target_sum=3):
    """Get the successors of each subsequence that sums to a target value.

    Parameters:
    seq (array-like): Sequence of 1s and 0s.
    target_sum (int): The target sum of the subsequence. Default is 3.

    Returns:
    np.ndarray: Array of successors to subsequences that sum to `target_sum`.
    """
    # Check if the input sequence is too short
    if len(seq) < 3:
        return np.array([])

    # Compute the sum of each subsequence of length 3
    kernel = [1, 1, 1]
    corr = np.correlate(seq, kernel, mode="valid")

    # Find the indices where the subsequence sums to the target value
    indices = np.nonzero(corr == target_sum)[0]

    # Remove cases where the subsequence is at the end of the sequence
    indices = indices[indices < len(seq) - 3]

    # Find the successors of each valid subsequence
    successors = seq[indices + 3] if len(indices) > 0 else np.array([])

    return successors
Generate a large number of sequences with length 100 and for each sequence, find each shot that follows three hits. Compute the percentage of these shots that are hits. Hint: if the sequence does not contain three consecutive hits, the function returns an empty sequence, so your code will have to handle that.
If you run this simulation many times, what is the average percentage of hits? How does this result vary as you increase or decrease the length of the sequence?
The famous paper is Gilovich, T., Vallone, R., & Tversky, A. (1985). The hot hand in basketball: On the misperception of random sequences. Cognitive psychology, 17(3), 295-314.
The paper showing the statistical error is Miller, J. B., & Sanjurjo, A. (2018). Surprised by the hot hand fallacy? A truth in the law of small numbers. Econometrica, 86(6), 2019-2047.
The first paper is available here. The second is available here. For an overview of the topic and an explanation of the error, you might like this video.