Statistics: Central Tendency. Mathematical and Python coding examples... | by Priyam Banerjee | Towards Data Science

In our quest to summarize data, either via data tables or visual effects, we wanted to represent the entirety of the data. However, often we have wished there was a single point that was representative of the data at hand. Using any extreme value in the data series would only explain one end of the series. So, it may be useful to use a central value with some observations larger or smaller than it. Such a measure is called central tendency. However, does a central point tell the entire story? You know some values are larger or smaller than the central tendency value, however it does not talk about the spread or heterogeneity in the data. This is what dispersion measures. The two concepts form a foundation for many advanced statistical concepts.
The aim of this article is to describe the different measures of central tendency and also introduce a bit of simple python coding to explain how to see them in practice. I’ll attempt to share some knowledge on dispersion in a follow up article.
Different measures of central tendency can be easily demonstrated by the below chart:
As evident from the image, central tendency can be classified into 3 classes — mean, median and mode. Before we get into the details of each, it is worth taking a look at the requisite properties of an ideal measure of central tendency as defined by Prof. Udny Yule:
The measure needs to be based on all observations in the data,
based on all observations,
affected as little as possible by sampling fluctuations
rigidly defined, easy to calculate, readily comprehensible and
there is possibility to do further mathematical work
With this in mind, let us take a look at different measures of Central Tendency below. Also, 2 noteworthy concepts to note here are population and sample. Population is the collection of all possible observations in a data set while sample is a subset drawn from the population using different techniques.
The mean is calculated by various mathematical operations on all observations in a data series:
Arithmetic Mean: One of the easiest to calculate measures, arithmetic mean is defined as the sum of all observations in a data series divided by the count of all observations in that series. Mathematically,
One interesting property of A.M. to note here is that if each observation in a series is increased or decreased by the same constant, the A.M. of the new series would also increase or decrease by the same constant.
Geometric mean: Geometric Mean is defined as the n-th root of the product of n observations in a data series. Mathematically,
If the calculations can be taken a bit further, it is very easy to see that usage of logarithms can make the calculations easier.
Harmonic Mean: Harmonic Mean is the reciprocal of the arithmetic mean of reciprocal of all observations in a data series. By the use of reciprocals, harmonic mean gives a higher weight to smaller magnitude observations and lower weight to the larger ones and hence is useful when there are a few extremely large or small observations in the data. Mathematically,
A useful relationship between the 3 types of mean is (look at the Python codes below for a demonstration):
An elaborate mathematical proof is available for this relationship which I’ll skip in the main article and provide separately later in a follow-up edition.
In a series of observations, median is defined as the middle most value. In a discrete series, if the count of observations is odd, then median is simply the (N+1)/2-th observation of the ordered series. However, if the discrete series has even number of observations then median is approximated as the arithmetic mean of the (N/2)-th and (N/2)+1-th observations of the ordered series.
For example, if there is a series of observations: 8, 12, 151, 7, 5, 10, 8, 9, 11. The ordered series (ascending) is: 5, 7, 8, 8, 9, 10, 11, 12, 151. This series has 9 observations hence the (9 + 1)/ 2 = 5th observation is the median, i.e., 9. Now, say we add another observation, say 6. The new ordered series then is: 5, 6, 7, 8, 8, 9, 10, 11, 12, 151. The series now has 10 observations which is even. So, the median is the arithmetic mean of (10/2)=5th and (10/2+1)=6th observations, i.e., (8 + 9)/2 = 8.5.
What if instead of an instead of an individual series like above, there are frequency distributions for discrete or continuous data?
We refer to frequency tables like figures 3a and 3b. The table in 3a is slightly easier so let us tackle that first. Here the total number of observations is 250, which is even. So, the median would be the N/2 = 125th observation. By looking at the cumulative frequency (Fig 4), the 125th observation can be determined. In table 3a, which lists discrete observations, we would expect the 125th observation to be 5. Why? There are 112 observations which are ≤ 4. The next 25 observations are valued 5. So, given 112 + 13 = 125, it means that 13th observation after the last 4 should be 5 and hence the median.
Next, let us look at 3b. Following the above approach, we can find that the median would lie in the class 46–55, but we do not know an exact point. Can that be found? The answer is yes. More often, answers from the data can be obtained by plotting it. A cumulative frequency for this example can be calculated (like Fig 4) for each limit of the weight classes and plotted in a chart:
Given there are 3,000 observations (N) in the data, the median would be the N/2 = 1,500th observation which lies between 135–144 lbs range (this is the median class). Based on the chart above (Fig 5), if you plot the point on the horizontal axis which corresponds to cumulative frequency = 1,500 on the vertical axis, you would find the weight to be approximately 137 lbs. Hence, the median weight is approximately 137 lbs. Such a curve is known as ogive. This can be generalized in a mathematical formula:
When the values are substituted, the median for this example comes out to be 136.96.
Mode is defined as the value which is most frequent in a series of observations. Considering a simple series like 9, 1, 10, 9, 3, 7, 4, 9, 5, 6 the mode is 9 as it occurs most frequently, i.e., 3 times. There can be multiple modes or no modes in a series. For example, 10, 11, 13, 11, 10, 15, 17, 12 has 2 modes, 10 and 11 whereas a series like 2, 6, 3, 2, 6, 3 has no mode as all the elements have exactly same frequency. Mode can be calculating by observing a data series. In case of a continuous distribution like the one in Fig 6, there is a mathematical formula that can be used.
The class 50–60 USD has the highest frequency and hence is the modal class. A mathematical formula to calculate the mode value in this case of continuous distributions with ranges is as follows:
Substituting the values in the above example, the mode for this data is 56.
We have looked at the ideal properties of central tendency earlier in the article. Let us compare the different measures of central tendency based on the properties and also look at some of their most common usages:
This section does not intend to introduce or teach Python programming language, but the below embedded codes will help users with even basic familiarity of Python to calculate central tendency from various data series.
# Import some required libraries
# All work done below has been done using Python 3.7.4
import numpy as np
from scipy import stats
import statistics
import pandas as pd

