Setting Random Seeds for Reproducible Analyses: A Practical Guide

By Dr. Zubair Khalid, DVM, MS, PhD ·

Setting Random Seeds for Reproducible Analyses: A Practical Guide

By the end of this guide you will be able to set a random seed in R and in Python, seed every random number generator in a typical machine learning pipeline, verify that the seed actually took effect, run an analysis under several seeds instead of one, and record all of it in a short reproducibility checklist. You will also know the mistakes that quietly break random seed reproducibility even after a seed has been set.

To follow the walkthrough you need R 4.x or Python 3.x installed. For the Python sections you need the numpy and scikit-learn packages. The deep learning section assumes PyTorch or TensorFlow, but you can skip it if your work uses only classical statistics.

Why Random Seeds Anchor Reproducible Research

Reproducible research means that another person, or you six months from now, can obtain similar results using the same code and data. The machine learning community treats this as a baseline requirement rather than a bonus. The NeurIPS reproducibility program, introduced in 2019, defined reproducibility as obtaining the presented results with the same code and data, and built a code submission policy, a community reproducibility challenge, and a reproducibility checklist into the conference's submission process. Concerns about quality, reliability, and reproducibility extend across biomedical research generally, not just machine learning [1].

Randomness sits at the center of the problem. Most modern analyses draw on random processes. Cross-validation splits data into folds at random. Bootstrap confidence intervals resample rows at random. Neural networks initialize their weights at random. Stochastic gradient descent shuffles training examples at random. Permutation tests shuffle labels at random. If any of these steps runs unrestrained, two runs of the identical script on the identical data can produce different numbers, and neither run is wrong. The pipeline is simply drawing a different random stream each time.

A random seed fixes the stream. Setting a seed tells the software to start its random number generator at a known state, so every draw that follows follows the same sequence. The analysis becomes deterministic even though it uses randomization internally.

One caution before the walkthrough. A fixed seed makes an analysis repeatable. It does not make a finding correct. A reproducible result built on a leaky data split or a misinterpreted p value is reproducibly wrong, a distinction that matters when results travel from a development notebook into production use [2].

How a Seed Actually Works

Software does not generate truly random numbers. It runs a pseudorandom number generator, usually shortened to PRNG. A PRNG is a deterministic algorithm that starts from an initial value called the seed, then produces a long sequence of numbers that looks random but is fully determined by that seed. Feed in the same seed and you get the same sequence, every time, in the same order.

Two details follow from this and explain most confusion later.

First, each library that generates random numbers keeps its own generator state. R has one state. Python's standard library random module has another. numpy has another, and modern numpy actually offers two, a legacy generator and a newer Generator object. PyTorch and TensorFlow keep their own. Seeding one of them does not seed the others, which is why pipelines that mix libraries need every generator seeded separately.

Second, the sequence depends on the algorithm and often on the version of the library. The same seed does not guarantee the same numbers across different PRNG algorithms, across major version changes, or across CPU and GPU implementations. Seeds guarantee reproducibility within a fixed environment, not across all environments.

Setting Random Seeds for Reproducible Analyses, Step by Step

Steps for picking a seed, seeding R, Python, scikit-learn and deep learning frameworks, then checking with a double run.

The walkthrough below follows one complete cycle. Pick and record a seed. Seed each language and library you use. Verify the seed took effect. Then repeat the analysis under multiple seeds.

Step 1: Choose a Seed and Write It Down

Any non-negative integer works. Popular defaults such as 42 have no statistical magic. They are memorable, and memorability helps because the seed must be recorded with the analysis. Treat the seed like a parameter: it belongs in your script, in your configuration file, and in your methods section.

A practical habit is to define the seed once, at the top of the script, and reference it everywhere else.

SEED = 42

Never choose a seed by trying many values and keeping the one with the best results. That turns the seed into a tuning parameter and inflates your apparent performance.

Step 2: Seed Everything in R

Base R uses one generator controlled by set.seed(). Every random function in base R and in most packages, including sample(), rnorm(), runif(), and the resampling inside many modeling functions, draws from that single state.

set.seed(42)
runif(3)

