This tutorial provides a comprehensive introduction to descriptive statistics using the Python programming language. It covers essential concepts such as mean, median, mode, range, variance, standard deviation, quantiles, and interquartile range (IQR). By the end of this tutorial, you will be able to compute and interpret these fundamental statistical measures using real-world data in Python — ideal for beginners and those refreshing their skills.
💡 Note: While R is widely used in statistics, Python offers powerful, flexible tools for data analysis. This guide bridges the gap by translating R-based descriptive statistics into Python equivalents.
Introduction
Descriptive Statistics is the branch of statistics focused on summarizing and describing the main features of a dataset. It helps answer questions like: - What is the typical value? (Central Tendency) - How spread out are the values? (Dispersion) - Are there extreme or unusual values? (Outliers)
Key measures include: - Mean, Median, Mode → Central tendency - Range, Variance, Standard Deviation, IQR → Dispersion - Quantiles → Distribution shape
These metrics enable data analysts to make sense of raw numbers, detect patterns, and communicate insights effectively — without advanced modeling.
We’ll use the rice arsenic dataset (same as in the R tutorial) to demonstrate all concepts with real data.
Prerequisites
Install the required packages:
Code
import importlib.utilimport sys# List of required packagespackages = ['pandas', 'scipy']# Check and install missing packagesfor package in packages:ifnot importlib.util.find_spec(package):try:import pip pip.main(['install', package])exceptImportError:print(f"Failed to install {package}. Pip is not available.")# Import packagesimport pandas as pdimport numpy as npfrom scipy import stats
Code
# Verify package availabilityfor package in packages:print(f"{package} installed: {bool(importlib.util.find_spec(package))}")
Data
Code
# Load the dataset directly from GitHuburl ="https://github.com/zia207/Python_for_Beginners/raw/refs/heads/main/Data/rice_arsenic_data.csv"df = pd.read_csv(url)# Display first few rows to inspectprint(df.head())
Central Tendency
Central tendency is a statistical measure that assists in describing the center point of a set of data values. This concept is used to identify a single value that is considered most representative of the entire distribution. By determining the central tendency, we can gain insights into the typical or common values in a dataset. Mean, median, and mode are the three most commonly used measures of central tendency.
Mean
The mean is the arithmetic average: sum of all values divided by count.
\[ \bar{X} = \frac{\sum_{i=1}^{n} X_i}{n} \]
Code
# Overall mean of Grain Yield (GY)mean_gy = df['GY'].mean()print(f"Overall Mean Grain Yield: {mean_gy:.4f}")# Mean by soil treatment group (TREAT)mean_by_treat = df.groupby('TREAT')['GY'].mean()print("\nMean Grain Yield by Soil Treatment:")print(mean_by_treat)
Median
The median is the middle value when data is sorted. It is robust to outliers.
Odd n: Middle value
Even n: Average of two middle values
Code
# Overall median of GYmedian_gy = df['GY'].median()print(f"\nOverall Median Grain Yield: {median_gy:.4f}")# Median by treatment groupmedian_by_treat = df.groupby('TREAT')['GY'].median()print("\nMedian Grain Yield by Soil Treatment:")print(median_by_treat)
Mode
The mode is the most frequently occurring value. A dataset can have one mode (unimodal), multiple modes (bimodal, multimodal), or none.
Unlike R, Python doesn’t have a built-in mode() function for Series, but we can use scipy.stats.mode() or value_counts().
Code
# Using scipy.stats.mode (returns mode and count)mode_result = stats.mode(df['GAs'], keepdims=True)print(f"\nMode of Grain Arsenic (GAs): {mode_result.mode[0]:.4f} (appears {mode_result.count[0]} times)")# Alternative: Using pandas value_counts()mode_pandas = df['GAs'].value_counts().idxmax()mode_count = df['GAs'].value_counts().max()print(f"Mode (using value_counts()): {mode_pandas:.4f} (count: {mode_count})")# Check if multiple modes existvalue_counts = df['GAs'].value_counts()modes = value_counts[value_counts == value_counts.max()].index.tolist()iflen(modes) >1:print(f"Multiple modes detected: {modes}")else:print("Unimodal dataset.")
Range
The range is the difference between the maximum and minimum values. It gives a quick sense of spread.
\[ \text{Range} = \max(X) - \min(X) \]
Code
# Calculate range using min and maxrange_gy = df['GY'].max() - df['GY'].min()print(f"\nRange of Grain Yield: {range_gy:.4f}")# Alternative: using numpy's.ptp() ("peak to peak")range_gy_np = np.ptp(df['GY'])print(f"Range (using np.ptp()): {range_gy_np:.4f}")
Variance
Variance measures how far each number in the set is from the mean. Higher variance = more spread.
# Sample variance (default in pandas)var_gy = df['GY'].var()print(f"\nSample Variance of Grain Yield: {var_gy:.4f}")# Population variance (divide by n instead of n-1)pop_var_gy = df['GY'].var(ddof=0) # ddof=0 for populationprint(f"Population Variance: {pop_var_gy:.4f}")# Manual calculation (for verification)mean_gy_manual = df['GY'].mean()squared_diffs = (df['GY'] - mean_gy_manual)**2manual_var = squared_diffs.sum() / (len(df) -1) # sample varianceprint(f"Manual sample variance: {manual_var:.4f}")
Standard Deviation
Standard Deviation is the square root of variance. It’s expressed in the same units as the original data, making it more interpretable.
\[ s = \sqrt{s^2} \]
Code
# Sample standard deviationstd_gy = df['GY'].std()print(f"\nSample Standard Deviation of Grain Yield: {std_gy:.4f}")# Population standard deviationpop_std_gy = df['GY'].std(ddof=0)print(f"Population Standard Deviation: {pop_std_gy:.4f}")# Verify: sqrt(variance)import mathprint(f"Verification (sqrt(var)): {math.sqrt(var_gy):.4f}")
Quantiles
Quantiles divide data into equal parts. Common ones include quartiles (Q1, Q2, Q3), deciles, and percentiles.
Code
# Specific quantilesq25 = df['GY'].quantile(0.25)q50 = df['GY'].quantile(0.50) # same as medianq75 = df['GY'].quantile(0.75)print(f"\nQuantiles of Grain Yield:")print(f"25th percentile (Q1): {q25:.4f}")print(f"50th percentile (Median): {q50:.4f}")print(f"75th percentile (Q3): {q75:.4f}")# All quartiles at oncequartiles = df['GY'].quantile([0.25, 0.5, 0.75])print(f"\nAll Quartiles:\n{quartiles}")# Deciles (10% intervals)deciles = df['GY'].quantile(np.arange(0, 1.1, 0.1))print(f"\nDeciles (0% to 100% in 10% steps):\n{deciles}")
Interquartile Range (IQR)
The IQR is the range between the first (Q1) and third (Q3) quartiles. It’s a robust measure of spread unaffected by outliers.
\[ \text{IQR} = Q3 - Q1 \]
Code
# Method 1: Subtract quantiles manuallyiqr_value = q75 - q25print(f"\nInterquartile Range (IQR): {iqr_value:.4f}")# Method 2: Using scipy.stats.iqr (recommended)from scipy.stats import iqriqr_scipy = iqr(df['GY'])print(f"IQR (using scipy.stats.iqr): {iqr_scipy:.4f}")# IQR is often used to detect outliers:lower_bound = q25 -1.5* iqr_valueupper_bound = q75 +1.5* iqr_valueoutliers = df[(df['GY'] < lower_bound) | (df['GY'] > upper_bound)]print(f"\nNumber of potential outliers (using 1.5*IQR rule): {len(outliers)}")
In this tutorial, we’ve covered the core components of descriptive statistics in Python:
Concept
Python Function
Mean
df['col'].mean()
Median
df['col'].median()
Mode
stats.mode() or value_counts()
Range
max() - min() or np.ptp()
Variance
df['col'].var()
Standard Deviation
df['col'].std()
Quantiles
df['col'].quantile(0.x)
IQR
scipy.stats.iqr(df['col'])
Why this matters: Descriptive statistics are the foundation of exploratory data analysis (EDA). They help you: - Spot anomalies - Understand data distribution - Choose appropriate models - Communicate findings clearly
Always visualize alongside numerical summaries (e.g., histograms, boxplots) for full insight.
Resources
Here are excellent resources to deepen your understanding:
Python for Data Analysis by Wes McKinney – Creator of pandas. Chapter 3 covers descriptive stats and data aggregation.