# Define your data for which the mean needs to be calculated
# Weight in lbs of 10 people
sample_data = np.array([115.3, 195.5, 120.5, 110.2, 90.4, 105.6, 110.9, 116.3, 122.3, 125.4])

## Let us calculate mean first

# Steps for Arithmetic mean:

calc_series_sum = sum(sample_data) #Sum of all the observations in the series
calc_series_n   = len(sample_data)      #Count of observations in the series
calc_arith_mean = round(calc_series_sum / calc_series_n,2) #Calculate mean = sum of all observations / count of observations
print("Arithmetic mean of height of "+str(calc_series_n)+" students in the sample data is: "+str(calc_arith_mean)) 
# Display the mean rounded to 2 decimal places

# What if we took a shorter route and calculated using an already available package?

calc_arith_mean_pkg = round(np.mean(sample_data),2) #Making use of "mean" function within numpy
print("Arithmetic mean (using np.mean) of height of "+str(calc_series_n)+" students in the sample data is: "
      +str(calc_arith_mean_pkg)) 
# Display the mean using np.mean rounded to 2 decimal places

#Do you see if the Arithmetic Mean is affected by outliers (like 195.5lbs) below?

Arithmetic mean of height of 10 students in the sample data is: 121.24
Arithmetic mean (using np.mean) of height of 10 students in the sample data is: 121.24

# Calculate Weighted Arithmetic mean

var_x = np.array([115.3, 195.5, 120.5, 110.2, 90.4, 105.6, 110.9, 116.3, 122.3, 125.4]) 
# Data series (same as weight of 10 people above)
freq_x = np.array([10,2,14,20,10,15,30,16,18,15]) # Frequency of each data point of series x

sumproduct = sum(var_x * freq_x) # Weighted sum of all observations

Weighted_AM = round(sumproduct / sum(freq_x),2) # Calculate weighted AM, rounded to 2 decimal places
print("Weighted Arithmetic mean of series x is: "+str(Weighted_AM))

Weighted Arithmetic mean of series x is: 114.62

# Calculate Geometric mean

var_geom_x = np.array([10,150,16,25,20,24,20,15])

product_x = (10 * 150 * 16 * 25 * 20 * 24 * 20 * 15) #Product of all observations
N_x = len(var_geom_x) #Count of observations

