Intermediate22 min

Distributions and plotting

Generate and inspect data distributions, then chart them with Matplotlib — plots render right here in the browser.

Why this matters in AI / ML / GenAI

Weight initialisation, dropout, and train/test splits all draw from distributions. Plotting a histogram of your features or a scatter of predictions against truth catches problems no summary statistic will reveal.

1. Read

Understand the idea in plain English first.

2. Run

Load any example into the compiler and press Run.

3. Change

Edit one value, rerun, and learn from the output.

Common distributions

np.random.default_rng(seed) creates a generator. Use it rather than the older np.random.seed global.

  • Uniform — every value in a range equally likely. rng.uniform(low, high, size).
  • Normal (Gaussian) — the bell curve, defined by mean and standard deviation. rng.normal(loc, scale, size). Roughly 68% of values fall within one standard deviation and 95% within two.
  • Integersrng.integers(low, high, size) for synthetic labels.
  • Choicerng.choice(options, size, p=probabilities) for weighted sampling, which is how you simulate class imbalance.

Neural network weights are initialised from scaled normal or uniform distributions; that scaling is what keeps activations from exploding or vanishing in deep networks.

Plotting with Matplotlib

The convention is import matplotlib.pyplot as plt.

Prefer the object-oriented style: fig, ax = plt.subplots() then ax.plot(...). It scales to multiple panels, unlike the stateful plt.plot shortcut.

Four charts cover most ML work:

  • ax.hist(values, bins=30) — distribution shape, skew, and outliers
  • ax.scatter(x, y) — relationship between two variables, or predicted vs actual
  • ax.plot(steps, losses) — training curves over time
  • ax.bar(labels, counts) — class distribution

Always set ax.set_title, ax.set_xlabel, and ax.set_ylabel. An unlabelled chart is unreadable a week later, and a reviewer cannot check your claim against it.

The compiler on this page renders figures below the output, so you can run every example and see the result immediately.

Reading what you plot

A histogram tells you the shape: symmetric, skewed, or bimodal. Bimodal usually means two populations are mixed together and should be modelled separately.

A predicted-vs-actual scatter should hug the diagonal. Systematic curvature means the model is underfitting the relationship.

A training curve where training loss keeps falling while validation loss rises is overfitting, and it is the single most useful plot in machine learning.

Plot the distribution of your features before and after scaling. Confirming the transform did what you expected takes ten seconds and saves hours.

Hands-on practice

Compiler on the left. Examples on the right.

On desktop, keep the compiler beside the examples. On mobile, the same blocks stack cleanly. Pick an example, try it in the compiler, then change one small thing.

Compare a feature before and after scaling

Live Python compiler

Try it — in-browser Python

Loads when needed: numpy, matplotlib

Change the mean and scale of the raw feature, then re-run to see both panels shift.

Code editor

Output

Python runs in your browser. First run downloads the runtime.

Press Run (or Ctrl+Enter) to execute.

Runs CPython in your browser. NumPy, pandas, scikit-learn and Matplotlib load on demand. Charts appear below the output. No input(), no GPU, no network installs.

Clear code examples

Every example is copy-ready. Use Try in compiler when you want to experiment without scrolling around.

Example

Sampling from distributions

Check the empirical mean and std against what you asked for.

import numpy as np

rng = np.random.default_rng(42)

uniform = rng.uniform(0, 1, 1000)
normal = rng.normal(loc=100, scale=15, size=1000)
labels = rng.choice(["positive", "negative", "neutral"], size=1000, p=[0.7, 0.25, 0.05])

print(f"uniform  mean={uniform.mean():.4f}  min={uniform.min():.4f}  max={uniform.max():.4f}")
print(f"normal   mean={normal.mean():.2f}   std={normal.std():.2f}")

within_1_sd = np.mean(np.abs(normal - normal.mean()) < normal.std())
within_2_sd = np.mean(np.abs(normal - normal.mean()) < 2 * normal.std())
print(f"within 1 sd: {within_1_sd:.1%} (expect ~68%)")
print(f"within 2 sd: {within_2_sd:.1%} (expect ~95%)")

