Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Section 2.7 — Simulation and empirical distributions

This notebook contains the code examples from Section 2.7 Simulation and empirical distributions of the No Bullshit Guide to Statistics.

Notebook setup

Note: you may need to restart the kernel to use updated packages.
<Figure size 640x480 with 0 Axes>

Why simulate?

Running simulations of random variables is super useful:

  • Simplified probability calculations: no need for complicated integrals, since we can replace any calculation you might want to do with the distribution fXf_X with an equivalent calculation on the list of observations [x1,x2,…,xN][x_1,x_2,\ldots,x_N]

  • Visualize probability theory results: using simulations based on random observations [x1,x2,x3,…,xN][x_1, x_2, x_3, \ldots, x_N], we can visualize these theoretical results to gain hands-on intuition and experience with them.

  • Verify that statistics procedures work: we can use simulation to experimentally verify the performance of the statistical procedure. Does a procedure really work correctly 95% of the time? Simulate 1000 datasets and count the proportion of times when the procedure worked correctly.

In the remainder of this section, we’ll learn how to use the list of observations [x1,x2,…,xN][x_1,x_2,\ldots,x_N] as an alternative to the math calculations with the random variable XX, including visualizations and various probabilities.

Observations from random variables

Generating lists of random observations

array([9.004, 0.554, 6.825, 2.235, 2.226, 4.698, 2.503])

Visualizing probability distribution

<Figure size 700x200 with 1 Axes>

Calculate the mean and variance

(5.119377169180479, 5.0)
(25.12355642871208, 25.0)
(5.012340414288726, 5.0)

Calculate probabilities

0.247
0.2465969639416065
0.24659696394160646

Compute expectations

2.894485288770725
2.9866974938644915

Empirical distributions

Empirical probability mass function

<Figure size 500x120 with 1 Axes>

Generating bootstrap observations

Suppose we want to simulate new random observations form fEf_E, but for some reason we don’t have access to the method rvE.rvs anymore.

array([3.5, 7. , 6. , 7. , 1. ])
[True, True, True, True, True]
2

Empirical cumulative distribution function

Let’s write a function that compute the value empirical cumulative distribution (empirical CDF) of the sample data. The function takes two inputs: data the sample of observations, and b, the value where we want to evaluate the function.

Note the sorting step is not strictly necessary, but it guarantees the sdata is a NymPy array object which allows the comparison to work.

0.6
0.9
<Figure size 700x200 with 1 Axes>
<Figure size 500x200 with 1 Axes>

Inverse of the empirical cumulative distribution function

TODO: explain inverse empirical CDF

<Figure size 700x200 with 1 Axes>

Random variable generation from scratch

Uniform random variable primitive

0.25092362374494015
0.3745401188473625
<Figure size 700x200 with 1 Axes>

Continuous random variable generation

See the book or explanations of the the invers-CDF trick.

Generating exponentially distributed observations

Where lam is the λ\lambda (lambda) parameter chosen for exponential model family.

[1.8403766434262139, 3.663511132756237, 7.311508774831116, 7.784719180427214, 10.222768798178041]
<Figure size 700x200 with 1 Axes>
<Figure size 700x200 with 1 Axes>

We’ll develop various way to analyze this “goodness of fit” between the sample es data we generated and the theoretical model fEf_E in the remainder of this notebook.

Measuring data-model fit

Want to measure if es comes from Expon(λ=0.2)\textrm{Expon}(\lambda=0.2). Here are the first five values we simulated:

[1.8403766434262139, 3.663511132756237, 7.311508774831116, 7.784719180427214, 10.222768798178041]

Visual comparison between data and model distributions

Here is the code for visual comparison of es2 and the theoretical model rvE =Expon(λ=0.2)=\mathrm{Expon}(\lambda=0.2), based on the probability density and cumulative probability distributions.

Using Q-Q plots to compare quantiles

The quantile-quantile plot qq_plot(data, dist) is used to compare the positions of the quantiles of the dataset data against the quantiles of the theoretical distribution dist, which is an instance of one of the probability models in scipy.stats.

Exponential sample vs. true exponential model
<Figure size 700x200 with 1 Axes>

Another way to generate a Q-Q plot is to use the function qqplot defined in the the statsmodels package.

<Figure size 700x200 with 1 Axes>
Exponential data vs. standard normal
<Figure size 700x200 with 1 Axes>

Note the lowest quantiles don’t match (exponential data limited to zero), and the high quantiles don’t match either (exponential data has a long tail).

Normal data vs. standard normal

Here is an example of a Q-Q plot with very good fit.

<Figure size 700x200 with 1 Axes>
Student’s t-data vs. standard normal
<Figure size 700x200 with 1 Axes>
Bimodal data vs. standard normal (BONUS EXAMPLE)
<Figure size 700x200 with 1 Axes>

Comparing moments

A simple way to measure how well the data sample x=(x1,x2,…,xn)\mathbf{x} = (x_1, x_2, \ldots , x_n) fits the probability model fXf_X is to check if the data distribution and the probability distribution have the same moments.

In order to easily be able to calculate the moments of the data samples, we’ll convert es into a pandas series object, which has all the necessary methods.

(5.3330881197327535, 5.0)
(30.45191637582025, 25.0)

Let’s now compare the skew of dataset and the skew of the distribution:

(1.711852367068079, 2.0)

Finally, let’s compare the kurtosis of dataset and the kurtosis of the distribution.

(3.0431185936008607, 6.0)

Kolmogorov–Smirnov distance

Let’s jump right ahead and compute the KS distance for the data and distribution of interest.

0.048609282602185055
<Figure size 700x200 with 1 Axes>
<Figure size 800x500 with 2 Axes>

Discussion

Seeding random number generators

Every time you run the following two cells, you’ll get different numbers.

array([-1.29926108, 1.10740701, 1.66512925])
array([ 0.82613809, -0.93630083, -1.2185572 ])

We can set random seed for repeatability.

array([0.58487584, 1.23119574, 0.82190026])
array([0.58487584, 1.23119574, 0.82190026])