Quick answer: Use NumPy’s Generator API to create explicit random streams and distribution samples. Seed generators for reproducible tests or experiments, record the bit generator and stream policy, and keep numerical randomness separate from cryptographic secret generation.

NumPy’s modern random API starts with np.random.default_rng(). It returns a Generator object that creates random floats, integers, samples, permutations, and distribution data for arrays.
The official NumPy documentation covers the random sampling module and the Generator API. Python’s secrets module is the better standard-library choice for security tokens.
Older examples often use module-level calls such as np.random.seed() and np.random.randint(). Those still exist for compatibility, but new code should usually create a generator with default_rng() and pass that generator to the code that needs random numbers.
Seeding is for reproducibility, not security. A fixed seed is useful in tutorials, tests, simulations, and notebooks because it makes output repeatable. Do not use NumPy random output for passwords, API keys, reset tokens, or cryptographic secrets.
Shape matters. Most generator methods accept size, which can be an integer or a tuple. A tuple such as (2, 3) creates a two-row, three-column array.
Use distribution-specific methods when the statistical meaning matters. random() creates uniform floats in the half-open interval [0, 1). normal() creates samples from a normal distribution with a chosen mean and standard deviation.
Keep one generator near the workflow that owns the randomness. Passing a generator into functions avoids hidden global state and makes tests easier to repeat.
This is especially important in notebooks and data pipelines. If every cell or helper function changes the module-level random state, results can depend on execution order. A local generator makes that dependency explicit.
Use fixed seeds in examples, regression tests, and debugging sessions. Remove or change those seeds when the goal is to run independent simulations. A fixed seed is useful for explaining behavior, but it intentionally repeats the same pseudo-random sequence.
Think carefully about replacement when sampling. Sampling with replacement means an item can appear more than once. Sampling without replacement is closer to drawing cards from a deck, where a selected item is no longer available for the same draw.
For generated test arrays, record the distribution, shape, and seed together. That makes it easier for another developer to reproduce the same input and understand whether the numbers were uniform, normal, integer, or sampled labels.
When teaching or reviewing random examples, avoid saying the values are completely random. NumPy generators produce pseudo-random numbers designed for numerical work. They are excellent for simulations and tests, but they are not a security boundary.
For security tokens, use secrets or a cryptography library instead of NumPy.
That keeps simulation randomness separate from authentication and authorization decisions.
Keep that boundary explicit.
Create Random Floats
default_rng() creates a generator object.
import numpy as np
rng = np.random.default_rng(42)
values = rng.random(3)
print(np.round(values, 3))
random(3) returns three floats between zero and one.
The seed makes the example output repeatable.
Rounding is used only to make the printed result easier to read.
Create Random Integers
Use integers() for random whole numbers.
import numpy as np
rng = np.random.default_rng(7)
values = rng.integers(1, 10, size=(2, 3))
print(values)
The low value is included.
The high value is excluded.
The shape argument creates a 2 by 3 array.

Sample Items With choice
choice() samples from an array or sequence.
import numpy as np
rng = np.random.default_rng(1)
colors = np.array(["red", "green", "blue", "gold"])
sample = rng.choice(colors, size=2, replace=False)
print(sample)
replace=False means the same item cannot be selected twice.
This is useful for sampling labels, rows, IDs, or other existing values.
Use replace=True when repeated selections are allowed.
Create Normal Samples
Use normal() for normally distributed values.
import numpy as np
rng = np.random.default_rng(5)
values = rng.normal(loc=10, scale=2, size=4)
print(np.round(values, 2))
loc sets the center of the distribution.
scale sets the standard deviation.
Use distribution parameters deliberately so the generated data matches the simulation or test case.
Permute Without Changing The Original
permutation() returns shuffled data while leaving the input unchanged.
import numpy as np
rng = np.random.default_rng(3)
items = np.array([1, 2, 3, 4])
permuted = rng.permutation(items)
print(permuted)
print(items)
The first print shows the permuted result.
The second print shows that the original array still has its original order.
Use shuffle() instead when in-place shuffling is intended.

Repeat Results With A Seed
Using the same seed creates the same sequence from a new generator.
import numpy as np
left = np.random.default_rng(99).integers(0, 5, size=4)
right = np.random.default_rng(99).integers(0, 5, size=4)
print(np.array_equal(left, right))
This is useful for repeatable tests and examples.
For independent simulation runs, use different seeds or seed-management tools suited to the project.
In short, prefer default_rng() and Generator methods for new NumPy random code, use seeds for reproducibility, use size for shapes, choose the distribution method that matches the problem, and avoid NumPy random output for security-sensitive secrets.
Prefer Generator
Create a Generator with default_rng and pass it into functions that need randomness. This makes state a visible dependency and avoids hidden coupling through global random calls.

Choose A Distribution
Select normal, uniform, integer, choice, permutation, or another distribution based on the model. Validate bounds, shape, dtype, and whether replacement is allowed.
Make Runs Reproducible
A seed can reproduce a test or experiment when the generator, NumPy version, call order, and stream allocation are controlled. Store those details with the result when auditability matters.
Split Independent Streams
Parallel simulations need deliberate stream allocation rather than every worker reusing the same seed. Use the Generator and SeedSequence mechanisms appropriate for the workload and verify independence assumptions.

Keep Security Separate
NumPy random is designed for simulation and numerical sampling, not passwords, reset links, keys, or authentication tokens. Use Python’s secrets module or a security library for those requirements.
Test Statistical Contracts
Test shape, bounds, seeded repeatability, independent stream setup, invalid arguments, dtype, empty outputs, and a basic distribution sanity check without overclaiming from tiny samples.
The official NumPy Generator documentation describes the modern random API. Related Python Pool references include NumPy arrays and tests.
For reproducible numerical workflows, compare NumPy array shapes, seed and distribution tests, and sample sequences before generating random data.
Frequently Asked Questions
How should I generate random values with NumPy?
Create a NumPy random Generator and call the distribution method that matches the model instead of relying on shared global state.
How do I make NumPy results reproducible?
Use a documented seed or seed sequence and record the generator configuration with the experiment or test.
Is NumPy random secure for passwords?
No. NumPy generators are for numerical simulation and sampling, not cryptographic secrets or authentication tokens.
Why did my random results change after a refactor?
The generator state, call order, bit generator, seed, or parallel stream allocation may have changed; make those dependencies explicit.