Continuous and Discrete Uniform Distributions in Python (PDF, CDF, Sampling)
A uniform distribution gives every value in a range the same chance. In Python, use scipy.stats.uniform(loc, scale) for the continuous case on [loc, loc + scale] and scipy.stats.randint(low, high) for whole numbers from low to high - 1. Both expose the same methods: pdf or pmf, cdf, ppf, mean, var and rvs.
The examples below were run with SciPy 1.18 and NumPy 2.5. The API has been stable for years, so older 1.x releases of SciPy behave the same way.
Continuous vs discrete uniform
| Continuous U(a, b) | Discrete U{a, …, b} | |
|---|---|---|
| Values | Any real number between a and b | Integers a, a+1, …, b |
| Density / mass | pdf = 1 / (b − a) | pmf = 1 / n, with n = b − a + 1 |
| CDF | (x − a) / (b − a), a straight ramp | (⌊x⌋ − a + 1) / n, a staircase |
| Mean | (a + b) / 2 | (a + b) / 2 |
| Variance | (b − a)² / 12 | (n² − 1) / 12 |
| SciPy | uniform(loc=a, scale=b - a) | randint(a, b + 1) |
Two parameterisation traps account for most bugs: uniform takes a width (scale), not an upper bound, and randint excludes its upper bound, like Python's range.
The continuous uniform with scipy.stats.uniform
Freeze the distribution once, then call its methods. Here is U(2, 10): loc=2 and a width of 8.
from scipy import stats
u = stats.uniform(loc=2, scale=8) # continuous U(2, 10)
print(u.pdf([1, 2, 5, 10, 11]))
print(u.cdf([1, 2, 5, 10, 11]))
print(u.ppf([0.25, 0.5, 0.9]))
print(u.mean(), u.var(), round(u.std(), 4))
print(u.cdf(7) - u.cdf(4))
[0. 0.125 0.125 0.125 0. ]
[0. 0. 0.375 1. 1. ]
[4. 6. 9.2]
6.0 5.333333333333333 2.3094
0.375
The density is a flat 1/8 inside the interval and zero outside. The cumulative distribution function (CDF) rises linearly from 0 at x = 2 to 1 at x = 10, so cdf(5) is 3/8. ppf is the inverse of the CDF (the quantile function): the median is 6 and the 90th percentile is 9.2. The probability of landing in any sub-interval is its length divided by the total width, which is why P(4 < X < 7) = 3/8 = 0.375. The variance matches the formula (10 − 2)² / 12 = 5.33.
The discrete uniform with scipy.stats.randint
A fair six-sided die is the classic discrete uniform. Because the upper bound is exclusive, you write randint(1, 7).
die = stats.randint(1, 7) # discrete: 1..6
print(die.pmf([0, 1, 6, 7]))
print(die.cdf([1, 3, 6]))
print(die.mean(), round(die.var(), 4))
print(die.ppf(0.5))
[0. 0.16666667 0.16666667 0. ]
[0.16666667 0.5 1. ]
3.5 2.9167
3.0
Discrete distributions have a probability mass function (pmf), not a pdf; calling die.pdf raises an AttributeError. The CDF is a staircase: P(X ≤ 3) = 0.5. The variance is (6² − 1) / 12 = 2.9167, slightly different from the continuous formula. Note that ppf(0.5) returns 3.0, the smallest value whose CDF reaches 0.5, and that SciPy returns it as a float.
Sampling with NumPy
For simulation, use NumPy's Generator, created with default_rng and an explicit seed for reproducibility. The legacy np.random.uniform and np.random.randint functions still work, but the Generator API is the one NumPy recommends for new code.
import numpy as np
rng = np.random.default_rng(42)
x = rng.uniform(2, 10, size=100_000) # continuous, [2, 10)
print(round(x.mean(), 3), round(x.var(), 3))
rolls = rng.integers(1, 7, size=6_000) # discrete, 1..6
counts = np.bincount(rolls, minlength=7)[1:]
print(counts)
6.005 5.328
[ 923 1011 1030 1020 1002 1014]
The sample mean and variance land close to the theoretical 6 and 5.33. rng.integers excludes its upper bound by default, the same convention as randint; pass endpoint=True if you prefer to write rng.integers(1, 6, endpoint=True). For a deeper look at integer sampling, including random.randint in the standard library, see how to generate random integers in Python.
You can also draw from the frozen SciPy objects with u.rvs(size=1000, random_state=rng). Passing the NumPy generator keeps all your randomness under one seed.
Checking that data looks uniform
Two standard tests cover the two cases. For counts in categories, such as die faces, use the chi-square goodness-of-fit test. With no expected frequencies supplied, chisquare assumes they are all equal, which is exactly the uniform hypothesis.
res = stats.chisquare(counts)
print(round(res.statistic, 3), round(res.pvalue, 3))
res = stats.kstest(x, u.cdf) # continuous sample vs U(2, 10)
print(round(res.statistic, 4), round(res.pvalue, 3))
7.55 0.183
0.003 0.331
A p-value of 0.18 for the die gives no evidence against fairness: a spread like 923 to 1,030 out of 6,000 rolls is ordinary noise. For a continuous sample, the Kolmogorov–Smirnov test compares the empirical CDF with the theoretical one; the maximum gap of 0.003 is tiny. Remember that a high p-value means "no evidence of a difference", not proof of uniformity, and that with very large samples even negligible deviations become significant.
Where the uniform distribution shows up
- Random number generation. Most samplers start from U(0, 1) values and transform them. Feeding uniform draws into any distribution's
ppf(inverse transform sampling) produces samples from that distribution. - Uninformative priors and bounds. When you only know a minimum and a maximum, a uniform is the simplest honest model.
- Rounding error. The error from rounding to the nearest unit is roughly U(−0.5, 0.5), with variance 1/12.
- Randomised experiments. Assigning users to buckets, shuffling, or picking a random item from a list are discrete uniform draws.
The uniform's excess kurtosis is −1.2: it has no tails at all, which makes it a useful reference point when reading the kurtosis of real data.
Common mistakes
- Passing the upper bound as
scale.uniform(2, 10)is U(2, 12), not U(2, 10). - Off-by-one with
randint.randint(1, 6)never produces 6. - Calling
pdfon a discrete distribution. Usepmf. - Using the continuous variance for integers. The discrete formula is (n² − 1) / 12, not (b − a)² / 12.
- Unseeded simulations. Create one
default_rng(seed)and pass it around so results can be reproduced.
Summary
Use stats.uniform(loc=a, scale=b - a) for real-valued ranges and stats.randint(a, b + 1) for integers, then read probabilities from cdf, quantiles from ppf and moments from mean and var. Sample with rng.uniform and rng.integers from a seeded generator, and check observed data with chisquare for counts or kstest for continuous values.
Sources
- SciPy documentation, scipy.stats.uniform and scipy.stats.randint.
- NumPy documentation, numpy.random.Generator.uniform.
- NIST/SEMATECH, e-Handbook of Statistical Methods: Uniform Distribution.
DataKurtosis in Python
Compute kurtosis in Python with scipy.stats.kurtosis, pandas Series.kurt and NumPy. Excess vs Pearson, the bias flag, worked examples and how to interpret it.
PythonRandom integers
Generate random integers in Python with random.randint, randrange, sample, secrets.randbelow and NumPy's rng.integers, plus seeding, uniqueness and speed.
DataSuppress scientific notation
Stop pandas and NumPy printing floats like 1.2e+09: set_printoptions(suppress=True), display.float_format, option_context, Styler and to_csv.