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.
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.
Copy-paste examples
Copy into your own editor, or load one into the compiler below and press Run.
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())))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")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")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))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")Compare a feature before and after scaling
Try it — in-browser Python
Packages: numpy, matplotlib
Change the mean and scale of the raw feature, then re-run to see both panels shift.
Output
Python runs in your browser. First run downloads the runtime.
Press Run (or Ctrl+Enter) to execute.
CPython in WebAssembly. Stdlib works. NumPy, pandas, scikit-learn and Matplotlib load on demand, and charts render below. No input(), no GPU, no network installs.
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.