Using `rng=` keyword argument  for NumPy randomness

In SPEC7 https://github.com/scientific-python/specs/pull/180, has two goals:

1. Deprecate the use of `RandomState` and `np.random.seed`
2. Standardize the usage of `rng` for setting seeding.

For 1, according to [NEP19](https://numpy.org/neps/nep-0019-rng-policy.html#numpy-random), I do not think NumPy wants to deprecate `np.random.seed` because they see valid use cases.

For 2, the primary reason around using `rng` instead of `random_state` is that it is a "better name" for NumPy's [Random Generator](https://numpy.org/doc/stable/reference/random/generator.html#random-generator). I am okay with keeping `random_state` and not have users go the pain of changing their code.

Currently, scikit-learn does not support generators because we tied it to https://github.com/scikit-learn/enhancement_proposals/pull/88. We wanted to use generators to cleanly switch to a different RNG behavior compared to RandomState. For me, I think they can be decoupled. If we tackle  https://github.com/scikit-learn/enhancement_proposals/pull/88, we can fix it for both RandomState and Generators.

@scikit-learn/core-devs What do you think of SPEC7's proposal?

COMMENT:
> For 1, according to [NEP19](https://numpy.org/neps/nep-0019-rng-policy.html#numpy-random), I do not think NumPy wants to deprecate `np.random.seed` because they see valid use cases.

To be quite clear, in NEP 19, I am very much staking the ground that `scikit-learn` is *not* one of the valid use cases for `np.random.seed()`. It is a blight upon you and your users (to be clear, one that *I* inflicted). As the NumPy project, per se, we are not going to remove `np.random.seed()`, but as the author of `np.random`, I vigorously encourage you to pretend that it does not exist.

> For 2, the primary reason around using `rng` instead of `random_state` is that it is a "better name" for NumPy's [Random Generator](https://numpy.org/doc/stable/reference/random/generator.html#random-generator). I am okay with keeping `random_state` and not have users go the pain of changing their code.

SPEC7 does not recommend a name change just because we are dealing with `Generator` objects rather than `RandomState` objects. SPEC7 is recommending changing the *semantics* of what the default `random_state=None` does; therefore, one should probably go through a deprecation cycle with a name change to reflect the semantics change. SPEC7 recommends (in the eventual state) that one use `rng=None` keyword arguments, then doing `rng = np.random.default_rng(rng)` to coerce the argument to a `Generator`. `default_rng()` is analogous to `check_random_state()` *except*, crucially, that there is no default global `Generator` like there is a default global `RandomState`. `np.random.default_rng(None)` creates a completely new `Generator`. If there had been no semantics change, we would have recommended retaining the `random_state` name despite dropping `RandomState` instances.

The deprecation lifecycle proposed by SPEC7 has an intermediate state that is somewhat along the lines of "allow people to pass a `Generator` but don't forbid `RandomState` or break current code" but it does involve an `rng=` argument.

> I personally don't mind renaming `random_state` to `rng` IF it unlocks some backward compatibility concerns with _fixing_ `random_state`.

Yes, this is the case.

As for what features using `Generator`s (and `Generator`s alone) can buy you: https://github.com/scikit-learn/scikit-learn/issues/16988#issuecomment-1518037853

Labels: API, Needs Decision, RFC