Skip to content

Discrete Distributions

NumPy provides functions for generating samples from discrete probability distributions.

np.random.randint

Generates random integers from a discrete uniform distribution.

1. Basic Usage

import numpy as np

def main():
    np.random.seed(0)

    # Random integers from 0 to 9
    samples = np.random.randint(0, 10, size=10)
    print(f"Samples: {samples}")

if __name__ == "__main__":
    main()

Output:

Samples: [5 0 3 3 7 9 3 5 2 4]

2. Half-Open Interval

randint(low, high) samples from \([\text{low}, \text{high})\) — high is excluded.

import numpy as np

def main():
    np.random.seed(42)

    # Integers from 1 to 6 (inclusive)
    # Use high=7 to include 6
    dice = np.random.randint(1, 7, size=10)
    print(f"Dice rolls: {dice}")

if __name__ == "__main__":
    main()

3. Fair Dice Histogram

import numpy as np
import matplotlib.pyplot as plt

def main():
    np.random.seed(0)

    data = np.random.randint(low=1, high=7, size=10_000)

    fig, ax = plt.subplots(figsize=(10, 4))
    ax.set_title("Fair Dice Rolls", fontsize=15)
    bins = np.arange(1, 8) - 0.5
    ax.hist(data, bins=bins, density=True, alpha=0.4, rwidth=0.9)
    ax.axhline(1/6, color='r', linestyle='--', label='Expected: 1/6')
    ax.set_xticks(np.arange(1, 7))
    ax.set_xlabel('Face')
    ax.set_ylabel('Frequency')
    ax.legend()
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)
    plt.show()

if __name__ == "__main__":
    main()

np.random.choice

Samples from a custom discrete distribution or array.

1. Uniform Sampling

import numpy as np

def main():
    np.random.seed(42)

    Omega = ['A', 'B', 'C', 'D', 'E']
    samples = np.random.choice(Omega, size=10)
    print(f"Samples: {samples}")

if __name__ == "__main__":
    main()

2. Custom Weights

import numpy as np
import matplotlib.pyplot as plt

def main():
    np.random.seed(0)

    Omega = [-3, -1, 1, 2, 5]
    pmf = [0.1, 0.1, 0.1, 0.5, 0.2]

    sample = np.random.choice(Omega, p=pmf, size=10_000)

    fig, ax = plt.subplots(figsize=(10, 4))
    ax.set_title("Custom Discrete Distribution", fontsize=15)
    bins = np.array([-4, -2, 0, 1.5, 3.5, 6])
    ax.hist(sample, bins=bins, density=True, alpha=0.4, rwidth=0.8)

    # Mark theoretical probabilities
    for val, prob in zip(Omega, pmf):
        ax.plot(val, prob, 'ro', markersize=10)

    ax.set_xlabel('Value')
    ax.set_ylabel('Probability')
    plt.show()

if __name__ == "__main__":
    main()

3. Without Replacement

import numpy as np

def main():
    np.random.seed(42)

    Omega = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    # Sample 5 unique elements
    sample = np.random.choice(Omega, size=5, replace=False)
    print(f"Without replacement: {sample}")

    # With replacement allows duplicates
    sample_wr = np.random.choice(Omega, size=5, replace=True)
    print(f"With replacement:    {sample_wr}")

if __name__ == "__main__":
    main()

np.random.geometric

Generates samples from the geometric distribution.

1. Basic Usage

import numpy as np

def main():
    np.random.seed(42)

    p = 0.3  # probability of success
    samples = np.random.geometric(p, size=10)
    print(f"Samples: {samples}")

if __name__ == "__main__":
    main()

2. Waiting Time

Number of trials until the first success.

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

def main():
    np.random.seed(0)

    p = 0.3
    data = np.random.geometric(p, size=10_000)

    fig, ax = plt.subplots(figsize=(10, 4))

    ax.set_title(f"Geometric(p={p})", fontsize=15)
    max_x = 20
    bins = np.arange(1, max_x + 1) - 0.5
    ax.hist(data[data < max_x], bins=bins, density=True, alpha=0.4, label='Samples')

    # Theoretical PMF
    x = np.arange(1, max_x)
    pmf = stats.geom(p).pmf(x)
    ax.stem(x, pmf, linefmt='r-', markerfmt='ro', basefmt=' ', label='PMF')

    ax.set_xlabel('Number of Trials')
    ax.set_ylabel('Probability')
    ax.legend()
    plt.show()

