Two very important theorems in statistics are the Law of Large Numbers and the Central Limit Theorem.

#### The Law of Large Numbers

The Law of Large Numbers is very simple: as the number of identically distributed, randomly generated variables increases, their sample mean (average) approaches their theoretical mean.

The Law of Large Numbers can be simulated in Python pretty easily:

 1 results = []
2 for num_throws in range(1,10000):
3     throws = np.random.randint(low=1,high=7, size=num_throws)
4     mean_of_throws = throws.mean()
5     results.append(mean_of_throws)
6
7 df = pd.DataFrame({ 'throws' : results})
8
9 from IPython.core.pylabtools import figsize
10 from matplotlib import pyplot as plt
11 figsize(11, 9)
12 df.plot(title='Law of Large Numbers Explained Shown Graphically',color='r')
13 plt.xlabel("Number of throws in sample")
14 plt.ylabel("Average Of Sample")

In this example, I am simulating throw a six-sided fair dice. The expected value is 3.5 and as you can see from the graph, when the same size is small, the mean is not close to 3.5, but as the sample size increases, the mean approaches 3.5.

#### The Central Limit Theorm

The Central Limit Theorem, in probability theory, a theorem that establishes the normal distribution as the distribution to which the mean (average) of almost any set of independent and randomly generated variables rapidly converges. The central limit theorem explains why the normal distribution arises so commonly and why it is generally an excellent approximation for the mean of a collection of data (often with as few as 10 variables).

 1 samples_all = []
2 samples_all_exp = []
3 samples_all_possion = []
4 samples_all_geometric = []
5 mu = .9
6 lam = 1.0
7 size=1000
8
9 for number_in_sample in range(1,20000):
10     samples = np.random.binomial(1, mu, size=size)
11     samples_all.append(samples.mean())
12     samples = np.random.exponential(scale=2.0,size=size)
13     samples_all_exp.append(samples.mean())
14     samples = np.random.geometric(p=.5, size=size)
15     samples_all_geometric.append(samples.mean())
16     samples = np.random.poisson (lam=lam, size=size)
17     samples_all_possion.append(samples.mean())
18
19 df = pd.DataFrame({ 'binomial' : samples_all,
20                      'poission' : samples_all_possion,
21                      'geometric' : samples_all_geometric,
22                     'exponential' : samples_all_exp})
23
24 from IPython.core.pylabtools import figsize
25 from matplotlib import pyplot as plt
26 figsize(11, 6)
27 fig, axes = plt.subplots(nrows=2, ncols=2)
28 df.binomial.hist(color='blue',ax=axes[0,0], alpha=0.9, bins=1000)
29 df.exponential.hist(ax=axes[0,1],color='r',bins=1000)
30 df.poission.hist(ax=axes[1,0],color='r',bins=1000)
31 df.geometric.hist(ax=axes[1,1],color='r',bins=1000)
32
33 axes[0,0].set_title('Binomial')
34 axes[0,1].set_title('Poisson')
35 axes[1,0].set_title('Geometric')
36 axes[1,1].set_title('Exponential')

In the example above, I am creating random samples from four different probability distributions: Normal, Poission, Exponential, and Geometric. After the sample has been created, I take the mean of it and cache it. You can see that when I plot histograms of these means, they all follow the normal distribution, which is pretty amazing, because only 1/4 distributions is normal.