Random numbers & simulation with Generator
Random numbers power synthetic datasets, sampling, train/test splits, bootstrapping and simulations of uncertain
business outcomes. Modern NumPy code uses a Generator object created with np.random.default_rng(), not the
legacy np.random.seed / np.random.rand functions.
Creating a Generator
import numpy as np
rng = np.random.default_rng(42) # the seed makes results reproducible
rng.random(3) # floats in [0, 1)
rng.integers(1, 7, size=10) # dice rolls: 1..6 (high is exclusive)
rng.normal(loc=500, scale=80, size=5) # mean 500, std 80
rng.choice(["UPI", "Card", "Cash"], size=8, p=[0.6, 0.25, 0.15])
rng.permutation(10) # shuffled 0..9
Pass the rng object into your functions instead of creating new generators everywhere. That keeps a whole
analysis reproducible from one seed.
Picking a distribution
| Distribution | Method | Models |
|---|---|---|
| Uniform | rng.uniform(a, b) |
Equal chance anywhere in a range |
| Normal | rng.normal(mu, sigma) |
Heights, measurement errors |
| Poisson | rng.poisson(lam) |
Customers arriving per hour |
| Binomial | rng.binomial(n, p) |
Conversions out of n visitors |
| Exponential | rng.exponential(scale) |
Time between arrivals |
| Lognormal | rng.lognormal(mean, sigma) |
Order values (skewed, positive) |
Monte Carlo: will we hit the target?
A tea stall chain wants to know the chance of crossing ₹30 lakh revenue next month. Daily customers per outlet are roughly Poisson with mean 260, and average spend is uncertain (normal, mean ₹38, std ₹4). There are 10 outlets and 30 days.
rng = np.random.default_rng(2025)
n_sims = 20_000
customers = rng.poisson(lam=260, size=(n_sims, 10, 30))
spend = rng.normal(38, 4, size=(n_sims, 1, 1)) # one spend level per simulation
revenue = (customers * spend).sum(axis=(1, 2))
print(f"Median revenue: ₹{np.median(revenue):,.0f}")
print(f"P(revenue >= 30 lakh): {(revenue >= 3_000_000).mean():.1%}")
print("90% interval:", np.percentile(revenue, [5, 95]).round(-3))
Instead of one optimistic number, you get a distribution of outcomes and a probability, which is far more useful for planning.
Bootstrapping a confidence interval
Resample your data with replacement many times to see how much a statistic could vary:
bills = rng.lognormal(mean=6, sigma=0.5, size=400) # sample of 400 bill values
idx = rng.integers(0, bills.size, size=(5000, bills.size))
boot_means = bills[idx].mean(axis=1)
print(np.percentile(boot_means, [2.5, 97.5]))
Independent streams
For parallel simulations, create independent generators with rng.spawn(4) (NumPy 1.25+), so workers do not share
or overlap random streams.
Common mistakes
- Mixing legacy
np.random.seed()with Generator code and expecting reproducibility. - Forgetting that
rng.integers(1, 6)never returns 6. - Running too few simulations; check that results are stable when you double
n_sims. - Using a normal distribution for values that cannot be negative, such as prices or waiting times.
Practice
Simulate a Diwali sale where 5,000 visitors each buy with probability 0.08 and order value is lognormal. Run 10,000 simulations and report the median revenue and the probability that revenue exceeds ₹10 lakh. Verify that rerunning with the same seed gives identical output.