A seed is a reproducibility aid, not a source of statistical independence or a complete experiment description. Record code, data, configuration and package versions too. Give each experiment a local random generator instead of changing a process-wide random stream used by unrelated code.
The test split receives fitted preprocessing parameters, but does not contribute to fitting them.
import numpy as np
a = np.random.default_rng(7).permutation(6)
b = np.random.default_rng(7).permutation(6)
print(np.array_equal(a, b))
print(sorted(a.tolist()))Split before fitting preprocessing or selecting thresholds. Training data fits parameters; development data chooses approaches; held-out test data estimates the final result. Repeatedly choosing based on a test set turns it into another development set. Grouped or temporally related examples may require a grouped or chronological split; a random index split alone does not prevent that leakage.
import numpy as np
train = np.array([[0.0, 2.0], [2.0, 2.0]])
mean = train.mean(axis=0)
scale = train.std(axis=0) # population standard deviation here
scale = np.where(scale == 0, 1.0, scale)
heldout = np.array([[10.0, 2.0]])
print((heldout - mean) / scale)Fitting mean/scale on combined train and held-out rows leaks test distribution information. Transforming the held-out rows with training parameters is fine. Constant training features use scale one, keeping outputs finite without forcing held-out values to zero. This exercise uses ddof=0 by contract.
One local seeded permutation can be sliced into adjacent, nonoverlapping ranges without touching a global random stream.
The build splits a permutation once into explicit counts and returns reusable training parameters. Check complete coverage, no overlap, same-seed equality, unchanged global RNG state and held-out values far outside the training range. The last case makes leakage visible even when ordinary examples look correct.