# Central Limit Theorem: An Intuitive Walk-Through

## A Data Driven Understanding

One of the most beautiful concepts in statistics and probability is *Central Limit Theorem*,people often face difficulties in getting a clear understanding of this and the related concepts, I myself struggled understanding this during my college days (eventually mugged up the definitions and formulas to pass the exam). In its core it is a very simple yet elegant theorem that enables us to estimate the population mean. Here I will try to explain these concepts using this toy dataset on customer demographics available on Kaggle (this is a fictional dataset created for educational purposes). Without wasting much time lets dive in and try to understand what CLT is

# CENTRAL LIMIT THEOREM

Here is what Central Limit Theorem states

If you take sufficiently large samples from a distribution,then the mean of these samples would follow approximately normal distribution with mean of distribution approximately equal to population mean and standard deviation equal on 1/√n times the population standard deviation (here n is number of elements in a sample)

Now comes the fun part, in order to have a better understanding and appreciation of the above statement, let us take our toy dateset (will take the annual income column for our analysis) and try to check if these approximations actually holds true.

We will try to estimate the mean income of a population, first let us have a look at the distribution and size the population

`df = pd.read_csv(r'toy_dataset.csv')`

print("Number of samples in our data: ",df.shape[0])

sns.kdeplot(df['Income'],shade=True)

Well, we can fairly say this is isn’t exactly a normal distribution and the original ** population mean** and

**is**

*standard deviation***and**

*91252.798***respectively. Now have a good look on to these numbers and let’s see if we could use Central Limit Theorem to approximate these values**

*24989.501*# GENERATING RANDOM SAMPLES

Now let us try to generate random samples from the population and try to plot sample mean distributions

`def return_mean_of_samples(total_samples,element_in_each_sample):`

sample_with_n_elements_m_size = []

for i in range(total_samples):

sample = df.sample(element_in_each_sample).mean()['Income']

sample_with_n_elements_m_size.append(sample)

return (sample_with_n_elements_m_size)

We will use this function to generate random sample means and later use it to calculate sampling distributions

Here we are taking 200 samples with 100 elements in each samples and see how the sample mean distribution looks like

`sample_means = return_mean_of_samples(200,100)`

sns.kdeplot(sample_means,shade=True)

print("Total Samples: ",200)

print("Total elements in each sample: ",100)

Well that looks pretty normal, so now we can assume that with sufficient sample size, sample means do follow normal distribution irrespective ofthe original distributions

Now comes the second part, let us try to see if we could estimate the population mean from this sampling distribution, below is a piece of code that generates different sampling distributions by varying total sample size and elements in each samples

total_samples_list = [100,500]

elements_in_each_sample_list = [50,100,500]

mean_list = []

std_list = []

key_list = []

estimate_std_list = []

key=''

pop_mean = [population_mean]*6

pop_std = [population_std]*6

for tot in total_samples_list:

for ele in elements_in_each_sample_list:

key = '{}_samples_with_{}_elements_each'.format(tot,ele)

key_list.append(key)

mean_list.append(np.round(np.mean(return_mean_of_samples(tot,ele)),3))

std_list.append(np.round(np.array(return_mean_of_samples(tot,ele)).std(),3))

estimate_std_list.append(np.round(population_std/(np.sqrt(ele)),3))pd.DataFrame(zip(key_list,pop_mean,mean_list,pop_std,estimate_std_list,std_list),columns=['Sample_Description','Population_Mean','Sample_Mean','Population_Standard_Deviation',"Pop_Std_Dev/"+u"\u221A"+"sample_size",'Sample_Standard_Deviation'])

- Look at second and third columns, we can clearly see the mean of sampling distribution is very close to the population mean in all the distributions
- Have a look at the last two columns, initially there is some difference in the deviations but as the sample size increases this difference becomes negligible

Let us further plot these sampling distributions and population mean and see how the plots look

def plot_distribution(sample,population_mean,i,j,color,sampling_dist_type):

sns.kdeplot(np.array(sample),color = color,ax = axs[i,j],shade=True)

axs[i, j].axvline(population_mean, linestyle="-", color='r', label="p_mean")