geomteric_mean_x = round(product_x ** (1/N_x),2) #Calculate geometric mean rounded to 2 decimal places
print("Geometric mean of series x is: "+str(geomteric_mean_x))

## The above way of calculating product may be cumbersome. Can we use logarithms?

product_log_x = sum(np.log(var_geom_x)) #Apply log on all observations and sum, equivalent to log of product of all observations
geomteric_mean_log_x = round(np.exp((1/N_x)*product_log_x),2) #Exponential to remove the logs
print("Geometric mean of series x using logarithms is: "+str(geomteric_mean_log_x))

Geometric mean of series x is: 23.28
Geometric mean of series x using logarithms is: 23.28

# Calculate GM if weights are provided
# Let us consider the var_geom_x in the earlier example and the following weights

wt_geom_x = np.array([20,2,14,10,15,30,6,15]) # Define weights

wtd_product_x = sum(np.log(var_geom_x)*wt_geom_x) # Calculate product
sum_weights = sum(wt_geom_x) # Sum of weights
geomteric_wtd_mean_log_x = round(np.exp((1/sum_weights)*wtd_product_x),2) # Weighted geometric mean rounded to 2 decimal places
print("Weighted GM of series x using logarithms is: "+str(geomteric_wtd_mean_log_x))

Weighted GM of series x using logarithms is: 18.36

# Calculate Harmonic mean

var_hm_x = np.array([10,150,16,25,20,24,20,15]) # Data series
N_hm_x = len(var_hm_x) # Count of observations

sum_reciprocal_hm_x = sum(1 / var_hm_x) # Sum of reciprocals
harmonic_mean_x = round(N_hm_x / sum_reciprocal_hm_x,2) # Reciprocal of arithmetic mean of reciprocals, i.e., harmonic mean
print("Harmonic Mean of series x: "+str(harmonic_mean_x)) # Harmonic mean rounded to 2 decimal places

# Weighted Harmonic Mean calculation should be easy to code after referring to examples from AM and GM

Harmonic Mean of series x: 19.16

# Can we verify the property of A.M. >= G.M. >= H.M. using the series: 10,150,16,25,20,24,20,15?

Series_x = np.array([10,150,16,25,20,24,20,15]) # Data series
N_series_x = len(Series_x) # Count of observations

AM_Series_x = round(sum(Series_x)/N_series_x,2) # Arithmetic Mean
GM_Series_x = round(np.exp(sum(np.log(Series_x))*(1/N_series_x)),2) # Geometric Mean
HM_Series_x = round((N_series_x / sum(1/Series_x)),2) # Harmonic Mean

print("Arithmetic Mean: "+str(AM_Series_x))
print("Geometric Mean : "+str(GM_Series_x))
print("Harmonic Mean  : "+str(HM_Series_x))

print("Hypothesis of A.M. >= G.M. >= H.M. is: "+ str((AM_Series_x >= GM_Series_x >= HM_Series_x)))

Arithmetic Mean: 35.0
Geometric Mean : 23.28
Harmonic Mean  : 19.16
Hypothesis of A.M. >= G.M. >= H.M. is: True

# Ask yourself, is H.M. the least affected by outliers then?
# Also, when do you think A.M. = G.M. = H.M.?

# Lastly, can we use packages like 'statistics' to calculate geometric and harmonic means? Let us check:
GM_Series_x_pkg = round(stats.gmean(Series_x),2) #Available in statistics from version 3.8 onwards
HM_Series_x_pkg = round(statistics.harmonic_mean(Series_x),2) #Available in statistics from version 3.6 onwards

print("Geometric Mean (using 'scipy.stats'): "+str(GM_Series_x_pkg))
print("Harmonic Mean  (using 'statistics'): "+str(HM_Series_x_pkg))

Geometric Mean (using 'scipy.stats'): 23.28
Harmonic Mean  (using 'statistics'): 19.16

# Calculate Median

# Consider the same Series_x as above

Sorted_Series_x = np.sort(Series_x) # Sort the series

print("Sorted series is: ", Sorted_Series_x)

# Number of observations is even. So the median would be the average of N/2-th and N/2+1-th value