if __name__ == "__main__":
    main()

3. Mean and Variance

\[E[X] = \frac{1}{p}, \quad \text{Var}(X) = \frac{1-p}{p^2}\]
import numpy as np

def main():
    p = 0.3
    samples = np.random.geometric(p, size=100_000)

    print(f"Theoretical mean: {1/p:.4f}")
    print(f"Sample mean:      {samples.mean():.4f}")
    print()
    print(f"Theoretical var:  {(1-p)/p**2:.4f}")
    print(f"Sample var:       {samples.var():.4f}")

if __name__ == "__main__":
    main()

Categorical Sampling

1. Multinomial

import numpy as np

def main():
    np.random.seed(42)

    # Roll a biased die 100 times
    probs = [0.1, 0.1, 0.2, 0.2, 0.2, 0.2]  # sum to 1
    n_trials = 100

    counts = np.random.multinomial(n_trials, probs)
    print(f"Face counts: {counts}")
    print(f"Total: {counts.sum()}")

if __name__ == "__main__":
    main()

2. Multiple Experiments

import numpy as np

def main():
    np.random.seed(42)

    probs = [0.2, 0.3, 0.5]
    n_trials = 10
    n_experiments = 5

    results = np.random.multinomial(n_trials, probs, size=n_experiments)
    print("Results (rows=experiments, cols=categories):")
    print(results)

if __name__ == "__main__":
    main()

3. Simulation Use Case

import numpy as np

def main():
    np.random.seed(42)

    # Customer segments: [Budget, Standard, Premium]
    segment_probs = [0.5, 0.35, 0.15]

    # Simulate 1000 customers
    n_customers = 1000
    segment_counts = np.random.multinomial(n_customers, segment_probs)

    labels = ['Budget', 'Standard', 'Premium']
    for label, count in zip(labels, segment_counts):
        print(f"{label:10}: {count} ({count/n_customers:.1%})")

if __name__ == "__main__":
    main()

Applications

1. Bootstrap Sampling

import numpy as np

def main():
    np.random.seed(42)

    data = np.array([2.3, 3.1, 4.5, 2.8, 3.9, 4.2, 3.5])

    # Bootstrap: sample with replacement
    n_bootstrap = 1000
    means = []

    for _ in range(n_bootstrap):
        sample = np.random.choice(data, size=len(data), replace=True)
        means.append(sample.mean())

    means = np.array(means)
    print(f"Original mean: {data.mean():.3f}")
    print(f"Bootstrap mean: {means.mean():.3f}")
    print(f"95% CI: [{np.percentile(means, 2.5):.3f}, {np.percentile(means, 97.5):.3f}]")

if __name__ == "__main__":
    main()

2. Random Index Selection

import numpy as np

def main():
    np.random.seed(42)

    n_samples = 1000
    n_select = 100

    # Random indices for train/test split
    indices = np.arange(n_samples)
    test_idx = np.random.choice(indices, size=n_select, replace=False)
    train_idx = np.setdiff1d(indices, test_idx)

    print(f"Train size: {len(train_idx)}")
    print(f"Test size:  {len(test_idx)}")

if __name__ == "__main__":
    main()

3. Weighted Random Selection

import numpy as np

def main():
    np.random.seed(42)

    items = ['Apple', 'Banana', 'Cherry', 'Date']
    weights = [10, 5, 3, 2]  # relative weights

    # Normalize to probabilities
    probs = np.array(weights) / sum(weights)

    # Sample 20 items
    samples = np.random.choice(items, size=20, p=probs)

    print("Samples:", samples)
    print()
    for item in items:
        count = (samples == item).sum()
        print(f"{item}: {count}")

if __name__ == "__main__":
    main()