Expected output:

[3] 0.9148060 0.9370754 0.2861395

Run the two lines again in a fresh session and you get the same three numbers. Reset the seed with the same call before each run you need to repeat, for example at the top of each cross-validation loop or each simulation replicate.

Two R specifics matter. The hidden object .Random.seed stores the generator state, and clearing the workspace with rm(list = ls()) does not reset it. If you want a truly fresh stream, remove .Random.seed as well or restart R. Also, R code that calls C or C++ through interfaces such as Rcpp can use its own generator, so a package that implements its own sampling may need its own seed argument. Read the package documentation and pass its seed argument explicitly when one exists.

Step 3: Seed the Standard Library and numpy in Python

Python has at least two independent generators in a basic script. Seed both.

import random
import numpy as np

random.seed(42)
np.random.seed(42)

print(random.random())
print(np.random.random())

Expected output:

0.6394267984578837
0.3745401188473625

The two numbers differ because they come from two different generators. That is expected, and it is exactly why both lines are needed.

Modern numpy recommends a Generator object created with default_rng() instead of the legacy np.random.seed() interface. The Generator uses a different algorithm, PCG64, so it produces different numbers from the same seed.

import numpy as np

rng = np.random.default_rng(42)
print(rng.random(3))

Expected output:

[0.77395605 0.43887844 0.85859792]

The best practice for new code is to create one rng = np.random.default_rng(SEED) at the top of the script and pass it down through your functions. Pass the generator itself, not the integer, so every function shares one controlled stream. Code that mixes np.random.seed() with default_rng() objects will behave unpredictably, because the two interfaces track state separately.

Step 4: Seed Machine Learning and Deep Learning Frameworks

scikit-learn exposes randomness through the random_state parameter on estimators and utilities such as train_test_split, KFold, and RandomForestClassifier. Pass your SEED constant to each one. Do not rely on a global seed for scikit-learn, because the library documents that its estimators use their own random state handling.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=SEED, stratify=y
)

After this call, the same rows land in the training set and the test set on every run. Check it by printing the shapes and a few row identifiers on two consecutive runs. They must match exactly.

PyTorch needs several settings, because CPU and GPU paths each have their own sources of nondeterminism.

import torch

SEED = 42
torch.manual_seed(SEED)
torch.cuda.manual_seed_all(SEED)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

torch.manual_seed() covers the CPU generator, and torch.cuda.manual_seed_all() covers every GPU device. The two cuDNN flags force deterministic convolution algorithms. For full determinism in newer versions you can also call torch.use_deterministic_algorithms(True), which raises an error at any operation that has no deterministic implementation rather than silently falling back.

TensorFlow bundles the common cases into one helper.

import tensorflow as tf

tf.keras.utils.set_random_seed(SEED)
tf.config.experimental.enable_op_determinism()

set_random_seed() seeds Python, numpy, and TensorFlow together. enable_op_determinism() forces deterministic GPU operations where implementations exist.

Step 5: Verify That the Seed Worked

Setting a seed proves nothing until you check it. Run the entire pipeline twice from a fresh session and compare outputs. For arrays, a direct comparison is enough.

import numpy as np

SEED = 42

a = np.random.default_rng(SEED).normal(size=1000)
b = np.random.default_rng(SEED).normal(size=1000)

print(np.array_equal(a, b))

Expected output:

True

If the pipeline produces a metric rather than an array, capture it in both runs and compare. For a permanent record, hash the result.

import hashlib

digest = hashlib.sha256(a.tobytes()).hexdigest()
print(digest)

The output is a 64-character hexadecimal string. Store it with your results. Anyone who reruns your code with the same seed, data, and library versions can regenerate the identical string and confirm bit-for-bit agreement.

Verification should cover the whole pipeline, not just the first random call. A seed can be set correctly at the top of a script and still fail to control a resampling step buried inside a package that manages its own generator state.

Step 6: Run Multiple Seeds, Not One

A single seeded run is reproducible, but it is also one draw from the distribution of possible results. In any stochastic method, the metric you report depends on the seed, and the size of that dependence is an empirical question. The direct answer is to repeat the analysis under several seeds and report the spread.

