
NumPy provides comprehensive tools for working with various probability distributions through its random module. In this article, we will explore some of the best practices for generating and analyzing data from these distributions. From the normal and uniform distributions to binomial and Poisson, NumPy makes it easy to simulate different statistical patterns. We’ll look at how to set seeds for reproducibility, calculate probabilities, and understand the applications of these distributions in various contexts.
Setting the Random Seed
Before we begin, it’s important to understand random seeds. Setting a seed ensures that your random number generation is reproducible:
import numpy as np # Set random seed for reproducibility np.random.seed(42)
Without setting a seed, you’ll get different results each time you run the code. Even with the same seed, you might see slight variations in exact numbers due to different NumPy versions, platform architectures, or Python versions. However, the statistical properties and patterns will remain consistent.
Normal (Gaussian) Distribution
The normal distribution is one of the most commonly used probability distributions in statistics. Here’s how to generate random numbers from a normal distribution:
# Set seed for reproducibility
np.random.seed(42)
# Generate 1000 random numbers from a normal distribution
# with mean=0 and standard deviation=1
normal_data = np.random.normal(loc=0, scale=1, size=1000)
# Calculate basic statistics
print(f"Mean: {np.mean(normal_data):.2f}")
print(f"Standard Deviation: {np.std(normal_data):.2f}")
# Generate a histogram to visualize the distribution
import matplotlib.pyplot as plt
plt.hist(normal_data, bins=30, density=True)
plt.title("Normal Distribution", fontsize=18)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Frequency", fontsize=14)
plt.show()
Expected output:
Mean: 0.02 Standard Deviation: 0.98

The resulting histogram shows the classic bell-shaped curve of a normal distribution, with most values clustered around zero and symmetric tails extending to both sides.
Uniform Distribution
The uniform distribution represents scenarios where all outcomes are equally likely. We’ll explore two common types of uniform distributions: continuous and discrete.
First, let’s look at a continuous uniform distribution where we generate numbers between 0 and 10. This type of distribution is useful when you need random values within a specific range, such as simulating measurements with random errors or generating random prices within a range:
# Reset seed for this example
np.random.seed(42)
# Generate random numbers between 0 and 10
uniform_data = np.random.uniform(low=0, high=10, size=1000)
# Calculate probability of numbers falling in a specific range
prob_range = np.sum((uniform_data >= 3) & (uniform_data <= 7)) / len(uniform_data)
print(f"Probability of numbers between 3 and 7: {prob_range:.2f}")
Expected output:
Probability of numbers between 3 and 7: 0.39
This result makes intuitive sense because the range 3 to 7 covers 40% of our possible values (4 units out of the total 10 unit range), and our empirical probability of 0.39 is very close to this theoretical value.
Next, let’s examine a discrete uniform distribution by simulating dice rolls. This is a perfect example of a discrete uniform distribution because a fair die has exactly six possible outcomes, each with equal probability:
# Generate numbers for a discrete uniform distribution
discrete_uniform = np.random.randint(low=1, high=7, size=1000) # Simulating dice rolls
unique, counts = np.unique(discrete_uniform, return_counts=True)
print("\nDice Roll Frequencies:")
for value, count in zip(unique, counts):
print(f"Value {value}: {count} times")
Expected output:
Dice Roll Frequencies: Value 1: 167 times Value 2: 176 times Value 3: 149 times Value 4: 171 times Value 5: 174 times Value 6: 163 times
In our simulation of 1000 dice rolls, we expect each number to appear approximately 166.67 times (1000/6). The actual frequencies we observe are pretty close to this expected value, demonstrating the uniform nature of the distribution.
Binomial Distribution
The binomial distribution models the number of successes in a fixed number of independent trials. Here’s how to use it:
# Reset seed for this example
np.random.seed(42)
# Generate binomial random numbers
# params: n=number of trials, p=probability of success
n_trials = 10
p_success = 0.5
binomial_data = np.random.binomial(n=n_trials, p=p_success, size=1000)
# Calculate probability of exactly 5 successes
prob_5 = np.sum(binomial_data == 5) / len(binomial_data)
print(f"Probability of exactly 5 successes: {prob_5:.2f}")
# Calculate probability of 7 or more successes
prob_7_or_more = np.sum(binomial_data >= 7) / len(binomial_data)
print(f"Probability of 7 or more successes: {prob_7_or_more:.2f}")
Expected output:
Probability of exactly 5 successes: 0.24 Probability of 7 or more successes: 0.17
These results align with what we’d expect in a fair scenario (p=0.5): getting exactly 5 successes in 10 trials is the most likely outcome, occurring about 24% of the time. The probability of getting 7 or more successes is lower (17%) because it’s more rare to get such a high number of successes when the probability of success for each trial is 0.5.
Poisson Distribution
The Poisson distribution models the number of events occurring in a fixed interval of time or space. Here’s how to work with it:
# Reset seed for this example
np.random.seed(42)
# Generate Poisson random numbers
# lambda_param = average number of events
lambda_param = 3.0
poisson_data = np.random.poisson(lam=lambda_param, size=1000)
# Calculate probability of exactly 3 events
prob_3 = np.sum(poisson_data == 3) / len(poisson_data)
print(f"Probability of exactly 3 events: {prob_3:.2f}")
# Calculate mean and variance (should be approximately equal for Poisson)
print(f"Mean: {np.mean(poisson_data):.2f}")
print(f"Variance: {np.var(poisson_data):.2f}")
Expected output:
Probability of exactly 3 events: 0.23 Mean: 2.96 Variance: 3.00
Note that for a Poisson distribution, the mean and variance should be approximately equal to the lambda parameter (3.0 in this case).
Conclusions and Further Reading
NumPy’s random module is a robust tool for working with various probability distributions, offering both continuous (like normal and uniform) and discrete (like binomial and Poisson) distributions. Setting a random seed is essential for reproducibility, though minor variations can occur across different systems or versions.
For those interested in exploring more advanced features, the NumPy documentation offers insights into additional distributions like chi-square and exponential, as well as techniques for parallel random number generation and cryptographic applications. Although our examples utilized the traditional approach, newer projects might benefit from the default_rng() interface, which enhances performance and functionality. By experimenting with these tools, you’ll be well-equipped to incorporate Numpy for tasks that involve probability distributions.