unique, counts = np.unique(labels, return_counts=True)
print("\nlabel distribution:", dict(zip(unique.tolist(), counts.tolist())))

Example

Histogram — the chart renders below the output

First run downloads Matplotlib, so allow it a few extra seconds.

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(0)
latencies = np.concatenate([
    rng.normal(200, 30, 900),
    rng.normal(1200, 200, 100),
])

fig, ax = plt.subplots(figsize=(7, 3.5))
ax.hist(latencies, bins=40, color="#1d4ed8", edgecolor="white")
ax.axvline(np.median(latencies), color="#ea580c", linewidth=2, label="median")
ax.axvline(np.percentile(latencies, 95), color="#dc2626", linewidth=2, linestyle="--", label="p95")
ax.set_title("Request latency distribution (n=1000)")
ax.set_xlabel("Latency (ms)")
ax.set_ylabel("Number of requests")
ax.legend()

print("bimodal — two populations are mixed here")
print("median:", round(float(np.median(latencies)), 1), "ms")
print("p95   :", round(float(np.percentile(latencies, 95)), 1), "ms")

Example

Training curve showing overfitting

The gap opening between the two lines is the thing to look for.

import matplotlib.pyplot as plt
import numpy as np

epochs = np.arange(1, 21)
train_loss = 1.2 * np.exp(-0.25 * epochs) + 0.05
val_loss = 1.2 * np.exp(-0.25 * epochs) + 0.05 + 0.012 * np.clip(epochs - 8, 0, None) ** 1.5

fig, ax = plt.subplots(figsize=(7, 3.5))
ax.plot(epochs, train_loss, marker="o", color="#1d4ed8", label="training loss")
ax.plot(epochs, val_loss, marker="s", color="#ea580c", label="validation loss")
best = int(np.argmin(val_loss)) + 1
ax.axvline(best, color="#64748b", linestyle=":", label=f"best epoch = {best}")
ax.set_title("Training vs validation loss")
ax.set_xlabel("Epoch")
ax.set_ylabel("Loss (cross-entropy)")
ax.legend()

print("stop training at epoch", best, "- after that the model is memorising")

Example

Scatter plot: predicted vs actual

Points should hug the diagonal. Curvature means underfitting.

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(7)
actual = rng.uniform(0, 100, 120)
predicted = actual * 0.92 + rng.normal(0, 6, 120) + 3

fig, ax = plt.subplots(figsize=(5.5, 5))
ax.scatter(actual, predicted, alpha=0.6, color="#1d4ed8", edgecolor="white")
lims = [0, 105]
ax.plot(lims, lims, color="#dc2626", linestyle="--", label="perfect prediction")
ax.set_xlim(lims)
ax.set_ylim(lims)
ax.set_title("Predicted vs actual values")
ax.set_xlabel("Actual value")
ax.set_ylabel("Predicted value")
ax.legend()

residuals = predicted - actual
print("mean absolute error:", round(float(np.abs(residuals).mean()), 2))
print("bias (mean residual):", round(float(residuals.mean()), 2))

Example

Bar chart of class distribution

Class imbalance is obvious in a bar chart and easy to miss in a table.

import matplotlib.pyplot as plt

labels = ["positive", "negative", "neutral"]
counts = [700, 250, 50]

fig, ax = plt.subplots(figsize=(6, 3.2))
bars = ax.bar(labels, counts, color=["#1d4ed8", "#ea580c", "#64748b"])
for bar, count in zip(bars, counts):
    ax.text(bar.get_x() + bar.get_width() / 2, count + 12, str(count), ha="center", fontweight="bold")
ax.set_title("Training label distribution")
ax.set_xlabel("Class")
ax.set_ylabel("Number of examples")

print("imbalance ratio:", round(max(counts) / min(counts), 1), ": 1")
print("a model predicting only 'positive' scores", f"{max(counts) / sum(counts):.0%}", "accuracy")

Takeaways

  • Seed with np.random.default_rng(seed) and check empirical mean and std against expectations.
  • Use fig, ax = plt.subplots() and always label the title and both axes.
  • Histogram for shape, scatter for predicted vs actual, line for training curves.