Generating Synthetic IPL Data
Combine random functions into a realistic fake dataset — runs, balls, strike rates and match records — for testing, prototyping and simulation.
Every dataset in this course so far was typed by hand. In real work you often have to generate data yourself: the real data isn't ready, it is private, or you want to ask "what if we had 10,000 matches instead of 14?". NumPy's random module turns a few lines of code into a full, realistic dataset.
Definition
Synthetic data is artificially generated data that imitates the structure and statistical behaviour of real data (ranges, averages, spread, relationships between columns), without coming from real events.
- Structure — the same columns/shape as the real thing (players × matches, one row per match …).
- Statistical behaviour — realistic averages and spread, chosen by picking the right distribution (normal for scores, binomial for win counts, choice with
pfor categories). - Relationships — columns that depend on each other (balls faced depends on runs; strike rate = runs / balls × 100).
Why generate fake data?
| Reason | Example |
|---|---|
| Testing code before real data exists | build the dashboard while the season hasn't started |
| Simulating scenarios | "what if a team played 10,000 seasons?" |
| Learning & prototyping | practise analysis without a sensitive dataset |
| Statistical simulation | Monte Carlo: dice, coin flips, ball outcomes, noisy sensors |
| Privacy | share a dataset that looks real but contains no real person's data |
The recipe
- Seed a generator → reproducible dataset.
- Decide the shape → e.g.
(5 players, 6 matches). - Pick a distribution per column → normal for runs, uniform for ratios, choice for categories, integers for IDs.
- Enforce realism →
np.clip(no negative runs),.round(),.astype(int). - Derive dependent columns with vectorised maths (strike rate, totals, averages).
Syntax — the building blocks
python
rng = np.random.default_rng(seed)
rng.normal(loc, scale, size) # realistic continuous values
rng.integers(low, high, size) # IDs, counts, scores in a range
rng.uniform(low, high, size) # bounded ratios
rng.choice(items, size, p=weights) # categories
np.clip(a, a_min, a_max) # force values into a valid range| Function | Parameter | Meaning | Default |
|---|---|---|---|
np.clip | a | Array to limit | — required |
np.clip | a_min / a_max | Lower / upper limit; None = no limit on that side | — required |
.round(decimals) | decimals | Digits after the decimal point | 0 |
.astype(dtype) | dtype | New data type, e.g. int | — required |
Example — a season of player stats
python
import numpy as np
rng = np.random.default_rng(42)
players = ["Virat", "Rohit", "Hardik", "Shubman", "Shreyas"]
n_matches = 6
runs = rng.normal(loc=38, scale=20, size=(5, n_matches))
runs = np.clip(runs, 0, None).round().astype(int) # no negative scores
balls = np.clip((runs / rng.uniform(1.1, 1.6, size=runs.shape)).round(), 1, None).astype(int)
strike_rate = (runs / balls * 100).round(1)
print("Runs (players x matches):")
print(runs)
print("Strike rates:")
print(strike_rate)
print("Season totals:", dict(zip(players, runs.sum(axis=1).tolist())))text
Runs (players x matches):
[[44 17 53 57 0 12]
[41 32 38 21 56 54]
[39 61 47 21 45 19]
[56 37 34 24 62 35]
[29 31 49 45 46 47]]
Strike rates:
[[146.7 154.5 126.2 129.5 0. 120. ]
[117.1 133.3 122.6 140. 133.3 150. ]
[144.4 124.5 151.6 150. 128.6 126.7]
[143.6 115.6 121.4 109.1 147.6 145.8]
[145. 147.6 132.4 136.4 117.9 114.6]]
Season totals: {'Virat': 183, 'Rohit': 242, 'Hardik': 232, 'Shubman': 248, 'Shreyas': 247}How it works:
rng.normal(38, 20, (5, 6))gives bell-curve scores around 38. One raw value came out negative (about −1.02 for Virat in match 5), sonp.clip(runs, 0, None)turned it into 0 — a duck.- Balls faced are derived from runs by dividing by a random "runs per ball" between 1.1 and 1.6, then clipped to at least 1 ball (so we never divide by zero).
- Strike rate is computed for all 30 innings at once — no loop.
.tolist()turns NumPy integers into plain Python ints so the dictionary prints cleanly.
Example — match records with categories
python
rng = np.random.default_rng(7)
teams = np.array(["CSK", "MI", "RCB", "KKR"])
n = 6
match_id = np.arange(101, 101 + n)
home = rng.choice(teams, size=n)
first_innings = rng.integers(140, 221, size=n)
toss = rng.choice(["bat", "bowl"], size=n, p=[0.3, 0.7])
for row in zip(match_id, home, first_innings, toss):
print(int(row[0]), str(row[1]), int(row[2]), str(row[3]))text
101 KKR 207 bat
102 RCB 158 bowl
103 RCB 144 bowl
104 KKR 164 bowl
105 RCB 163 bowl
106 KKR 210 batEach column uses the right tool: arange for IDs, choice for categories, integers for a bounded total, and weighted choice because captains choose to bowl first more often (70%).
Choosing the right function for each column
| Column type | Use | Why |
|---|---|---|
| ID / match number | np.arange | unique and ordered, not random |
| Runs, heights, marks | normal + clip + round | clusters around a mean |
| Score within known limits | integers(low, high) | flat range of whole numbers |
| Ratio between bounds | uniform(low, high) | no typical value known |
| Wins / successes out of n | binomial(n, p) | count of yes/no outcomes |
| Category (team, toss decision) | choice(items, p=…) | realistic frequencies |
Common mistakes
- ❌ Generating every column independently when they are related (strike rate random and runs random). ✅ Derive dependent columns from others.
- ❌ Leaving negative or fractional values where the real world has none. ✅
np.clip,.round(),.astype(int). - ❌ Forgetting the seed — your "test data" changes every run and bugs can't be reproduced. ✅ Always
default_rng(seed). - ❌ Dividing by a column that can be 0. ✅ Clip the denominator to at least 1 (or use
np.where).
Interview tip
Q: How would you create a test dataset of 1 million transactions in NumPy?
A: Seed a generator, then build each column with a vectorised call of size 1,000,000 — arange for IDs, lognormal/normal + clip for amounts, choice with weights for categories — and derive dependent columns with array maths. No Python loop is needed, so it runs in well under a second.
Key points to remember
- Synthetic data imitates the shape and statistics of real data without coming from real events.
- Use it to test code early, simulate scenarios, prototype, and protect privacy.
- Always seed the generator so the dataset is reproducible.
- Pick a distribution per column: normal for scores, integers for bounded counts, choice with p for categories, arange for IDs.
- Enforce realism with np.clip, .round() and .astype(int).
- Derive dependent columns (strike rate, totals) from other columns with vectorised maths.
Try it yourself
Fake season stats for 5 players
Monte Carlo: chase 180 in 20 overs
Practice: build a bowler dataset
Revision Flashcards
Tap a card to flip it and see the answer.
