oversampleqa 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oversampleqa/__init__.py +210 -0
- oversampleqa/_provenance.py +131 -0
- oversampleqa/_render.py +81 -0
- oversampleqa/_rng.py +83 -0
- oversampleqa/advanced_benchmark.py +1076 -0
- oversampleqa/benchmark.py +399 -0
- oversampleqa/caching.py +262 -0
- oversampleqa/cli.py +115 -0
- oversampleqa/cli_enhanced.py +1498 -0
- oversampleqa/clustering.py +98 -0
- oversampleqa/config_templates.py +85 -0
- oversampleqa/deprecation.py +131 -0
- oversampleqa/distance.py +237 -0
- oversampleqa/estimator.py +229 -0
- oversampleqa/exceptions.py +60 -0
- oversampleqa/extended_distances.py +336 -0
- oversampleqa/fidelity.py +755 -0
- oversampleqa/inference.py +1016 -0
- oversampleqa/memory_efficient_validator.py +414 -0
- oversampleqa/metrics.py +295 -0
- oversampleqa/optimized_distance.py +903 -0
- oversampleqa/plotting.py +517 -0
- oversampleqa/plugin_contract.py +296 -0
- oversampleqa/plugin_system.py +362 -0
- oversampleqa/py.typed +0 -0
- oversampleqa/report.py +114 -0
- oversampleqa/reports.py +288 -0
- oversampleqa/surrogate.py +105 -0
- oversampleqa/typed_validator.py +423 -0
- oversampleqa/types.py +378 -0
- oversampleqa/validator.py +860 -0
- oversampleqa-0.5.1.dist-info/METADATA +237 -0
- oversampleqa-0.5.1.dist-info/RECORD +37 -0
- oversampleqa-0.5.1.dist-info/WHEEL +4 -0
- oversampleqa-0.5.1.dist-info/entry_points.txt +4 -0
- oversampleqa-0.5.1.dist-info/licenses/AUTHORS.md +30 -0
- oversampleqa-0.5.1.dist-info/licenses/LICENSE +21 -0
oversampleqa/__init__.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oversampleqa: A diagnostic toolkit for validating oversampling methods.
|
|
3
|
+
|
|
4
|
+
This package implements validation methods for synthetic data generated by
|
|
5
|
+
oversampling techniques like SMOTE, ADASYN, and their variants.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.5.1"
|
|
9
|
+
__author__ = "Diogo Ribeiro"
|
|
10
|
+
__email__ = "dfr@esmad.ipp.pt"
|
|
11
|
+
__orcid__ = "0009-0001-2022-7072"
|
|
12
|
+
|
|
13
|
+
from .advanced_benchmark import (
|
|
14
|
+
DatasetRepository,
|
|
15
|
+
StatisticalBenchmark,
|
|
16
|
+
create_benchmark_report,
|
|
17
|
+
format_statistical_summary,
|
|
18
|
+
)
|
|
19
|
+
from .benchmark import (
|
|
20
|
+
compute_ranking,
|
|
21
|
+
export_benchmark_results,
|
|
22
|
+
load_standard_datasets,
|
|
23
|
+
run_benchmark,
|
|
24
|
+
)
|
|
25
|
+
from .caching import ValidationCache
|
|
26
|
+
from .cli import main as cli_main
|
|
27
|
+
from .clustering import cluster_based_diagnostics
|
|
28
|
+
from .deprecation import deprecated
|
|
29
|
+
from .distance import (
|
|
30
|
+
braycurtis_distance,
|
|
31
|
+
canberra_distance,
|
|
32
|
+
chebyshev_distance,
|
|
33
|
+
correlation_distance,
|
|
34
|
+
distance_matrix,
|
|
35
|
+
energy_distance,
|
|
36
|
+
hamming_distance,
|
|
37
|
+
hassanat_distance,
|
|
38
|
+
hellinger_distance,
|
|
39
|
+
jaccard_distance,
|
|
40
|
+
jensen_shannon_distance,
|
|
41
|
+
mahalanobis_distance,
|
|
42
|
+
minkowski_distance,
|
|
43
|
+
wasserstein_1d_distance,
|
|
44
|
+
)
|
|
45
|
+
from .estimator import OversamplingValidator, validation_scorer
|
|
46
|
+
from .fidelity import (
|
|
47
|
+
BoundaryReport,
|
|
48
|
+
FidelityReport,
|
|
49
|
+
ManifoldMetrics,
|
|
50
|
+
MemorisationReport,
|
|
51
|
+
boundary_violation_rate,
|
|
52
|
+
fidelity_report,
|
|
53
|
+
memorisation_report,
|
|
54
|
+
precision_recall_density_coverage,
|
|
55
|
+
)
|
|
56
|
+
from .inference import (
|
|
57
|
+
NullCalibration,
|
|
58
|
+
TwoSampleTestResult,
|
|
59
|
+
cross_match_test,
|
|
60
|
+
mst_two_sample_test,
|
|
61
|
+
nn_two_sample_test,
|
|
62
|
+
null_error_rate,
|
|
63
|
+
)
|
|
64
|
+
from .memory_efficient_validator import MemoryEfficientValidator
|
|
65
|
+
from .metrics import (
|
|
66
|
+
calculate_error_rate,
|
|
67
|
+
check_model_fairness,
|
|
68
|
+
confidence_ratio,
|
|
69
|
+
duplication_rate,
|
|
70
|
+
local_density_divergence,
|
|
71
|
+
minority_recall_loss,
|
|
72
|
+
noise_sensitivity_diagnostic,
|
|
73
|
+
umap_manifold_distance,
|
|
74
|
+
)
|
|
75
|
+
from .optimized_distance import OptimizedDistanceMatrix
|
|
76
|
+
from .plotting import (
|
|
77
|
+
plot_class_balance,
|
|
78
|
+
plot_distance_histogram,
|
|
79
|
+
plot_error_boxplot,
|
|
80
|
+
plot_error_comparison,
|
|
81
|
+
plot_error_heatmap,
|
|
82
|
+
plot_error_ranking,
|
|
83
|
+
plot_noise_sensitivity,
|
|
84
|
+
plot_sample_distribution,
|
|
85
|
+
)
|
|
86
|
+
from .plugin_contract import (
|
|
87
|
+
METRIC_DOMAINS,
|
|
88
|
+
AxiomReport,
|
|
89
|
+
MetricPlugin,
|
|
90
|
+
check_metric_axioms,
|
|
91
|
+
)
|
|
92
|
+
from .plugin_system import plugin_manager, register_metric, register_validator
|
|
93
|
+
from .report import generate_report
|
|
94
|
+
from .reports import SCHEMA_VERSION, RunMetadata, ValidationReport
|
|
95
|
+
from .surrogate import evaluate_surrogate_models
|
|
96
|
+
from .typed_validator import (
|
|
97
|
+
PydanticValidationConfig,
|
|
98
|
+
TypedValidator,
|
|
99
|
+
registry,
|
|
100
|
+
validation_session,
|
|
101
|
+
)
|
|
102
|
+
from .types import (
|
|
103
|
+
BenchmarkConfig,
|
|
104
|
+
ConfigurationError,
|
|
105
|
+
MetricError,
|
|
106
|
+
OversampleQAError,
|
|
107
|
+
ReferenceSet,
|
|
108
|
+
ValidationConfig,
|
|
109
|
+
ValidationDetails,
|
|
110
|
+
ValidationError,
|
|
111
|
+
ValidationMode,
|
|
112
|
+
ValidationResult,
|
|
113
|
+
)
|
|
114
|
+
from .validator import (
|
|
115
|
+
extract_synthetic_samples,
|
|
116
|
+
validate_multiclass_oversampling,
|
|
117
|
+
validate_oversampling,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
__all__ = [
|
|
121
|
+
"METRIC_DOMAINS",
|
|
122
|
+
"SCHEMA_VERSION",
|
|
123
|
+
"AxiomReport",
|
|
124
|
+
"BenchmarkConfig",
|
|
125
|
+
"BoundaryReport",
|
|
126
|
+
"ConfigurationError",
|
|
127
|
+
"DatasetRepository",
|
|
128
|
+
"FidelityReport",
|
|
129
|
+
"ManifoldMetrics",
|
|
130
|
+
"MemorisationReport",
|
|
131
|
+
"MemoryEfficientValidator",
|
|
132
|
+
"MetricError",
|
|
133
|
+
"MetricPlugin",
|
|
134
|
+
"NullCalibration",
|
|
135
|
+
"OptimizedDistanceMatrix",
|
|
136
|
+
"OversampleQAError",
|
|
137
|
+
"OversamplingValidator",
|
|
138
|
+
"PydanticValidationConfig",
|
|
139
|
+
"ReferenceSet",
|
|
140
|
+
"RunMetadata",
|
|
141
|
+
"StatisticalBenchmark",
|
|
142
|
+
"TwoSampleTestResult",
|
|
143
|
+
"TypedValidator",
|
|
144
|
+
"ValidationCache",
|
|
145
|
+
"ValidationConfig",
|
|
146
|
+
"ValidationDetails",
|
|
147
|
+
"ValidationError",
|
|
148
|
+
"ValidationMode",
|
|
149
|
+
"ValidationReport",
|
|
150
|
+
"ValidationResult",
|
|
151
|
+
"boundary_violation_rate",
|
|
152
|
+
"braycurtis_distance",
|
|
153
|
+
"calculate_error_rate",
|
|
154
|
+
"canberra_distance",
|
|
155
|
+
"chebyshev_distance",
|
|
156
|
+
"check_metric_axioms",
|
|
157
|
+
"check_model_fairness",
|
|
158
|
+
"cli_main",
|
|
159
|
+
"cluster_based_diagnostics",
|
|
160
|
+
"compute_ranking",
|
|
161
|
+
"confidence_ratio",
|
|
162
|
+
"correlation_distance",
|
|
163
|
+
"create_benchmark_report",
|
|
164
|
+
"cross_match_test",
|
|
165
|
+
"deprecated",
|
|
166
|
+
"distance_matrix",
|
|
167
|
+
"duplication_rate",
|
|
168
|
+
"energy_distance",
|
|
169
|
+
"evaluate_surrogate_models",
|
|
170
|
+
"export_benchmark_results",
|
|
171
|
+
"extract_synthetic_samples",
|
|
172
|
+
"fidelity_report",
|
|
173
|
+
"format_statistical_summary",
|
|
174
|
+
"generate_report",
|
|
175
|
+
"hamming_distance",
|
|
176
|
+
"hassanat_distance",
|
|
177
|
+
"hellinger_distance",
|
|
178
|
+
"jaccard_distance",
|
|
179
|
+
"jensen_shannon_distance",
|
|
180
|
+
"load_standard_datasets",
|
|
181
|
+
"local_density_divergence",
|
|
182
|
+
"mahalanobis_distance",
|
|
183
|
+
"memorisation_report",
|
|
184
|
+
"minkowski_distance",
|
|
185
|
+
"minority_recall_loss",
|
|
186
|
+
"mst_two_sample_test",
|
|
187
|
+
"nn_two_sample_test",
|
|
188
|
+
"noise_sensitivity_diagnostic",
|
|
189
|
+
"null_error_rate",
|
|
190
|
+
"plot_class_balance",
|
|
191
|
+
"plot_distance_histogram",
|
|
192
|
+
"plot_error_boxplot",
|
|
193
|
+
"plot_error_comparison",
|
|
194
|
+
"plot_error_heatmap",
|
|
195
|
+
"plot_error_ranking",
|
|
196
|
+
"plot_noise_sensitivity",
|
|
197
|
+
"plot_sample_distribution",
|
|
198
|
+
"plugin_manager",
|
|
199
|
+
"precision_recall_density_coverage",
|
|
200
|
+
"register_metric",
|
|
201
|
+
"register_validator",
|
|
202
|
+
"registry",
|
|
203
|
+
"run_benchmark",
|
|
204
|
+
"umap_manifold_distance",
|
|
205
|
+
"validate_multiclass_oversampling",
|
|
206
|
+
"validate_oversampling",
|
|
207
|
+
"validation_scorer",
|
|
208
|
+
"validation_session",
|
|
209
|
+
"wasserstein_1d_distance",
|
|
210
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Provenance metadata for benchmark datasets.
|
|
2
|
+
|
|
3
|
+
A benchmark result is only interpretable if you can say where its data came
|
|
4
|
+
from. These helpers build one consistent record for that, so the two catalogs --
|
|
5
|
+
:func:`~oversampleqa.benchmark.load_standard_datasets` and
|
|
6
|
+
:class:`~oversampleqa.advanced_benchmark.DatasetRepository` -- describe their
|
|
7
|
+
datasets the same way rather than each inventing a shape.
|
|
8
|
+
|
|
9
|
+
Every record carries the same six keys:
|
|
10
|
+
|
|
11
|
+
``source``
|
|
12
|
+
``"synthetic"``, ``"bundled"`` or ``"OpenML"``. The coarse question of
|
|
13
|
+
whether the data is generated, shipped with scikit-learn, or downloaded.
|
|
14
|
+
``generator``
|
|
15
|
+
The fully-qualified callable that produced or fetched it.
|
|
16
|
+
``params``
|
|
17
|
+
Arguments passed, including any ``random_state``. For synthetic data this
|
|
18
|
+
is sufficient to regenerate the dataset exactly.
|
|
19
|
+
``url``
|
|
20
|
+
Where a human can read about it.
|
|
21
|
+
``license``
|
|
22
|
+
Terms. "Unknown" is a legitimate value and is better than omitting the key,
|
|
23
|
+
because omission reads as "no restrictions" to a hurried reader.
|
|
24
|
+
``notes``
|
|
25
|
+
Anything a reader needs in order not to misread the numbers -- applied
|
|
26
|
+
preprocessing, truncation, known caveats.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"SKLEARN_LICENSE",
|
|
35
|
+
"bundled_provenance",
|
|
36
|
+
"openml_provenance",
|
|
37
|
+
"synthetic_provenance",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
SKLEARN_LICENSE = "BSD-3-Clause (scikit-learn synthetic generator)"
|
|
41
|
+
|
|
42
|
+
_SKLEARN_GENERATORS_URL = (
|
|
43
|
+
"https://scikit-learn.org/stable/datasets/sample_generators.html"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def synthetic_provenance(generator: str, **params: Any) -> dict[str, Any]:
|
|
48
|
+
"""Build provenance for a scikit-learn synthetic dataset.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
generator: Fully-qualified name of the generator used.
|
|
52
|
+
**params: Generation parameters, including ``random_state``.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A provenance record.
|
|
56
|
+
"""
|
|
57
|
+
return {
|
|
58
|
+
"source": "synthetic",
|
|
59
|
+
"generator": generator,
|
|
60
|
+
"params": params,
|
|
61
|
+
"url": _SKLEARN_GENERATORS_URL,
|
|
62
|
+
"license": SKLEARN_LICENSE,
|
|
63
|
+
"notes": (
|
|
64
|
+
"Generated deterministically from the fixed random_state; "
|
|
65
|
+
"not real-world data."
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def bundled_provenance(
|
|
71
|
+
generator: str,
|
|
72
|
+
*,
|
|
73
|
+
url: str,
|
|
74
|
+
license: str,
|
|
75
|
+
notes: str = "",
|
|
76
|
+
**params: Any,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
"""Build provenance for a dataset shipped inside scikit-learn.
|
|
79
|
+
|
|
80
|
+
Bundled data is reproducible in the sense that it does not change between
|
|
81
|
+
runs, but it is real-world data with its own citation and terms, so it is
|
|
82
|
+
recorded separately from synthetic data rather than lumped in with it.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
generator: Fully-qualified loader, e.g. ``sklearn.datasets.load_breast_cancer``.
|
|
86
|
+
url: Where the dataset is documented.
|
|
87
|
+
license: Terms of use.
|
|
88
|
+
notes: Caveats a reader needs, such as applied truncation.
|
|
89
|
+
**params: Arguments passed to the loader.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
A provenance record.
|
|
93
|
+
"""
|
|
94
|
+
return {
|
|
95
|
+
"source": "bundled",
|
|
96
|
+
"generator": generator,
|
|
97
|
+
"params": params,
|
|
98
|
+
"url": url,
|
|
99
|
+
"license": license,
|
|
100
|
+
"notes": notes,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def openml_provenance(
|
|
105
|
+
name: str,
|
|
106
|
+
version: int,
|
|
107
|
+
*,
|
|
108
|
+
notes: str = "",
|
|
109
|
+
) -> dict[str, Any]:
|
|
110
|
+
"""Build provenance for a dataset fetched from OpenML.
|
|
111
|
+
|
|
112
|
+
The version is part of the record because it is the only thing standing
|
|
113
|
+
between a "reproducible" benchmark and an upstream dataset being silently
|
|
114
|
+
replaced under it.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: OpenML dataset name.
|
|
118
|
+
version: Pinned OpenML version.
|
|
119
|
+
notes: Preprocessing or caveats.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
A provenance record.
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
"source": "OpenML",
|
|
126
|
+
"generator": "sklearn.datasets.fetch_openml",
|
|
127
|
+
"params": {"name": name, "version": version},
|
|
128
|
+
"url": f"https://www.openml.org/search?type=data&q={name}",
|
|
129
|
+
"license": "Varies per dataset; see the OpenML page.",
|
|
130
|
+
"notes": notes or f"Downloaded from OpenML with the version pinned to {version}.",
|
|
131
|
+
}
|
oversampleqa/_render.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Frame rendering shared by the report and export paths.
|
|
2
|
+
|
|
3
|
+
Lives here rather than in ``report`` because ``report`` imports from
|
|
4
|
+
``benchmark``, so ``benchmark`` importing back from ``report`` would be a
|
|
5
|
+
cycle -- and both need to render a frame as Markdown. Two copies is how the
|
|
6
|
+
``to_csv(sep="|")`` bug survived in one of them after being fixed in the
|
|
7
|
+
other.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
__all__ = ["frame_to_html", "frame_to_markdown"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def frame_to_markdown(frame: pd.DataFrame, *, float_format: str = "{:.4f}") -> str:
|
|
20
|
+
"""Render a DataFrame as a GitHub-flavoured Markdown table.
|
|
21
|
+
|
|
22
|
+
Written out rather than delegated to ``DataFrame.to_markdown``, which needs
|
|
23
|
+
``tabulate``. That is installed here only as a transitive dependency of
|
|
24
|
+
something else, and depending on a package nobody declared is how a working
|
|
25
|
+
install becomes a broken one after an unrelated upgrade.
|
|
26
|
+
|
|
27
|
+
The previous implementation used ``to_csv(sep="|")``, which is not Markdown:
|
|
28
|
+
it has no header separator row and no leading or trailing pipes, so it
|
|
29
|
+
rendered as one run-on paragraph rather than a table.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
frame: Frame to render. The index becomes the first column when it is
|
|
33
|
+
named, since ``compute_ranking`` returns the oversampler there.
|
|
34
|
+
float_format: Format applied to floating-point cells. Raw repr leaks
|
|
35
|
+
values like ``0.21000000000000002`` into a document meant to be read.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A Markdown table, or a note when the frame is empty.
|
|
39
|
+
"""
|
|
40
|
+
if frame.empty:
|
|
41
|
+
return "_No results._"
|
|
42
|
+
|
|
43
|
+
display = frame.reset_index() if frame.index.name else frame.copy()
|
|
44
|
+
|
|
45
|
+
def render(value: Any) -> str:
|
|
46
|
+
if isinstance(value, float):
|
|
47
|
+
return float_format.format(value)
|
|
48
|
+
return str(value)
|
|
49
|
+
|
|
50
|
+
headers = [str(c) for c in display.columns]
|
|
51
|
+
rows = [[render(v) for v in row] for row in display.itertuples(index=False)]
|
|
52
|
+
|
|
53
|
+
widths = [
|
|
54
|
+
max(len(headers[i]), *(len(r[i]) for r in rows)) if rows else len(headers[i])
|
|
55
|
+
for i in range(len(headers))
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
def line(cells: list[str]) -> str:
|
|
59
|
+
padded = [c.ljust(w) for c, w in zip(cells, widths, strict=True)]
|
|
60
|
+
return "| " + " | ".join(padded) + " |"
|
|
61
|
+
|
|
62
|
+
separator = "| " + " | ".join("-" * w for w in widths) + " |"
|
|
63
|
+
return "\n".join([line(headers), separator, *(line(r) for r in rows)])
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def frame_to_html(frame: pd.DataFrame, *, float_format: str = "{:.4f}") -> str:
|
|
67
|
+
"""Render a DataFrame as an HTML table.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
frame: Frame to render. A named index becomes the first column.
|
|
71
|
+
float_format: Format applied to floating-point cells.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
An HTML table, or a note when the frame is empty.
|
|
75
|
+
"""
|
|
76
|
+
if frame.empty:
|
|
77
|
+
return "<p><em>No results.</em></p>"
|
|
78
|
+
display = frame.reset_index() if frame.index.name else frame
|
|
79
|
+
# pandas ships no type information, so to_html is typed Any. str() makes
|
|
80
|
+
# the declared return type honest instead of suppressing the error.
|
|
81
|
+
return str(display.to_html(index=False, float_format=float_format.format))
|
oversampleqa/_rng.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Random-number normalisation shared by every validator.
|
|
2
|
+
|
|
3
|
+
Seeding behaviour lives here so it cannot drift between the validators again.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
__all__ = ["RandomStateLike", "as_generator", "spawn_generators"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
RandomStateLike = int | np.random.Generator | np.random.SeedSequence | None
|
|
14
|
+
"""Anything accepted as a seed by the validators."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def as_generator(random_state: RandomStateLike) -> np.random.Generator:
|
|
18
|
+
"""Normalise a seed-like value to a :class:`numpy.random.Generator`.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
random_state : int, Generator, SeedSequence or None
|
|
23
|
+
``None`` draws from fresh entropy, meaning results are not
|
|
24
|
+
reproducible. An ``int`` seeds a new generator. A ``Generator`` is
|
|
25
|
+
returned unchanged, so callers can thread one generator through a whole
|
|
26
|
+
pipeline and keep a single stream.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
numpy.random.Generator
|
|
31
|
+
"""
|
|
32
|
+
if isinstance(random_state, np.random.Generator):
|
|
33
|
+
return random_state
|
|
34
|
+
if isinstance(random_state, np.random.SeedSequence):
|
|
35
|
+
return np.random.default_rng(random_state)
|
|
36
|
+
return np.random.default_rng(random_state)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def spawn_generators(
|
|
40
|
+
random_state: RandomStateLike, n: int
|
|
41
|
+
) -> list[np.random.Generator]:
|
|
42
|
+
"""Return ``n`` independent generators derived from ``random_state``.
|
|
43
|
+
|
|
44
|
+
Uses :class:`numpy.random.SeedSequence` spawning, which guarantees the
|
|
45
|
+
streams are statistically independent.
|
|
46
|
+
|
|
47
|
+
Deriving repeat seeds as ``seed + i`` would **not** be safe: consecutive
|
|
48
|
+
integer seeds produce correlated streams, so repeats built that way share
|
|
49
|
+
structure and the resulting dispersion is understated.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
random_state : int, Generator, SeedSequence or None
|
|
54
|
+
Parent seed.
|
|
55
|
+
n : int
|
|
56
|
+
Number of child generators.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
list of numpy.random.Generator
|
|
61
|
+
"""
|
|
62
|
+
if n < 1:
|
|
63
|
+
raise ValueError(f"n must be at least 1; got {n}")
|
|
64
|
+
|
|
65
|
+
if isinstance(random_state, np.random.SeedSequence):
|
|
66
|
+
parent = random_state
|
|
67
|
+
elif isinstance(random_state, np.random.Generator):
|
|
68
|
+
# Draw a fresh entropy value from the supplied generator so repeated
|
|
69
|
+
# calls on the same generator do not replay the same children.
|
|
70
|
+
parent = np.random.SeedSequence(int(random_state.integers(0, 2**63 - 1)))
|
|
71
|
+
else:
|
|
72
|
+
parent = np.random.SeedSequence(random_state)
|
|
73
|
+
|
|
74
|
+
return [np.random.default_rng(child) for child in parent.spawn(n)]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def integer_seed(rng: np.random.Generator) -> int:
|
|
78
|
+
"""Draw an integer seed from ``rng``.
|
|
79
|
+
|
|
80
|
+
For interoperability with scikit-learn estimators, which accept
|
|
81
|
+
``int``/``RandomState`` but not ``Generator``.
|
|
82
|
+
"""
|
|
83
|
+
return int(rng.integers(0, 2**31 - 1))
|