This practice is now visible in applied work. One benchmark of retinal image segmentation pipelines retrained every configuration under three independent random seeds, 42, 15, and 89, and reported a seed-level standard deviation alongside every mean so that each ranking carried an explicit confidence limit [3]. On one of its datasets, the strongest configuration reached a mean disc Dice of 0.9523 with a seed-level standard deviation of 0.0017, a small spread that still had to be measured rather than assumed [3].

For your own work, the pattern is simple. Pick three to five seeds in advance. Run the full analysis under each. Report the mean and the standard deviation of your headline metric. If the spread is large relative to the effect you are claiming, the effect needs more data or a more stable estimator, and no single seed will fix that.

Reference Table of Seed Controls

EnvironmentControlScopeNotes
Rset.seed(42)All base R and most package samplingrm(list = ls()) does not reset the generator state
Python standard libraryrandom.seed(42)random module onlySeparate from numpy and frameworks
numpy legacynp.random.seed(42)Global legacy generatorUsed by older tutorials and some packages
numpy modernnp.random.default_rng(42)One Generator object you pass aroundPreferred for new code, different sequence from legacy
scikit-learnrandom_state=42 argumentEach estimator and splitter individuallySet it on every splitter and estimator, not once globally
PyTorchtorch.manual_seed(42), torch.cuda.manual_seed_all(42), cudnn flagsCPU and GPU generatorsAdd torch.use_deterministic_algorithms(True) for full determinism
TensorFlowtf.keras.utils.set_random_seed(42) with enable_op_determinism()Python, numpy, and TensorFlowOne call covers the common generators

Random Seeds and Your Reproducibility Checklist

A seed is one line in a longer document. The checklist pattern, in which a short structured list of required items is verified before work is considered complete, has become a standard tool for transparent research across fields. Reporting checklists in neuroimaging promote transparency, replicability, and reproducibility by making fundamental design and execution details explicit [4]. Image-based checklists have improved the reproducibility of ultrasound measurements by standardizing exactly how each acquisition and measurement step is performed [5]. In the statistical office setting, an independent verification step and a documented checklist are treated as prerequisites before an algorithmic method is trusted for production [2].

Your own random seed reproducibility checklist should contain at least these items.

  1. The seed value itself, recorded in the script and in the methods text.
  2. Every generator seeded, including the language runtime, numpy, and each framework, as verified by a double run.
  3. Library and language versions, captured with sessionInfo() in R or pip freeze in Python, because PRNG algorithms can change between versions.
  4. A hash or checksum of the input data, so a silently changed dataset is detectable.
  5. The complete list of random_state or seed arguments passed to splitters, estimators, and samplers.
  6. The set of seeds used for any multi-seed repetition, with the per-seed results.
  7. The expected output of the verification run, so a future rerun can be checked mechanically.

Formal versions of this idea already exist at the community level. The Machine Learning Reproducibility Checklist used at NeurIPS asks authors to disclose exactly these kinds of details as part of submission. If your field has a reporting checklist, adopt it, and add the seed items above if the field checklist omits them.

A note on what the checklist cannot do. It guarantees that the numbers regenerate. It does not guarantee that the statistical conclusions are sound. Careful interpretation still applies, and a p value near 0.05 from a single run carries far weaker evidence than many readers assume, a point that exact calculations of false positive risk make concrete [6].

Common Mistakes and Limitations

Seeding only one generator. The most common failure in mixed pipelines. A script seeds numpy but not the Python random module, or seeds PyTorch but not the DataLoader worker processes, and one layer of the pipeline still varies between runs. Seed every generator the pipeline touches, then verify with a full double run.

Assuming a seed works across machines and versions. The same seed does not guarantee the same numbers across different PRNG implementations, different library versions, or CPU versus GPU backends. Bit-for-bit reproducibility is a property of a fixed software environment. Pin your versions and state them.

Treating seed choice as model selection. Trying many seeds and reporting the best one is seed hunting. It inflates results just as effectively as any other form of overfitting to the evaluation. Fix seeds in advance and report the spread across them, as the multi-seed design above illustrates [3].

