debiased-inference 0.1.0__tar.gz

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.
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ .Rhistory
3
+ .RData
4
+ .Rproj.user/
5
+ *.Rcheck/
6
+ /*.tar.gz
7
+ python/.pytest_cache/
8
+ python/.coverage
9
+ python/.mypy_cache/
10
+ python/.ruff_cache/
11
+ python/.venv/
12
+ python/build/
13
+ python/dist/
14
+ python/src/*.egg-info/
15
+ __pycache__/
16
+ *.py[cod]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gang Cheng and Yen-Chi Chen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.5
2
+ Name: debiased-inference
3
+ Version: 0.1.0
4
+ Summary: Bootstrap inference with debiased nonparametric estimators
5
+ Project-URL: Homepage, https://github.com/mathcg/debiased-inference
6
+ Project-URL: Repository, https://github.com/mathcg/debiased-inference.git
7
+ Project-URL: Issues, https://github.com/mathcg/debiased-inference/issues
8
+ Project-URL: Paper, https://doi.org/10.1214/19-EJS1575
9
+ Author: Gang Cheng, Yen-Chi Chen
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Gang Cheng and Yen-Chi Chen
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: bootstrap,confidence-bands,kernel-density,nonparametric-regression
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
41
+ Requires-Python: >=3.10
42
+ Requires-Dist: numpy>=1.23
43
+ Provides-Extra: dev
44
+ Requires-Dist: ruff>=0.6; extra == 'dev'
45
+ Provides-Extra: test
46
+ Requires-Dist: pytest-cov>=4; extra == 'test'
47
+ Requires-Dist: pytest>=7; extra == 'test'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # debiased-inference (Python)
51
+
52
+ Python implementation of the procedures in Cheng and Chen,
53
+ [“Nonparametric Inference via Bootstrapping the Debiased
54
+ Estimator”](https://projecteuclid.org/journalArticle/Download?urlId=10.1214%2F19-EJS1575).
55
+
56
+ If you use these methods or this software, please cite the paper (Electronic
57
+ Journal of Statistics 13(1), 2019; doi:10.1214/19-EJS1575).
58
+
59
+ ```python
60
+ import numpy as np
61
+ from debiased_inference import kde_confidence_band
62
+
63
+ rng = np.random.default_rng(2026)
64
+ sample = rng.normal(size=300)
65
+ band = kde_confidence_band(sample, n_boot=499, random_state=2026)
66
+ print(band)
67
+ ```
68
+
69
+ The package also provides debiased local-linear regression, density level-set
70
+ and inverse-regression confidence sets, normal-reference and cross-validated
71
+ bandwidth selectors, and a studentized density band. The project repository
72
+ contains the full statistical specification and cross-language API guide.
@@ -0,0 +1,23 @@
1
+ # debiased-inference (Python)
2
+
3
+ Python implementation of the procedures in Cheng and Chen,
4
+ [“Nonparametric Inference via Bootstrapping the Debiased
5
+ Estimator”](https://projecteuclid.org/journalArticle/Download?urlId=10.1214%2F19-EJS1575).
6
+
7
+ If you use these methods or this software, please cite the paper (Electronic
8
+ Journal of Statistics 13(1), 2019; doi:10.1214/19-EJS1575).
9
+
10
+ ```python
11
+ import numpy as np
12
+ from debiased_inference import kde_confidence_band
13
+
14
+ rng = np.random.default_rng(2026)
15
+ sample = rng.normal(size=300)
16
+ band = kde_confidence_band(sample, n_boot=499, random_state=2026)
17
+ print(band)
18
+ ```
19
+
20
+ The package also provides debiased local-linear regression, density level-set
21
+ and inverse-regression confidence sets, normal-reference and cross-validated
22
+ bandwidth selectors, and a studentized density band. The project repository
23
+ contains the full statistical specification and cross-language API guide.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "debiased-inference"
7
+ version = "0.1.0"
8
+ description = "Bootstrap inference with debiased nonparametric estimators"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {file = "LICENSE"}
12
+ authors = [
13
+ {name = "Gang Cheng"},
14
+ {name = "Yen-Chi Chen"},
15
+ ]
16
+ keywords = ["bootstrap", "confidence-bands", "kernel-density", "nonparametric-regression"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Science/Research",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Scientific/Engineering :: Mathematics",
26
+ ]
27
+ dependencies = ["numpy>=1.23"]
28
+
29
+ [project.optional-dependencies]
30
+ test = ["pytest>=7", "pytest-cov>=4"]
31
+ dev = ["ruff>=0.6"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/mathcg/debiased-inference"
35
+ Repository = "https://github.com/mathcg/debiased-inference.git"
36
+ Issues = "https://github.com/mathcg/debiased-inference/issues"
37
+ Paper = "https://doi.org/10.1214/19-EJS1575"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/debiased_inference"]
41
+
42
+ [tool.hatch.build]
43
+ exclude = ["/.coverage"]
44
+
45
+ [tool.pytest.ini_options]
46
+ addopts = "-ra --strict-markers"
47
+ testpaths = ["tests"]
48
+
49
+ [tool.ruff]
50
+ line-length = 100
51
+ target-version = "py310"
@@ -0,0 +1,36 @@
1
+ """Bootstrap inference with debiased nonparametric estimators."""
2
+
3
+ from ._results import ConfidenceBandResult, EstimateResult, SetEstimateResult
4
+ from .bandwidth import density_bandwidth, regression_bandwidth
5
+ from .kde import debiased_kde, kde_confidence_band
6
+ from .regression import debiased_local_linear, regression_confidence_band
7
+ from .sets import (
8
+ density_level_set,
9
+ density_level_set_confidence,
10
+ hausdorff_distance,
11
+ inverse_regression,
12
+ inverse_regression_confidence,
13
+ invert_confidence_band,
14
+ level_set,
15
+ )
16
+
17
+ __all__ = [
18
+ "ConfidenceBandResult",
19
+ "EstimateResult",
20
+ "SetEstimateResult",
21
+ "debiased_kde",
22
+ "debiased_local_linear",
23
+ "density_bandwidth",
24
+ "density_level_set",
25
+ "density_level_set_confidence",
26
+ "hausdorff_distance",
27
+ "inverse_regression",
28
+ "inverse_regression_confidence",
29
+ "invert_confidence_band",
30
+ "kde_confidence_band",
31
+ "level_set",
32
+ "regression_bandwidth",
33
+ "regression_confidence_band",
34
+ ]
35
+
36
+ __version__ = "0.1.0"
@@ -0,0 +1,90 @@
1
+ """Immutable result containers for the public API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ import numpy as np
8
+ from numpy.typing import NDArray
9
+
10
+ FloatArray = NDArray[np.float64]
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class EstimateResult:
15
+ """A nonparametric estimate evaluated on a finite grid."""
16
+
17
+ points: FloatArray
18
+ estimate: FloatArray
19
+ bandwidth: float
20
+ tau: float
21
+ method: str
22
+
23
+ def __repr__(self) -> str:
24
+ return (
25
+ f"EstimateResult(method={self.method!r}, points={len(self.points)}, "
26
+ f"bandwidth={self.bandwidth:.6g}, tau={self.tau:.6g})"
27
+ )
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class ConfidenceBandResult:
32
+ """A simultaneous bootstrap confidence band on a finite grid."""
33
+
34
+ points: FloatArray
35
+ estimate: FloatArray
36
+ lower: FloatArray
37
+ upper: FloatArray
38
+ critical_value: float
39
+ bandwidth: float
40
+ tau: float
41
+ confidence: float
42
+ n_boot: int
43
+ bootstrap_statistics: FloatArray
44
+ method: str
45
+ studentized: bool = False
46
+ standard_error: FloatArray | None = None
47
+
48
+ @property
49
+ def width(self) -> FloatArray:
50
+ """Pointwise width of the simultaneous band."""
51
+ return self.upper - self.lower
52
+
53
+ def __repr__(self) -> str:
54
+ return (
55
+ f"ConfidenceBandResult(method={self.method!r}, points={len(self.points)}, "
56
+ f"confidence={self.confidence:.3g}, n_boot={self.n_boot}, "
57
+ f"bandwidth={self.bandwidth:.6g})"
58
+ )
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class SetEstimateResult:
63
+ """A grid-based estimate or confidence region for a level set.
64
+
65
+ ``roots`` is a vector for a one-dimensional level set and an ``(k, 2)``
66
+ contour point cloud for a two-dimensional level set.
67
+ """
68
+
69
+ points: FloatArray
70
+ mask: NDArray[np.bool_]
71
+ roots: FloatArray
72
+ level: float
73
+ method: str
74
+ radius: float | None = None
75
+ confidence: float | None = None
76
+ n_boot: int | None = None
77
+ bootstrap_statistics: FloatArray | None = None
78
+
79
+ @property
80
+ def geometry(self) -> FloatArray:
81
+ """Return the interpolated level-set geometry."""
82
+ return self.roots
83
+
84
+ def __repr__(self) -> str:
85
+ size = len(self.roots)
86
+ detail = f", radius={self.radius:.6g}" if self.radius is not None else ""
87
+ return (
88
+ f"SetEstimateResult(method={self.method!r}, level={self.level:.6g}, "
89
+ f"geometry_points={size}{detail})"
90
+ )
@@ -0,0 +1,81 @@
1
+ """Shared input validation helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ from numpy.typing import ArrayLike, NDArray
7
+
8
+ FloatArray = NDArray[np.float64]
9
+
10
+
11
+ def as_samples(x: ArrayLike, *, name: str = "x") -> FloatArray:
12
+ """Return observations as an ``(n, d)`` float array."""
13
+ values = np.asarray(x, dtype=float)
14
+ if values.ndim == 1:
15
+ values = values[:, None]
16
+ if values.ndim != 2 or values.shape[0] < 2 or values.shape[1] < 1:
17
+ raise ValueError(f"{name} must contain at least two observations")
18
+ if not np.all(np.isfinite(values)):
19
+ raise ValueError(f"{name} must contain only finite values")
20
+ if np.all(np.ptp(values, axis=0) == 0):
21
+ raise ValueError(f"{name} must contain at least two distinct observations")
22
+ return values
23
+
24
+
25
+ def as_vector(x: ArrayLike, *, name: str) -> FloatArray:
26
+ """Return a finite, non-empty one-dimensional float array."""
27
+ values = np.asarray(x, dtype=float)
28
+ if values.ndim != 1 or values.size == 0:
29
+ raise ValueError(f"{name} must be a non-empty one-dimensional array")
30
+ if not np.all(np.isfinite(values)):
31
+ raise ValueError(f"{name} must contain only finite values")
32
+ return values
33
+
34
+
35
+ def as_evaluation_points(
36
+ points: ArrayLike | None, samples: FloatArray, *, grid_size: int = 200
37
+ ) -> FloatArray:
38
+ """Validate or generate evaluation points with the samples' dimension."""
39
+ dimension = samples.shape[1]
40
+ if points is None:
41
+ if dimension != 1:
42
+ raise ValueError("evaluation points are required for multivariate data")
43
+ if not isinstance(grid_size, (int, np.integer)) or grid_size < 2:
44
+ raise ValueError("grid_size must be an integer of at least 2")
45
+ spread = float(np.std(samples[:, 0], ddof=1))
46
+ pad = 0.05 * max(float(np.ptp(samples[:, 0])), spread)
47
+ if pad == 0:
48
+ pad = 1.0
49
+ return np.linspace(samples[:, 0].min() - pad, samples[:, 0].max() + pad, grid_size)[:, None]
50
+
51
+ result = np.asarray(points, dtype=float)
52
+ if dimension == 1 and result.ndim == 1:
53
+ result = result[:, None]
54
+ if result.ndim != 2 or result.shape[1] != dimension or result.shape[0] == 0:
55
+ raise ValueError(f"evaluation points must have shape (m, {dimension})")
56
+ if not np.all(np.isfinite(result)):
57
+ raise ValueError("evaluation points must contain only finite values")
58
+ return result
59
+
60
+
61
+ def positive_scalar(value: float, *, name: str) -> float:
62
+ """Validate a finite positive scalar."""
63
+ result = float(value)
64
+ if not np.isfinite(result) or result <= 0:
65
+ raise ValueError(f"{name} must be a finite positive scalar")
66
+ return result
67
+
68
+
69
+ def bootstrap_parameters(confidence: float, n_boot: int) -> tuple[float, int]:
70
+ """Validate confidence level and bootstrap count."""
71
+ confidence = float(confidence)
72
+ if not 0 < confidence < 1:
73
+ raise ValueError("confidence must be strictly between 0 and 1")
74
+ if not isinstance(n_boot, (int, np.integer)) or n_boot < 1:
75
+ raise ValueError("n_boot must be a positive integer")
76
+ return confidence, int(n_boot)
77
+
78
+
79
+ def public_points(points: FloatArray) -> FloatArray:
80
+ """Represent one-dimensional grids as vectors in public results."""
81
+ return points[:, 0].copy() if points.shape[1] == 1 else points.copy()
@@ -0,0 +1,144 @@
1
+ """Bandwidth selectors for ordinary KDE and local-linear regression."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ from numpy.typing import ArrayLike
7
+
8
+ from ._validation import as_samples, as_vector, positive_scalar
9
+
10
+
11
+ def density_bandwidth(
12
+ x: ArrayLike,
13
+ *,
14
+ method: str = "normal_reference",
15
+ candidates: ArrayLike | None = None,
16
+ block_size: int = 512,
17
+ ) -> float:
18
+ """Select an isotropic bandwidth for the ordinary KDE.
19
+
20
+ ``method="normal_reference"`` uses Silverman's robust rule
21
+ ``0.9 min(sd, IQR / 1.34) n^(-1/5)``. In higher dimensions the scalar
22
+ scale is the geometric mean of positive marginal robust scales and the
23
+ normal-reference dimension adjustment is used.
24
+
25
+ ``method="cv"`` minimizes the least-squares cross-validation criterion
26
+ over ``candidates``. If candidates are omitted, a geometric grid centered
27
+ on the normal-reference choice is used. Pairwise calculations are blocked
28
+ to use ``O(n * block_size)`` rather than ``O(n^2)`` memory.
29
+ """
30
+ samples = as_samples(x)
31
+ if method not in {"normal_reference", "cv"}:
32
+ raise ValueError("method must be 'normal_reference' or 'cv'")
33
+ n, dimension = samples.shape
34
+ standard_deviation = np.std(samples, axis=0, ddof=1)
35
+ quartiles = np.percentile(samples, [25, 75], axis=0)
36
+ robust = (quartiles[1] - quartiles[0]) / 1.34
37
+ scales = np.minimum(standard_deviation, robust)
38
+ scales = np.where(scales > 0, scales, standard_deviation)
39
+ positive = scales[scales > 0]
40
+ if positive.size == 0:
41
+ raise ValueError("cannot select bandwidth from zero-scale data")
42
+ scale = float(np.exp(np.mean(np.log(positive))))
43
+ if dimension == 1:
44
+ factor = 0.9 * n ** (-1.0 / 5.0)
45
+ else:
46
+ factor = (4.0 / (dimension + 2.0)) ** (1.0 / (dimension + 4.0))
47
+ factor *= n ** (-1.0 / (dimension + 4.0))
48
+ reference = positive_scalar(scale * factor, name="selected bandwidth")
49
+ if method == "normal_reference":
50
+ if candidates is not None:
51
+ raise ValueError("candidates are only used when method='cv'")
52
+ return reference
53
+
54
+ if candidates is None:
55
+ candidate_values = reference * np.geomspace(0.35, 2.5, 31)
56
+ else:
57
+ candidate_values = as_vector(candidates, name="candidates")
58
+ if np.any(candidate_values <= 0):
59
+ raise ValueError("candidates must be positive")
60
+ if not isinstance(block_size, (int, np.integer)) or block_size < 1:
61
+ raise ValueError("block_size must be a positive integer")
62
+ scores = np.empty(candidate_values.size, dtype=float)
63
+ for index, candidate in enumerate(candidate_values):
64
+ h = float(candidate)
65
+ integrated_normalizer = (4.0 * np.pi * h**2) ** (-0.5 * dimension)
66
+ ordinary_normalizer = (2.0 * np.pi * h**2) ** (-0.5 * dimension)
67
+ integrated_sum = 0.0
68
+ ordinary_sum = 0.0
69
+ for start in range(0, n, int(block_size)):
70
+ stop = min(start + int(block_size), n)
71
+ differences = samples[start:stop, None, :] - samples[None, :, :]
72
+ distances_squared = np.sum(differences**2, axis=-1)
73
+ integrated_sum += integrated_normalizer * float(
74
+ np.sum(np.exp(-distances_squared / (4.0 * h**2)))
75
+ )
76
+ ordinary_sum += ordinary_normalizer * float(
77
+ np.sum(np.exp(-distances_squared / (2.0 * h**2)))
78
+ )
79
+ ordinary_off_diagonal = ordinary_sum - n * ordinary_normalizer
80
+ scores[index] = (
81
+ integrated_sum / n**2
82
+ - 2.0 * ordinary_off_diagonal / (n * (n - 1))
83
+ )
84
+ return float(candidate_values[int(np.argmin(scores))])
85
+
86
+
87
+ def regression_bandwidth(
88
+ x: ArrayLike,
89
+ y: ArrayLike,
90
+ *,
91
+ candidates: ArrayLike | None = None,
92
+ n_folds: int = 5,
93
+ random_state: int | None = 0,
94
+ ) -> float:
95
+ """Select a local-linear bandwidth by deterministic K-fold CV.
96
+
97
+ Candidate loss is mean squared prediction error from the ordinary
98
+ local-linear smoother. Failed boundary fits are excluded; a candidate is
99
+ eligible only if every held-out point can be predicted.
100
+ """
101
+ from .regression import _local_polynomial
102
+
103
+ x_values = as_vector(x, name="x")
104
+ y_values = as_vector(y, name="y")
105
+ if x_values.size != y_values.size:
106
+ raise ValueError("x and y must have the same length")
107
+ if np.ptp(x_values) == 0:
108
+ raise ValueError("x must contain at least two distinct values")
109
+ n = x_values.size
110
+ if not isinstance(n_folds, (int, np.integer)) or not 2 <= n_folds <= n:
111
+ raise ValueError("n_folds must be an integer between 2 and len(x)")
112
+
113
+ if candidates is None:
114
+ scale = min(float(np.std(x_values, ddof=1)), float(np.ptp(x_values)) / 4.0)
115
+ base = max(scale * n ** (-1.0 / 5.0), np.finfo(float).eps)
116
+ candidate_values = base * np.geomspace(0.35, 2.5, 21)
117
+ else:
118
+ candidate_values = as_vector(candidates, name="candidates")
119
+ if np.any(candidate_values <= 0):
120
+ raise ValueError("candidates must be positive")
121
+
122
+ generator = np.random.default_rng(random_state)
123
+ order = generator.permutation(n)
124
+ folds = np.array_split(order, n_folds)
125
+ losses = np.full(candidate_values.size, np.inf)
126
+ for candidate_index, candidate in enumerate(candidate_values):
127
+ squared_errors: list[np.ndarray] = []
128
+ valid = True
129
+ for held_out in folds:
130
+ keep = np.ones(n, dtype=bool)
131
+ keep[held_out] = False
132
+ predicted = _local_polynomial(
133
+ x_values[keep], y_values[keep], x_values[held_out],
134
+ float(candidate), degree=1, derivative=0,
135
+ )
136
+ if np.any(~np.isfinite(predicted)):
137
+ valid = False
138
+ break
139
+ squared_errors.append((y_values[held_out] - predicted) ** 2)
140
+ if valid:
141
+ losses[candidate_index] = float(np.mean(np.concatenate(squared_errors)))
142
+ if not np.any(np.isfinite(losses)):
143
+ raise RuntimeError("all candidate bandwidths produced singular local fits")
144
+ return float(candidate_values[int(np.argmin(losses))])