Data

Continuous and Discrete Uniform Distributions in Python (PDF, CDF, Sampling)

Data · Published 2026-10-27

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}
ValuesAny real number between a and bIntegers a, a+1, …, b
Density / masspdf = 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
SciPyuniform(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

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

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

Read next

More in Data