Random splits that leak. In longitudinal or grouped data, splitting samples at random can place records from the same subject in both training and test sets. A re-analysis of a longitudinal gut microbiome dataset found that machine learning diagnostic models built on that cohort suffered serious data leakage from random sample-level cross-validation splitting, producing artificially inflated AUC values, and fixed it by stratifying cross-validation folds by subject identifier [7]. In any dataset with repeated measures or clusters, the split unit is the subject or group, not the row.

Seeding inside a loop without intention. Calling set.seed() with the same value at the top of every iteration of a simulation gives every replicate the identical random draws. Give each replicate a distinct, recorded seed, for example SEED plus the replicate index.

Believing determinism equals validity. A seeded pipeline regenerates its own errors exactly. Verification protocols for algorithmic methods emphasize an independent check of the method before it is trusted for production use [2]. Schedule that check separately from the reproducibility check.

Forgetting that results can differ in the last decimal place on GPUs. Even with all flags set, some operations lack deterministic implementations and will raise an error under torch.use_deterministic_algorithms(True). Where an error appears, either accept run-to-run variation and report multi-seed statistics, or replace the operation with a deterministic alternative.

Frequently Asked Questions

What is a random seed in simple terms?

It is a starting number that positions a pseudorandom number generator at a known point in its sequence. Set the same seed and the software produces the same sequence of random numbers, which makes your randomized analysis repeatable.

Which seed number should I use, and does 42 matter?

Any non-negative integer works and 42 has no statistical property. Pick a fixed value, record it in your script and your methods section, and choose it before you see any results rather than after comparing outcomes.

Do I need to set a seed separately in R and in Python?

Yes. R, the Python standard library, numpy, and each deep learning framework each maintain their own generator state, and seeding one does not seed the others. Seed every generator your pipeline actually uses.

Why do I still get different results after setting a seed on a GPU?

Some GPU operations, especially certain cuBLAS and convolution routines, have nondeterministic implementations. Set torch.backends.cudnn.deterministic to True and torch.backends.cudnn.benchmark to False in PyTorch, or call tf.config.experimental.enable_op_determinism() in TensorFlow, and replace any operation that still refuses determinism.

What is the difference between np.random.seed and np.random.default_rng?

Comparison of np.random.seed and np.random.default_rng across algorithm, output from seed 42, state handling and recommended use.

np.random.seed controls the legacy global generator, while np.random.default_rng(42) creates a modern Generator object using the PCG64 algorithm. They produce different numbers from the same seed, so use one interface consistently throughout a project and pass the Generator explicitly in new code.

Does setting a seed make my study statistically valid?

No. Seeding makes a run repeatable, which is a property of your code. Validity depends on study design, adequate sample size, correct handling of grouped data, and appropriate inference, none of which a seed addresses.

Should I report the seed in my paper or report?

Yes. Report the seed value, the software and library versions, and any per-seed results from multi-seed repetitions. Reporting checklists in several research fields now treat this level of disclosure as a baseline expectation.

Can two people with the same seed get different results?

They can, if their library versions differ, if they use different hardware backends, or if one of them missed a generator in the pipeline. Bit-for-bit agreement requires the same seed, the same versions, and the same environment, which is why versions belong in the checklist.

Sources

  1. Editorial: Do you see what I see? Quality, reliability, and reproducibility in biomedical research.
  2. Verify and protect before you trust: A practical protocol for AI and algorithmic adoption in methodology development for national statistical offices
  3. A Systematic Multi-Dataset, Multi-Seed Evaluation of Preprocessing Strategies for Retinal Optic Disc and Cup Segmentation.
  4. Reporting checklists in neuroimaging: promoting transparency, replicability, and reproducibility
  5. Reproducibility of Breech Progression Angle: Standardization of Transperineal Measurements and Development of Image-Based Checklist for Quality Control
  6. The reproducibility of research and the misinterpretation of p-values
  7. Gut Microbiota and Metabolic Pathway Signatures for Inflammatory Bowel Disease Identified via Subject-Stratified Random Forest Based on the Longitudinal HMP2 Cohort.