What You'll Build
In this hands-on exercise you will build a complete descriptive-statistics analyser for a season of cricket-merchandise sales data, transforming a raw list of daily transaction amounts into a polished statistical report. Your analyser will compute every measure from this module — central tendency, spread, shape, the five-number summary, and the correlation between two related variables — then automatically flag outliers and diagnose the distribution's shape. This mirrors exactly what a data analyst does on day one with any new dataset: before any modelling, you must understand what you are holding. By the end you will have a reusable Python tool that ingests numbers and emits a trustworthy, interpretation-ready summary, reinforcing why each statistic exists and when to trust it.
Prerequisites
- Python 3.10 or later installed, with comfort writing and running functions from the command line.
- NumPy and SciPy installed for numerical computation and shape statistics.
- Conceptual understanding of mean, median, mode, variance, standard deviation, and IQR from Lessons 1 and 2.
- Familiarity with skewness, kurtosis, and the five-number summary from Lessons 3 and 4.
- Basic understanding of covariance and Pearson correlation from Lesson 5.
Setup & Project Structure
You will create a small single-module project containing one Python file for the analyser and a short script that feeds it sample sales data. Keeping the analyser logic separate from the data-loading script is good practice because it lets you reuse the same statistical engine on any dataset later. Install the two dependencies into a virtual environment so your system Python stays clean, then verify the imports work before writing any analysis logic.
# Create and enter the project directory
mkdir cricket_sales_stats && cd cricket_sales_stats
# Create an isolated virtual environment
python -m venv venv
source venv/bin/activate # on Windows: venv\Scripts\activate
# Install dependencies
pip install numpy scipy
# Create the project files
touch analyser.py run_report.py
# Verify the install
python -c "import numpy, scipy; print('NumPy', numpy.__version__, '| SciPy ready')"
Step 1 — Foundation
Step 1 builds the central-tendency and spread foundation of the analyser. You will write a function that accepts a list of sales figures and returns the mean, median, mode, variance, standard deviation, and IQR. This is the foundation because every later diagnostic builds on these core numbers, and getting the sample-versus-population distinction right here (using ddof=1) ensures every downstream statistic generalises correctly to the broader sales population the data represents.
# analyser.py
import numpy as np
from scipy import stats
def central_and_spread(sales):
"""Compute core central-tendency and spread statistics for sales data."""
sales = np.asarray(sales, dtype=float)
q1, q3 = np.percentile(sales, [25, 75])
return {
"n": len(sales),
"mean": np.mean(sales),
"median": np.median(sales),
"mode": stats.mode(sales, keepdims=False).mode,
"variance": np.var(sales, ddof=1), # sample variance
"std_dev": np.std(sales, ddof=1), # sample standard deviation
"iqr": q3 - q1,
}
if __name__ == "__main__":
sample = [120, 95, 110, 130, 95, 105, 125, 95, 115, 100]
for k, v in central_and_spread(sample).items():
print(f"{k:>10}: {v}")
Step 2 — Core Logic
Step 2 adds the shape diagnostics and outlier detection that turn raw numbers into interpretation. You will write a function that computes skewness and excess kurtosis, derives the five-number summary, and applies the 1.5 times IQR fence rule to flag outliers automatically. This is the analytical core because it answers the questions that matter: is the sales distribution lopsided, does it have dangerous fat tails, and which days are genuine anomalies worth investigating?
# analyser.py (continued)
def shape_and_outliers(sales):
"""Compute distribution shape and flag outliers via the IQR fence rule."""
sales = np.asarray(sales, dtype=float)
q1, q3 = np.percentile(sales, [25, 75])
iqr = q3 - q1
lower_fence = q1 - 1.5 * iqr
upper_fence = q3 + 1.5 * iqr
outliers = sales[(sales < lower_fence) | (sales > upper_fence)]
skew = stats.skew(sales)
shape = ("right-skewed" if skew > 0.5 else
"left-skewed" if skew < -0.5 else "roughly symmetric")
return {
"five_number": {
"min": float(np.min(sales)), "q1": float(q1),
"median": float(np.median(sales)), "q3": float(q3),
"max": float(np.max(sales)),
},
"skewness": skew,
"excess_kurtosis": stats.kurtosis(sales),
"shape_verdict": shape,
"outliers": outliers.tolist(),
}
Step 3 — Integration & Enhancement
Step 3 brings everything together into a single report function and adds the correlation analysis from Lesson 5. You will write a master function that combines the foundation and shape results into one dictionary, then add a separate function that computes the Pearson correlation between two related series — for example, daily sales versus the number of matches played that day — so the analyser can also surface relationships, not just describe single variables.
# analyser.py (continued)
def full_report(sales):
"""Combine all descriptive statistics into one report."""
report = central_and_spread(sales)
report.update(shape_and_outliers(sales))
# Mean-median gap as a free skewness cross-check
report["mean_median_gap"] = report["mean"] - report["median"]
return report
def relationship(series_a, series_b):
"""Pearson correlation between two related series."""
r, p_value = stats.pearsonr(series_a, series_b)
strength = ("strong" if abs(r) > 0.7 else
"moderate" if abs(r) > 0.4 else "weak")
return {"pearson_r": r, "p_value": p_value, "strength": strength}
Step 4 — Testing & Verification
Now you will run the complete analyser on a realistic sales dataset and verify the output makes sense. Feed it merchandise sales with one deliberate spike (a match-day surge) and confirm the tool flags that day as an outlier, reports right skew, and shows a strong positive correlation between sales and matches played. Check that the mean exceeds the median, confirming the skew diagnosis independently.
# run_report.py
from analyser import full_report, relationship
# Daily merchandise sales (thousands of rupees); one match-day spike
daily_sales = [42, 38, 45, 40, 39, 44, 41, 210, 43, 37, 46, 40]
matches_today = [0, 0, 1, 0, 0, 1, 0, 3, 1, 0, 1, 0]
report = full_report(daily_sales)
print("=== DESCRIPTIVE REPORT ===")
for k, v in report.items():
print(f"{k:>16}: {v}")
print("\n=== RELATIONSHIP ===")
print(relationship(daily_sales, matches_today))
# Expected (approximately):
# mean: ~54.1 (pulled up by the 210 spike)
# median: ~41.5 (robust to the spike)
# shape_verdict: right-skewed
# outliers: [210.0]
# mean_median_gap: ~12.6 (positive -> confirms right skew)
# strength: strong (sales rise with matches played)
Warning: A common error is forgetting ddof=1 in the variance and standard deviation calls, or mixing NumPy (which defaults to ddof=0) with pandas (which defaults to ddof=1) in the same pipeline. This silently underestimates spread and produces inconsistent numbers between functions. Always set ddof explicitly so your sample statistics correctly generalise to the broader population the data represents.
Extension Challenge: Extend the analyser to accept a pandas DataFrame and produce a per-column report automatically, then add a coefficient-of-variation field so columns measured on different scales can be compared for relative volatility. As a stretch goal, add a log-transform option that re-runs the shape diagnostics on log(sales) and reports whether the transform successfully reduced the skewness toward zero.
- Descriptive analysis always precedes modelling: you must understand a dataset's centre, spread, shape, and outliers first.
- Using ddof=1 for sample variance and standard deviation ensures statistics generalise to the broader population.
- The 1.5 times IQR fence rule flags outliers robustly without being inflated by the very extremes it detects.
- A positive mean-minus-median gap independently confirms right skew, serving as a free cross-check on the skewness statistic.
- Pearson correlation surfaces relationships between variables, extending the analysis beyond single-variable description.
- Separating the statistical engine from the data-loading script makes the analyser reusable across any future dataset.