axs[i, j].axvline(np.array(sample).mean(), linestyle="-.", color='b', label="s_mean")

axs[i, j].set_title(key)

axs[i, j].legend()colors = ['r','g','b','y', 'c', 'm', 'k']

plt_grid = [(0,0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

sample_sizes = [(100,50), (100, 100), (100, 500), (500, 50), (500, 100), (500, 500)]total_samples_list = [100,500]

elements_in_each_sample_list = [50,100,500]fig, axs = plt.subplots(3, 2, figsize=(10, 9))

i = 0

for tot in total_samples_list:

for ele in elements_in_each_sample_list:

key = '{}_samples_with_{}_elements_each'.format(tot,ele)

plot_distribution(return_mean_of_samples(tot,ele), population_mean , plt_grid[i][0], plt_grid[i][1] , colors[i], key)

i = i + 1

plt.show()

As you can see the mean of sampling distribution is pretty close to the population mean. (*Here is a food for thought, have a look at first and last plots do you notice there is a difference in spread of data, first one is more spread around population mean as compared to last, look at the scale on x axis for better clarity, well once your reach the end of this blog try to answer this question yourself*)

# CONFIDENCE INTERVAL

A confidence interval can be defined as an entire interval of plausible values of a population parameter, such as mean based on observations obtained from a random sample of size n.

Let’s summarize our leanings so far and and try to understand Confidence Intervals from it.

- Sampling distribution of mean of samples follow a normal distribution
- Hence using property of normal distribution
*95% of sample means lie within two standard deviations*of population mean - We can rephrase the sentence and say that
*95% of these Intervals or rather Confidence Intervals (two standard deviations away from the mean on either side) contains the population mean*

Here I would like to give more focus on the last point we talked about. People often get confused and say once we take a random sample and calculate its mean and corresponding 95% CI, there is a 95% chance that the population mean lies within 2 standard deviations of this sample mean, this statement is wrong

When we talk about a probability estimate w.r.t to a sample then that sample gets fixed here (*including the corresponding sample mean and confidence interval*), also population mean eventually is a fixed value, hence there is no point in saying there is a 95% probability of a fixed point (population mean) lying in a fixed interval (CI of sample used), it would either exist there or not, instead the more proper definition of 95% CI is

If random samples are taken and corresponding sample means and CI’s (two standard deviations from the mean on either side) are calculated, then 95% of these CI’s would contain the population mean. For example let’s say we take 100 random samples and calculate their CI’s then 95% of these CI’s would contain the population mean

Enough of the talking business, now as always lets take our toy dataset and see if this holds true.

`def get_CI_percent(size):`

counter = 0

for i in range(size):

is_contains = False

sample_mean = df.sample(50)['Income'].mean()

lower_lim = sample_mean - 2*standard_error

upper_lim = sample_mean + 2*standard_error

if (population_mean>=lower_lim)&(population_mean<=upper_lim):

is_contains = True

counter = counter + 1

return np.round(counter/size*100,2)

I took 20 random samples with 50 elements in each sample and calculated their sample means and respective two standard deviation intervals (95% CI’s), 18 out of 20 intervals contained these intervals. (actually this interval count varied between 18–20)

Instead of giving a point estimate (taking a sample and calculating its mean), it is more plausible to give an interval estimate (this helps us include any error that might occur due to sampling) hence we calculate these confidence intervals along with the sample mean.

# CONCLUSION

One question that some of you might be having, why go through so much hard work of taking random samples, calculating its sample mean hence the Confidence Interval later, why not simply do np.mean() like it was done in the very first line of code here.

Well if you have your data completely available in digital form and you could calculate your population metric just from a single line of code within milliseconds then definitely go for it there is no point in going through the whole process

However there are many scenarios where data collection itself is a big challenge, suppose we want to estimate mean height of people in Bangalore then it is practically impossible to go to every person and record the data. This is where these concepts of sampling, Confidence Intervals are really useful.

Here is the link to the code file and the dataset. For those of you who are new to these topics, I would strongly recommend to try running the code yourself, play around with the dataset (there are other fields like age) to get a better understanding.