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. - Integers —
rng.integers(low, high, size)for synthetic labels. - Choice —
rng.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 outliersax.scatter(x, y)— relationship between two variables, or predicted vs actualax.plot(steps, losses)— training curves over timeax.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.