100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Statistics & Probability for Data Science
55 minintermediate

Practice — Descriptive Stats on Sales Data

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.

Analogy🏏Cricket
🏏 Think of it like cricket: Building a Monte Carlo simulator is like a team running thousands of practice scenarios in the nets to estimate how often a tactic works, rather than relying on a coach's theoretical hunch. Just as repeatedly playing out the scenario and counting successes reveals the true odds far more reliably than armchair reasoning, simulating thousands of random matches reveals probabilities the maths might struggle to express. Just as more net sessions give a sharper estimate, more simulated trials give a more accurate probability. The insight is that when a probability is hard to derive, you can let randomness itself compute the answer by playing the scenario out enough times to see the pattern emerge.

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.

Analogy🏏Cricket
🏏 Think of it like cricket: setting up this simulation project is like building a reliable practice net before running match scenarios. You create one simulator module — the 'random delivery engine' that bowls virtual balls — the way a bowling machine can reproduce any spell on demand, and you keep the experiments script separate, like the coach's session plan that decides which drills to run today. Just as one machine serves countless practice plans, separating the engine from the experiments lets you reuse the same simulator for many different probability questions. Crucially, just as a bowling machine set to identical settings must deliver the identical sequence so two players face a fair, repeatable test, you seed the random number generator so results are deterministic across runs — essential for debugging and for anyone reproducing your work. The payoff: a clean, seeded structure means your Monte Carlo experiments are trustworthy and repeatable, not a different answer every time you press start.
bash
# 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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 1 is like building a perfectly weighted bowling machine that delivers each type of ball with exactly the right frequency — dots most often, wickets rarely. Just as the machine's weighting must faithfully match the real probabilities for the practice to be meaningful, your sampler's probability vector must faithfully encode the delivery distribution. Just as one bowled ball is the atom of an over, one sampled outcome is the atom of every simulation. The insight is that a faithful single-event sampler is the engine from which all larger probabilistic scenarios are assembled.
python
# 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?

Analogy🏏Cricket
🏏 Think of it like cricket: Step 2 is like bowling ten thousand practice overs and tallying how many were maidens to estimate your real maiden-over rate. Just as the proportion of maidens across thousands of overs converges to your true maiden probability, the fraction of successful trials in a Monte Carlo run converges to the true event probability. Just as ten overs would give a noisy estimate but ten thousand a reliable one, small simulations are noisy while large ones are precise. The insight is that empirical frequency over many independent trials is a direct, trustworthy estimate of probability.
python
# 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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 3 is like watching a new bowler over many overs and continuously revising your estimate of how good he really is, starting from a cautious prior and sharpening it with each wicket or wicketless over. Just as your belief about his true skill tightens as evidence accumulates, the Bayesian posterior concentrates as more simulated deliveries are observed. Just as one good over barely moves your judgement but fifty overs settle it, each observation nudges the posterior proportionally. The insight is that simulation and Bayesian updating combine naturally: simulation produces evidence, and Bayes turns that evidence into refined belief.
python
# 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.

Analogy🏏Cricket
🏏 Think of it like cricket: verifying a Monte Carlo simulation is like trusting a batting average only after enough innings. Estimate the chance of a maiden over from ten simulated overs and the number jumps around wildly, just as a batter's average after two innings tells you little; run ten thousand overs and it settles onto the true value, exactly as an average stabilises over a long career. Just as facing more deliveries lets you read a bowler's real wicket-taking rate rather than being fooled by a lucky first spell, your Bayesian posterior mean creeps toward the true hidden wicket rate as more balls are observed. Convergence toward the known expected answer is your proof the engine and the inference are wired correctly, the way a scorer trusts the system once it reproduces a result everyone already agrees on. The payoff: watching estimates lock onto known truths as trials grow is the trust-check that lets you believe the simulator on questions you can't solve by hand.
bash
# 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.
Lesson 6 of 35
0% complete