N_by_2 = int(len(Sorted_Series_x)/2-1) # Why is the -1 necessary? Think wearing the Pythonista hat!
N_by_2_plus_1 = int(len(Sorted_Series_x)/2) # Why don't we add the 1? Wear the Pythonista hat again!

Val_1 = Sorted_Series_x[N_by_2] # Get value of the observation at N/2-th position
Val_2 = Sorted_Series_x[N_by_2_plus_1] # Get value of the observation at N/2+1-th position

print("Element at N/2-th position is: ",Val_1, " and at N/2+1-th position is: ",Val_2)

Median_val = (Val_1 + Val_2)/2

print("Median for series x is: ",Median_val)

Sorted series is:  [ 10  15  16  20  20  24  25 150]
Element at N/2-th position is:  20  and at N/2+1-th position is:  20
Median for series x is:  20.0

# Let us consider the median for a continuous series now
# We will import the weights_data.csv file that is available on the github repo

weights_data = pd.read_csv("weights_data.csv")
weights_data.head(10)

# Let us now create the predefined cuts of the data as provided in the article

weights_data['binned_weight'] = pd.cut(x=weights_data['Weight in Lbs'], bins=[104,119,127,135,144,152,162,170,182]) 

# Next create a grouped-frequency table
freq_table = weights_data['binned_weight'].value_counts().sort_index().rename_axis('Weight_Ranges').reset_index(name='Frequency')
freq_table['Cumulative_Frequency'] = freq_table['Frequency'].cumsum()
print(freq_table)

  Weight_Ranges  Frequency  Cumulative_Frequency
0    (104, 119]        305                   305
1    (119, 127]        552                   857
2    (127, 135]        540                  1397
3    (135, 144]        472                  1869
4    (144, 152]        377                  2246
5    (152, 162]        304                  2550
6    (162, 170]        250                  2800
7    (170, 182]        200                  3000

The above frequency table gives you the easy view of the median class, class prior to median class and respective cumulative frequencies. So, it becomes easier to apply the formula explained in the article:
We need to substitute the values in the formula:
Median_weight = round(135 + (((3000/2) - 1397)/472)*(144-135)) # Median rounded to 0 decimal places
print("Median weight from the data on 3,000 people is: ", Median_weight)

Median weight from the data on 3,000 people is:  137

# After the hard way, comes the easier method using "statistics" package:

Median_weight_scipy = round(statistics.median(weights_data['Weight in Lbs']))
print("Median weight from the data on 3,000 people (using statistics package) is: ",Median_weight_scipy)

Median weight from the data on 3,000 people (using statistics package) is:  137

# Notice that the rounded values are exactly same, but there will be differences by a few decimal places

# Let us consider a new series of data Y:

array_y = np.array([5, 8, 6, 8, 7, 8, 6, 5, 6, 5, 5, 10, 7, 7, 7, 10, 10, 7, 10])
Series_y = pd.Series(array_y)

freq_table=Series_y.value_counts().sort_index().rename_axis('Y_value').reset_index(name='Frequency')

print(freq_table)

# Find the mode from the frequency table:

Mode_Y = freq_table.loc[freq_table['Frequency']==max(freq_table['Frequency']),'Y_value'].iloc[0]
print("Mode of Y is: ", Mode_Y)

   Y_value  Frequency
0        5          4
1        6          3
2        7          5
3        8          3
4       10          4
Mode of Y is:  7

For grouped frequency, the same process as median calculation can be followed and replace the Median calculation formula with that of the Mode as explained in the article. In the below cell, look at a much simpler way of calculating mode using the statistics package:
# Mode using 'Statistics' package

Mode_Y_pkg = statistics.mode(Series_y) # Use mode function in statistics package
print("Mode of Y using statistics package is: ", Mode_Y_pkg)

Mode of Y using statistics package is:  7

This article introduced the foundational principles for calculating Central Tendency and also new age computational resources like Python that can make the task of calculating it much easier with huge volumes of data! Central Tendency serves as a foundation concept for all further advanced statistical work and needs to be understood well before working with advanced data distributions.
The next most important concept to understand would be measures of dispersion which I shall attempt to explain in my next article.
Read my next article describing measures of dispersion with mathematical formulae and Python coding here.