Skip to main content

Section 14.7 Applying the CLT

To see why the Central Limit Theorem is useful, let’s get back to the example in Chapter 9: testing the apparent difference in mean pregnancy length for first babies and others. We’ll use the NSFG data again—instructions for downloading it are in the notebook for this chapter.
The following cell downloads the data.
Listing 14.7.1. Python Code
download("https://github.com/AllenDowney/ThinkStats/raw/v3/nb/nsfg.py")
download("https://github.com/AllenDowney/ThinkStats/raw/v3/data/2002FemPreg.dct")
download("https://github.com/AllenDowney/ThinkStats/raw/v3/data/2002FemPreg.dat.gz")
We’ll use get_nsfg_groups to read the data and divide it into first babies and others.
Listing 14.7.2. Python Code
$ from nsfg import get_nsfg_groups

live, firsts, others = get_nsfg_groups()
/home/downey/ThinkStats/soln/nsfg.py:220: PerformanceWarning: DataFrame is highly fragmented.  This is usually the result of calling `frame.insert` many times, which has poor performance.  Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`
  df["totalwgt_lb"] = df.birthwgt_lb + df.birthwgt_oz / 16.0
As we’ve seen, first babies are born a little later, on average—the apparent difference is about 0.078 weeks.
Listing 14.7.3. Python Code
$ delta = firsts["prglngth"].mean() - others["prglngth"].mean()
delta
np.float64(0.07803726677754952)
To see whether this difference might have happened by chance, we’ll assume as a null hypothesis that the mean and variance of pregnancy lengths is actually the same for both groups, so we can estimate it using all live births.
Listing 14.7.4. Python Code
all_lengths = live["prglngth"]
m, s2 = all_lengths.mean(), all_lengths.var()
The distribution of pregnancy lengths does not follow a normal distribution— nevertheless, we can use a normal distribution to approximate the sampling distribution of the mean.
The following function takes a sequence of values and returns a Normal object that represents the sampling distribution of the mean of a sample with the given size, n, drawn from a normal distribution with the same mean and variance as the data.
Listing 14.7.5. Python Code
def sampling_dist_mean(data, n):
    mean, var = data.mean(), data.var()
    dist = Normal(mean, var)
    return dist.sum(n) / n
Here’s a normal approximation to the sampling distribution of mean weight for first births, under the null hypothesis.
Listing 14.7.6. Python Code
$ n1 = firsts["totalwgt_lb"].count()
dist_firsts = sampling_dist_mean(all_lengths, n1)
n1
np.int64(4363)
And here’s the sampling distribution for other babies.
Listing 14.7.7. Python Code
$ n2 = others["totalwgt_lb"].count()
dist_others = sampling_dist_mean(all_lengths, n2)
n2
np.int64(4675)
We can compute the sampling distribution of the difference like this.
Listing 14.7.8. Python Code
$ dist_diff = dist_firsts - dist_others
dist_diff
Normal(0.0, 0.003235837567930557)
The mean is 0, which makes sense because if we draw two samples from the same distribution, we expect the difference in means to be 0, on average. The variance of the sampling distribution is 0.0032, which indicates how much variation we expect in the difference due to chance.
To confirm that this distribution approximates the sampling distribution, we can also estimate it by resampling.
Listing 14.7.9. Python Code
sample_firsts = [np.random.choice(all_lengths, n1).mean() for i in range(1001)]
sample_others = [np.random.choice(all_lengths, n2).mean() for i in range(1001)]
sample_diffs = np.subtract(sample_firsts, sample_others)
Here’s the empirical CDF of the resampled differences compared to the normal model. The vertical dotted lines show the observed difference, positive and negative.
Listing 14.7.10. Python Code
dist_diff.plot_cdf(**model_options)
Cdf.from_seq(sample_diffs).plot(label="sample")
plt.axvline(delta, ls=":")
plt.axvline(-delta, ls=":")

decorate(xlabel="Difference in pregnancy length", ylabel="CDF")
Figure 14.7.11.
In this example, the sample sizes are large and the skewness of the measurements is modest, so the sampling distribution is well approximated by a normal distribution. Therefore, we can use the normal CDF to compute a p-value. The following method computes the CDF of a normal distribution.
Listing 14.7.12. Python Code
%%add_method_to Normal


def cdf(self, xs):
    sigma = np.sqrt(self.sigma2)
    return norm.cdf(xs, self.mu, sigma)
Here’s the probability of a difference as large as delta under the null hypothesis, which is the area under the right tail of the sampling distribution.
Listing 14.7.13. Python Code
$ right = 1 - dist_diff.cdf(delta)
right
np.float64(0.08505405315526993)
And here’s the probability of a difference as negative as -delta, which is the area under the left tail.
Listing 14.7.14. Python Code
$ left = dist_diff.cdf(-delta)
left
np.float64(0.08505405315526993)
left and right are the same because the normal distribution is symmetric. The sum of the two is the probability of a difference as large as delta, positive or negative.
Listing 14.7.15. Python Code
$ left + right
np.float64(5.722951275096036e-11)
The resulting p-value is 0.170, which is consistent with the estimate we computed by resampling in Chapter 9.
The way we computed this p-value is similar to an independent sample \(t\) test. SciPy provides a function called ttest_ind that takes two samples and computes a p-value for the difference in their means.
Listing 14.7.16. Python Code
$ from scipy.stats import ttest_ind

result = ttest_ind(firsts["prglngth"], others["prglngth"])
result.pvalue
np.float64(0.16755412639414996)
When the sample sizes are large, the result of the \(t\) test is close to what we computed with normal distributions. The \(t\) test is so called because it is based on a \(t\) distribution rather than a normal distribution. The \(t\) distribution is also useful for testing whether a correlation is statistically significant, as we’ll see in the next section.