model-diagnostics 1.4.2__tar.gz → 1.4.3__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.
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/PKG-INFO +9 -8
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/mkdocs.yml +2 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/pyproject.toml +31 -34
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/__about__.py +1 -1
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/binning.py +36 -31
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/partial_dependence.py +1 -1
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/tests/test_isotonic.py +1 -1
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/tests/test_partial_dependence.py +6 -12
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/identification.py +6 -3
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/plots.py +2 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/tests/test_identification.py +8 -2
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/tests/test_plots.py +11 -4
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/plots.py +1 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/scoring.py +29 -27
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/.codecov.yml +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/.gitignore +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/LICENSE +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/README.md +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/Untitled.ipynb +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/dev_tools/print_package_versions.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/development.md +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/examples/classification.ipynb +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/examples/quantile_regression.ipynb +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/examples/regression_on_workers_compensation.ipynb +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/gen_ref_pages.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/index.md +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/docs/javascripts/mathjax.js +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/__init__.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_config.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/__init__.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/array.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/isotonic.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/plot_helper.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/test_helper.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/tests/test_array.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/tests/test_binning.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/__init__.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/tests/__init__.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/__init__.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/tests/__init__.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/tests/test_plots.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/tests/test_scoring.py +0 -0
- {model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/tests/test_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: model-diagnostics
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.3
|
|
4
4
|
Summary: Tools for diagnostics and assessment of (machine learning) models
|
|
5
5
|
Project-URL: Homepage, https://lorentzenchr.github.io/model-diagnostics/
|
|
6
6
|
Project-URL: Source, https://github.com/lorentzenchr/model-diagnostics
|
|
@@ -27,6 +27,7 @@ License: MIT License
|
|
|
27
27
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
28
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
29
|
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
30
31
|
Keywords: calibration,machine learning,model diagnostics
|
|
31
32
|
Classifier: Development Status :: 5 - Production/Stable
|
|
32
33
|
Classifier: Framework :: Hatch
|
|
@@ -34,13 +35,13 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
34
35
|
Classifier: Operating System :: OS Independent
|
|
35
36
|
Classifier: Programming Language :: Python :: 3
|
|
36
37
|
Classifier: Topic :: Scientific/Engineering
|
|
37
|
-
Requires-Python: >=3.
|
|
38
|
-
Requires-Dist: matplotlib>=3.
|
|
39
|
-
Requires-Dist: numpy>=
|
|
38
|
+
Requires-Python: >=3.11
|
|
39
|
+
Requires-Dist: matplotlib>=3.8.4
|
|
40
|
+
Requires-Dist: numpy>=2.0.0
|
|
40
41
|
Requires-Dist: packaging>=23.1
|
|
41
|
-
Requires-Dist: polars>=1.
|
|
42
|
-
Requires-Dist: scikit-learn>=1.2
|
|
43
|
-
Requires-Dist: scipy>=1.
|
|
42
|
+
Requires-Dist: polars>=1.1.0
|
|
43
|
+
Requires-Dist: scikit-learn>=1.4.2
|
|
44
|
+
Requires-Dist: scipy>=1.13.1
|
|
44
45
|
Description-Content-Type: text/markdown
|
|
45
46
|
|
|
46
47
|
# model-diagnostics
|
|
@@ -10,7 +10,7 @@ authors = [
|
|
|
10
10
|
description = "Tools for diagnostics and assessment of (machine learning) models"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
license = { file="LICENSE" }
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Framework :: Hatch",
|
|
@@ -21,12 +21,12 @@ classifiers = [
|
|
|
21
21
|
]
|
|
22
22
|
keywords = ["machine learning", "model diagnostics", "calibration"]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"matplotlib>=3.
|
|
25
|
-
"numpy>=
|
|
24
|
+
"matplotlib>=3.8.4", # numpy 2 support
|
|
25
|
+
"numpy>=2.0.0",
|
|
26
26
|
"packaging>=23.1",
|
|
27
|
-
"polars>=1.
|
|
28
|
-
"scikit-learn>=1.2
|
|
29
|
-
"scipy>=1.
|
|
27
|
+
"polars>=1.1.0", # numpy 2 support
|
|
28
|
+
"scikit-learn>=1.4.2", # numpy 2 support
|
|
29
|
+
"scipy>=1.13.1", # numpy 2 support
|
|
30
30
|
]
|
|
31
31
|
dynamic = ["version"]
|
|
32
32
|
|
|
@@ -122,7 +122,6 @@ lint.isort.known-first-party = ["model_diagnostics"]
|
|
|
122
122
|
# disallow_untyped_defs = false
|
|
123
123
|
# follow_imports = "normal"
|
|
124
124
|
ignore_missing_imports = true
|
|
125
|
-
plugins = "numpy.typing.mypy_plugin"
|
|
126
125
|
pretty = true
|
|
127
126
|
# show_column_numbers = true
|
|
128
127
|
# warn_no_return = false
|
|
@@ -150,9 +149,10 @@ exclude = [
|
|
|
150
149
|
]
|
|
151
150
|
|
|
152
151
|
[tool.hatch.envs.default]
|
|
152
|
+
installer = "uv"
|
|
153
153
|
dependencies = [
|
|
154
|
-
"pandas>=
|
|
155
|
-
"pyarrow>=
|
|
154
|
+
"pandas>=2.2.2", # numpy 2 support
|
|
155
|
+
"pyarrow>=16.0.0", # numpy 2 support
|
|
156
156
|
"pytest",
|
|
157
157
|
"pytest-cov",
|
|
158
158
|
"pytest-xdist",
|
|
@@ -172,9 +172,7 @@ dependencies = [
|
|
|
172
172
|
"mkdocs-literate-nav>=0.6.1",
|
|
173
173
|
"mkdocs-section-index>=0.3.9",
|
|
174
174
|
"mkdocs-jupyter==0.24.7", # https://github.com/danielfrg/mkdocs-jupyter/issues/212
|
|
175
|
-
|
|
176
|
-
extra-dependencies = [
|
|
177
|
-
"scikit-learn>=1.3.0", # for Gamma deviance in HistGradientBoostingRegressor
|
|
175
|
+
"click<=8.2.1", # https://github.com/mkdocs/mkdocs/issues/4032
|
|
178
176
|
]
|
|
179
177
|
|
|
180
178
|
[tool.hatch.envs.docs.scripts]
|
|
@@ -185,7 +183,6 @@ ci-build = "mkdocs gh-deploy --force {args}"
|
|
|
185
183
|
[tool.hatch.envs.jupyter]
|
|
186
184
|
extra-dependencies = [
|
|
187
185
|
"jupyterlab>=4.0.4",
|
|
188
|
-
"scikit-learn>=1.3.0",
|
|
189
186
|
"plotly>=5.11.0",
|
|
190
187
|
]
|
|
191
188
|
|
|
@@ -195,7 +192,7 @@ lab = "jupyter lab"
|
|
|
195
192
|
[tool.hatch.envs.format]
|
|
196
193
|
detached = true
|
|
197
194
|
dependencies = [
|
|
198
|
-
"numpy>=
|
|
195
|
+
"numpy>=2.3",
|
|
199
196
|
"mypy>=1.14",
|
|
200
197
|
"ruff==0.4.4",
|
|
201
198
|
]
|
|
@@ -241,36 +238,36 @@ dependencies = [
|
|
|
241
238
|
]
|
|
242
239
|
|
|
243
240
|
[[tool.hatch.envs.test.matrix]]
|
|
244
|
-
python = ["3.
|
|
241
|
+
python = ["3.11", "3.12", "3.13", "3.14"]
|
|
245
242
|
|
|
246
243
|
[tool.hatch.envs.test.overrides]
|
|
247
|
-
# Minimal versions
|
|
244
|
+
# Minimal versions
|
|
248
245
|
# but including pandas and pyarrow
|
|
249
|
-
name."py3.
|
|
250
|
-
"numpy==
|
|
251
|
-
"polars==1.
|
|
252
|
-
"scipy==1.
|
|
253
|
-
"pandas==
|
|
254
|
-
"pyarrow==
|
|
246
|
+
name."py3.11".set-extra-dependencies = [
|
|
247
|
+
"numpy==2.0.0",
|
|
248
|
+
"polars==1.1.0",
|
|
249
|
+
"scipy==1.13.1",
|
|
250
|
+
"pandas==2.2.2", # numpy 2 support
|
|
251
|
+
"pyarrow==16.0.*" # numpy 2 support
|
|
255
252
|
]
|
|
256
253
|
# Minimal versions without pandas and pyarrow
|
|
257
|
-
name."py3.
|
|
258
|
-
"numpy==
|
|
259
|
-
"polars==1.
|
|
260
|
-
"scipy==1.
|
|
254
|
+
name."py3.12".set-extra-dependencies = [
|
|
255
|
+
"numpy==2.0.0",
|
|
256
|
+
"polars==1.1.0",
|
|
257
|
+
"scipy==1.13.1",
|
|
261
258
|
]
|
|
262
259
|
# Some intermediate versions, with pandads but without pyarrow
|
|
263
|
-
name."py3.
|
|
264
|
-
"numpy==1
|
|
265
|
-
"polars==1.
|
|
266
|
-
"scipy==1.
|
|
267
|
-
"pandas==2.
|
|
260
|
+
name."py3.13".set-extra-dependencies = [
|
|
261
|
+
"numpy==2.1.*",
|
|
262
|
+
"polars==1.10.*",
|
|
263
|
+
"scipy==1.14.*",
|
|
264
|
+
"pandas==2.3.*",
|
|
268
265
|
"plotly",
|
|
269
266
|
]
|
|
270
267
|
# Latest versions, with plotly
|
|
271
|
-
name."py3.
|
|
272
|
-
"numpy>=2.
|
|
273
|
-
"polars>=1.
|
|
268
|
+
name."py3.14".set-extra-dependencies = [
|
|
269
|
+
"numpy>=2.3.0",
|
|
270
|
+
"polars>=1.31.0",
|
|
274
271
|
"scipy",
|
|
275
272
|
"pandas",
|
|
276
273
|
"plotly",
|
|
@@ -31,7 +31,7 @@ def bin_feature(
|
|
|
31
31
|
n_obs: int,
|
|
32
32
|
n_bins: int = 10,
|
|
33
33
|
bin_method: str = "sturges",
|
|
34
|
-
):
|
|
34
|
+
) -> tuple[pl.Series, int, pl.DataFrame]:
|
|
35
35
|
"""Helper function to bin features of different dtypes.
|
|
36
36
|
|
|
37
37
|
Best call this function inside a `with pl.StringCache()` context manager.
|
|
@@ -119,7 +119,7 @@ def bin_feature(
|
|
|
119
119
|
# The following statement, i.e. possibly the creation of a pl.Categorical,
|
|
120
120
|
# MUST be under the StringCache context manager!
|
|
121
121
|
try:
|
|
122
|
-
|
|
122
|
+
feature_out = pl.Series(name=feature_name, values=feature)
|
|
123
123
|
except ImportError:
|
|
124
124
|
# FIXME: pyarrow not installed
|
|
125
125
|
# For non numpy-backed columns, pyarrow is needed. Here we handle the case
|
|
@@ -131,38 +131,38 @@ def bin_feature(
|
|
|
131
131
|
feature.dtype, # type: ignore
|
|
132
132
|
pandas.CategoricalDtype,
|
|
133
133
|
)
|
|
134
|
-
|
|
134
|
+
feature_out = pl.from_dataframe(
|
|
135
135
|
feature.to_frame(name=feature_name) # type: ignore
|
|
136
136
|
)[:, 0]
|
|
137
|
-
if is_pandas_categorical and isinstance(
|
|
137
|
+
if is_pandas_categorical and isinstance(feature_out.dtype, pl.Enum):
|
|
138
138
|
# Pandas categoricals usually get mapped to polars categoricals.
|
|
139
139
|
# But this code path gives pl.Enum.
|
|
140
|
-
|
|
140
|
+
feature_out = feature_out.cast(pl.Categorical)
|
|
141
141
|
else:
|
|
142
142
|
raise # re-raises the ImportError
|
|
143
|
-
if length_of_first_dimension(
|
|
143
|
+
if length_of_first_dimension(feature_out) != n_obs:
|
|
144
144
|
msg = (
|
|
145
145
|
f"The feature array {feature_name} does not have length {n_obs} of its"
|
|
146
146
|
" first dimension."
|
|
147
147
|
)
|
|
148
148
|
raise ValueError(msg)
|
|
149
|
-
if
|
|
149
|
+
if feature_out.dtype == pl.Categorical:
|
|
150
150
|
is_categorical = True
|
|
151
|
-
elif
|
|
151
|
+
elif feature_out.dtype == pl.Enum:
|
|
152
152
|
is_enum = True
|
|
153
|
-
elif
|
|
153
|
+
elif feature_out.dtype in [pl.Utf8, pl.Object]:
|
|
154
154
|
# We could convert strings to categoricals.
|
|
155
155
|
is_string = True
|
|
156
|
-
elif
|
|
156
|
+
elif feature_out.dtype.is_float():
|
|
157
157
|
# We treat NaN as Null values, numpy will see a Null as a NaN.
|
|
158
|
-
|
|
158
|
+
feature_out = feature_out.fill_nan(None)
|
|
159
159
|
else:
|
|
160
160
|
# integers
|
|
161
161
|
pass
|
|
162
162
|
|
|
163
163
|
# If we have Null values, we should reserve one bin for it and reduce
|
|
164
164
|
# the effective number of bins by 1.
|
|
165
|
-
n_bins_ef = max(1, n_bins -
|
|
165
|
+
n_bins_ef = max(1, n_bins - feature_out.has_nulls())
|
|
166
166
|
|
|
167
167
|
if is_categorical or is_enum or is_string:
|
|
168
168
|
# For categorical and string features, knowing the frequency table in
|
|
@@ -181,7 +181,7 @@ def bin_feature(
|
|
|
181
181
|
# value_counts(sort=True) sorts ties by first occurence, we want
|
|
182
182
|
# alphanumerical sorting order.
|
|
183
183
|
value_counts = (
|
|
184
|
-
|
|
184
|
+
feature_out.drop_nulls()
|
|
185
185
|
.value_counts()
|
|
186
186
|
.sort(by=["count", feature_name], descending=[True, False])
|
|
187
187
|
)
|
|
@@ -189,18 +189,21 @@ def bin_feature(
|
|
|
189
189
|
if n_bins_ef >= value_counts.shape[0]:
|
|
190
190
|
# This also covers the case of only null values.
|
|
191
191
|
n_bins_ef = value_counts.shape[0]
|
|
192
|
-
f_binned = pl.DataFrame({"bin":
|
|
192
|
+
f_binned = pl.DataFrame({"bin": feature_out})
|
|
193
193
|
else:
|
|
194
194
|
# We keep the n_bins_ef - 1 most frequent values. Ties are resolved by
|
|
195
195
|
# taking the first one of the sorted values (most often alpha-numerical).
|
|
196
|
-
if
|
|
196
|
+
if feature_out.has_nulls():
|
|
197
197
|
# To ease adding the null value, we take one value more.
|
|
198
198
|
keep_values = value_counts[feature_name].head(n_bins_ef)
|
|
199
|
-
if is_categorical:
|
|
200
|
-
# FIXME: Workaround for https://github.com/pola-rs/polars/issues/21175
|
|
199
|
+
if is_categorical or is_enum:
|
|
200
|
+
# FIXME: categorical: Workaround for https://github.com/pola-rs/polars/issues/21175
|
|
201
|
+
# FIXME: enum: Workaround for https://github.com/pola-rs/polars/pull/25245
|
|
202
|
+
# to be released in polars 1.35.3 or 1.36.
|
|
203
|
+
dtype = keep_values.dtype
|
|
201
204
|
keep_values = keep_values.cast(pl.String)
|
|
202
205
|
keep_values[-1] = None
|
|
203
|
-
keep_values = keep_values.cast(
|
|
206
|
+
keep_values = keep_values.cast(dtype)
|
|
204
207
|
else:
|
|
205
208
|
keep_values[-1] = None
|
|
206
209
|
else:
|
|
@@ -209,14 +212,16 @@ def bin_feature(
|
|
|
209
212
|
# n = n_remaining.
|
|
210
213
|
n_remaining = value_counts.shape[0] - (n_bins_ef - 1)
|
|
211
214
|
remaining_name = "other " + _format_integer(n_remaining)
|
|
212
|
-
while remaining_name in keep_values:
|
|
215
|
+
while remaining_name in keep_values.cast(pl.String):
|
|
213
216
|
remaining_name = "_" + remaining_name
|
|
214
|
-
return_dtype =
|
|
217
|
+
return_dtype = feature_out.dtype
|
|
215
218
|
if is_enum:
|
|
216
219
|
return_dtype = pl.Enum(
|
|
217
|
-
pl.concat(
|
|
220
|
+
pl.concat(
|
|
221
|
+
[feature_out.dtype.categories, pl.Series([remaining_name])]
|
|
222
|
+
)
|
|
218
223
|
)
|
|
219
|
-
f_binned =
|
|
224
|
+
f_binned = feature_out.replace_strict(
|
|
220
225
|
old=keep_values,
|
|
221
226
|
new=keep_values,
|
|
222
227
|
default=remaining_name,
|
|
@@ -226,13 +231,13 @@ def bin_feature(
|
|
|
226
231
|
else:
|
|
227
232
|
# Binning a numerical feature
|
|
228
233
|
# We will need min and max anyway.
|
|
229
|
-
feature_min, feature_max =
|
|
234
|
+
feature_min, feature_max = feature_out.min(), feature_out.max()
|
|
230
235
|
if feature_min == -np.inf:
|
|
231
|
-
finite_min =
|
|
236
|
+
finite_min = feature_out.filter(feature_out > -np.inf).min()
|
|
232
237
|
else:
|
|
233
238
|
finite_min = feature_min
|
|
234
239
|
if feature_max == np.inf:
|
|
235
|
-
finite_max =
|
|
240
|
+
finite_max = feature_out.filter(feature_out < np.inf).max()
|
|
236
241
|
else:
|
|
237
242
|
finite_max = feature_max
|
|
238
243
|
f_range = finite_max - finite_min
|
|
@@ -241,7 +246,7 @@ def bin_feature(
|
|
|
241
246
|
# We use method="inverted_cdf" instead of the default "linear" because
|
|
242
247
|
# "linear" produces as many unique values as before.
|
|
243
248
|
q = np.nanquantile(
|
|
244
|
-
|
|
249
|
+
feature_out,
|
|
245
250
|
# Improved rounding errors by using integers and dividing at the
|
|
246
251
|
# end as opposed to np.linspace with 1/n_bins step size.
|
|
247
252
|
q=np.arange(1, n_bins_ef) / n_bins_ef,
|
|
@@ -252,11 +257,11 @@ def bin_feature(
|
|
|
252
257
|
bin_edges = finite_min + f_range * np.arange(1, n_bins_ef) / n_bins_ef
|
|
253
258
|
else:
|
|
254
259
|
# numpy histogram bin methods
|
|
255
|
-
a =
|
|
260
|
+
a = feature_out.filter(feature_out.is_finite() & feature_out.is_not_null())
|
|
256
261
|
bin_edges = np.histogram_bin_edges(a, bins=bin_method)[1:-1]
|
|
257
262
|
n_bins_ef = bin_edges.shape[0] + 1
|
|
258
263
|
# We want: bins[i-1] < x <= bins[i]
|
|
259
|
-
f_binned = np.digitize(
|
|
264
|
+
f_binned = np.digitize(feature_out, bins=bin_edges, right=True)
|
|
260
265
|
# The full bin edges also include min and max of the feature.
|
|
261
266
|
if bin_edges.size == 0:
|
|
262
267
|
bin_edges = np.r_[feature_min, feature_max]
|
|
@@ -272,8 +277,8 @@ def bin_feature(
|
|
|
272
277
|
f_binned = (
|
|
273
278
|
pl.LazyFrame(
|
|
274
279
|
[
|
|
275
|
-
|
|
276
|
-
pl.Series("__f_binned", f_binned, dtype=
|
|
280
|
+
feature_out,
|
|
281
|
+
pl.Series("__f_binned", f_binned, dtype=feature_out.dtype),
|
|
277
282
|
pl.Series(
|
|
278
283
|
"__bin_edges",
|
|
279
284
|
bin_edges[f_binned],
|
|
@@ -293,4 +298,4 @@ def bin_feature(
|
|
|
293
298
|
)
|
|
294
299
|
.collect()
|
|
295
300
|
)
|
|
296
|
-
return
|
|
301
|
+
return feature_out, n_bins_ef + feature_out.has_nulls(), f_binned
|
|
@@ -236,7 +236,7 @@ def test_gpava_simple(y, fun_lower, x_lower, fun_upper, x_upper):
|
|
|
236
236
|
tol=1e-12,
|
|
237
237
|
)
|
|
238
238
|
|
|
239
|
-
assert result.fun == pytest.approx(loss_lower, rel=1e-
|
|
239
|
+
assert result.fun == pytest.approx(loss_lower, rel=1e-7)
|
|
240
240
|
|
|
241
241
|
# Check that it is the boundary of solutions.
|
|
242
242
|
eps = 1e-8 * np.mean(np.abs(xl))
|
|
@@ -6,7 +6,7 @@ import polars as pl
|
|
|
6
6
|
import pytest
|
|
7
7
|
from numpy.testing import assert_allclose, assert_equal
|
|
8
8
|
from polars.testing import assert_frame_equal
|
|
9
|
-
from sklearn.base import RegressorMixin
|
|
9
|
+
from sklearn.base import BaseEstimator, RegressorMixin
|
|
10
10
|
from sklearn.inspection import partial_dependence
|
|
11
11
|
|
|
12
12
|
from model_diagnostics._utils.array import (
|
|
@@ -82,21 +82,15 @@ def test_compute_partial_dependence(n_max, weights, feature_type, data_container
|
|
|
82
82
|
"b": X["b"].to_numpy(),
|
|
83
83
|
}
|
|
84
84
|
)
|
|
85
|
-
elif feature_type == "string":
|
|
86
|
-
# TODO: With pandas 2.0.3, this does not work for strings. Get
|
|
87
|
-
# AssertionError from polars in string_column_to_ndarray.
|
|
88
|
-
# X_skl = pandas.api.interchange.from_dataframe(X). So we do it manually.
|
|
89
|
-
X_skl = pandas.DataFrame(
|
|
90
|
-
{
|
|
91
|
-
"a": X["a"].to_numpy(),
|
|
92
|
-
"b": X["b"].to_numpy(),
|
|
93
|
-
}
|
|
94
|
-
)
|
|
95
85
|
else:
|
|
96
86
|
X_skl = pandas.api.interchange.from_dataframe(X)
|
|
97
87
|
else:
|
|
98
88
|
X_skl = X.to_pandas()
|
|
99
89
|
|
|
90
|
+
if feature_type == "numeric" and pandas is not None:
|
|
91
|
+
# scikit-learn does not like integers in partial dependence
|
|
92
|
+
X_skl["a"] = 1.0 * X_skl["a"]
|
|
93
|
+
|
|
100
94
|
grid = X.get_column("a").unique().sort()
|
|
101
95
|
# Make sure grid is the same data container as X.
|
|
102
96
|
if data_container == "list":
|
|
@@ -143,7 +137,7 @@ def test_compute_partial_dependence(n_max, weights, feature_type, data_container
|
|
|
143
137
|
rng=rng,
|
|
144
138
|
)
|
|
145
139
|
|
|
146
|
-
class ModelWrapPredict(RegressorMixin):
|
|
140
|
+
class ModelWrapPredict(RegressorMixin, BaseEstimator):
|
|
147
141
|
def fit(self, X):
|
|
148
142
|
self.is_fitted_ = True
|
|
149
143
|
return self
|
|
@@ -60,6 +60,7 @@ def identification_function(
|
|
|
60
60
|
Notes
|
|
61
61
|
-----
|
|
62
62
|
[](){#notes}
|
|
63
|
+
|
|
63
64
|
The function \(V(y, z)\) for observation \(y=y_{pred}\) and prediction
|
|
64
65
|
\(z=y_{pred}\) is a strict identification function for the functional \(T\), or
|
|
65
66
|
induces the functional \(T\) as:
|
|
@@ -235,6 +236,7 @@ def compute_bias(
|
|
|
235
236
|
Notes
|
|
236
237
|
-----
|
|
237
238
|
[](){#notes}
|
|
239
|
+
|
|
238
240
|
A model \(m(X)\) is conditionally calibrated iff
|
|
239
241
|
\(\mathbb{E}(V(m(X), Y)|X)=0\) almost surely with canonical identification
|
|
240
242
|
function \(V\).
|
|
@@ -311,20 +313,21 @@ def compute_bias(
|
|
|
311
313
|
with pl.StringCache():
|
|
312
314
|
feature_name = None
|
|
313
315
|
if feature is not None:
|
|
314
|
-
|
|
316
|
+
feature_series, n_bins, f_binned = bin_feature(
|
|
315
317
|
feature=feature,
|
|
316
318
|
feature_name=None,
|
|
317
319
|
n_obs=n_obs,
|
|
318
320
|
n_bins=n_bins,
|
|
319
321
|
bin_method=bin_method,
|
|
320
322
|
)
|
|
321
|
-
feature_name =
|
|
322
|
-
is_cat_or_string =
|
|
323
|
+
feature_name = feature_series.name
|
|
324
|
+
is_cat_or_string = feature_series.dtype in [
|
|
323
325
|
pl.Categorical,
|
|
324
326
|
pl.Enum,
|
|
325
327
|
pl.Utf8,
|
|
326
328
|
pl.Object,
|
|
327
329
|
]
|
|
330
|
+
feature = feature_series
|
|
328
331
|
|
|
329
332
|
for i in range(len(pred_names)):
|
|
330
333
|
# Loop over columns of y_pred.
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/plots.py
RENAMED
|
@@ -93,6 +93,7 @@ def plot_reliability_diagram(
|
|
|
93
93
|
Notes
|
|
94
94
|
-----
|
|
95
95
|
[](){#notes}
|
|
96
|
+
|
|
96
97
|
The expectation conditional on the predictions is \(E(Y|y_{pred})\). This object is
|
|
97
98
|
estimated by the pool-adjacent violator (PAV) algorithm, which has very desirable
|
|
98
99
|
properties:
|
|
@@ -425,6 +426,7 @@ def plot_bias(
|
|
|
425
426
|
Notes
|
|
426
427
|
-----
|
|
427
428
|
[](){#notes}
|
|
429
|
+
|
|
428
430
|
A model \(m(X)\) is conditionally calibrated iff \(E(V(m(X), Y))=0\) a.s. The
|
|
429
431
|
empirical version, given some data, reads \(\frac{1}{n}\sum_i V(m(x_i), y_i)\).
|
|
430
432
|
See `[FLM2022]`.
|
|
@@ -296,7 +296,10 @@ def test_compute_bias_n_bins_string_like_feature(feature_type):
|
|
|
296
296
|
if feature_type == "cat":
|
|
297
297
|
dtype = pl.Categorical
|
|
298
298
|
elif feature_type == "cat_physical":
|
|
299
|
-
|
|
299
|
+
if Version(pl.__version__) < Version("1.32.0"):
|
|
300
|
+
dtype = pl.Categorical(ordering="lexical")
|
|
301
|
+
else:
|
|
302
|
+
dtype = pl.Categorical()
|
|
300
303
|
elif feature_type == "enum":
|
|
301
304
|
dtype = pl.Enum(categories=["b", "a", "c"])
|
|
302
305
|
else:
|
|
@@ -824,7 +827,10 @@ def test_compute_marginal_n_bins_string_like_feature(feature_type):
|
|
|
824
827
|
if feature_type == "cat":
|
|
825
828
|
dtype = pl.Categorical
|
|
826
829
|
elif feature_type == "cat_physical":
|
|
827
|
-
|
|
830
|
+
if Version(pl.__version__) < Version("1.32.0"):
|
|
831
|
+
dtype = pl.Categorical(ordering="lexical")
|
|
832
|
+
else:
|
|
833
|
+
dtype = pl.Categorical()
|
|
828
834
|
elif feature_type == "enum":
|
|
829
835
|
dtype = pl.Enum(categories=["b", "a", "c"])
|
|
830
836
|
else:
|
|
@@ -5,6 +5,7 @@ import matplotlib.pyplot as plt
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import polars as pl
|
|
7
7
|
import pytest
|
|
8
|
+
from packaging.version import Version
|
|
8
9
|
from sklearn.base import BaseEstimator, TransformerMixin
|
|
9
10
|
from sklearn.compose import ColumnTransformer
|
|
10
11
|
from sklearn.datasets import make_classification
|
|
@@ -305,7 +306,7 @@ def test_plot_bias(
|
|
|
305
306
|
random_state=42,
|
|
306
307
|
)
|
|
307
308
|
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
|
|
308
|
-
clf = LogisticRegression(solver="
|
|
309
|
+
clf = LogisticRegression(solver="newton-cg")
|
|
309
310
|
clf.fit(X_train, y_train)
|
|
310
311
|
feature = X_test[:, 0]
|
|
311
312
|
if feature_type != "num":
|
|
@@ -319,7 +320,10 @@ def test_plot_bias(
|
|
|
319
320
|
elif feature_type == "cat":
|
|
320
321
|
dtype = pl.Categorical
|
|
321
322
|
elif feature_type == "cat_physical":
|
|
322
|
-
|
|
323
|
+
if Version(pl.__version__) < Version("1.32.0"):
|
|
324
|
+
dtype = pl.Categorical(ordering="lexical")
|
|
325
|
+
else:
|
|
326
|
+
dtype = pl.Categorical()
|
|
323
327
|
elif feature_type == "enum":
|
|
324
328
|
dtype = pl.Enum(categories=np.unique(feature))
|
|
325
329
|
else:
|
|
@@ -527,7 +531,10 @@ def test_plot_marginal(with_null_values, feature_type, bin_method, ax, plot_back
|
|
|
527
531
|
elif feature_type == "cat":
|
|
528
532
|
dtype = pl.Categorical
|
|
529
533
|
elif feature_type == "cat_physical":
|
|
530
|
-
|
|
534
|
+
if Version(pl.__version__) < Version("1.32.0"):
|
|
535
|
+
dtype = pl.Categorical(ordering="lexical")
|
|
536
|
+
else:
|
|
537
|
+
dtype = pl.Categorical()
|
|
531
538
|
elif feature_type == "enum":
|
|
532
539
|
dtype = pl.Enum(categories=np.unique(feature))
|
|
533
540
|
else:
|
|
@@ -576,7 +583,7 @@ def test_plot_marginal(with_null_values, feature_type, bin_method, ax, plot_back
|
|
|
576
583
|
|
|
577
584
|
clf = make_pipeline(
|
|
578
585
|
ColumnTransformer([("ohe", safe_ohe, [0])], remainder="passthrough"),
|
|
579
|
-
LogisticRegression(solver="
|
|
586
|
+
LogisticRegression(solver="newton-cg"),
|
|
580
587
|
)
|
|
581
588
|
clf.fit(X, y)
|
|
582
589
|
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/scoring.py
RENAMED
|
@@ -60,7 +60,10 @@ class _BaseScoringFunction(ABC):
|
|
|
60
60
|
score : float
|
|
61
61
|
The average score.
|
|
62
62
|
"""
|
|
63
|
-
return np.average(
|
|
63
|
+
return np.average(
|
|
64
|
+
self.score_per_obs(y_obs, y_pred),
|
|
65
|
+
weights=weights, # type: ignore
|
|
66
|
+
)
|
|
64
67
|
|
|
65
68
|
@abstractmethod
|
|
66
69
|
def score_per_obs(
|
|
@@ -142,9 +145,9 @@ class HomogeneousExpectileScore(_BaseScoringFunction):
|
|
|
142
145
|
Examples
|
|
143
146
|
--------
|
|
144
147
|
>>> hes = HomogeneousExpectileScore(degree=2, level=0.1)
|
|
145
|
-
>>> hes(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
146
|
-
0.95
|
|
147
|
-
"""
|
|
148
|
+
>>> hes(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
149
|
+
np.float64(0.95)
|
|
150
|
+
"""
|
|
148
151
|
|
|
149
152
|
def __init__(self, degree: float = 2, level: float = 0.5) -> None:
|
|
150
153
|
self.degree = degree
|
|
@@ -268,9 +271,9 @@ class SquaredError(HomogeneousExpectileScore):
|
|
|
268
271
|
Examples
|
|
269
272
|
--------
|
|
270
273
|
>>> se = SquaredError()
|
|
271
|
-
>>> se(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
272
|
-
0.75
|
|
273
|
-
"""
|
|
274
|
+
>>> se(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
275
|
+
np.float64(0.75)
|
|
276
|
+
"""
|
|
274
277
|
|
|
275
278
|
def __init__(self) -> None:
|
|
276
279
|
super().__init__(degree=2, level=0.5)
|
|
@@ -296,9 +299,9 @@ class PoissonDeviance(HomogeneousExpectileScore):
|
|
|
296
299
|
Examples
|
|
297
300
|
--------
|
|
298
301
|
>>> pd = PoissonDeviance()
|
|
299
|
-
>>> pd(y_obs=[0, 0, 1, 1], y_pred=[2, 1, 1 , 2])
|
|
300
|
-
1.6534264097200273
|
|
301
|
-
"""
|
|
302
|
+
>>> pd(y_obs=[0, 0, 1, 1], y_pred=[2, 1, 1 , 2])
|
|
303
|
+
np.float64(1.6534264097200273)
|
|
304
|
+
"""
|
|
302
305
|
|
|
303
306
|
def __init__(self) -> None:
|
|
304
307
|
super().__init__(degree=1, level=0.5)
|
|
@@ -325,9 +328,9 @@ class GammaDeviance(HomogeneousExpectileScore):
|
|
|
325
328
|
Examples
|
|
326
329
|
--------
|
|
327
330
|
>>> gd = GammaDeviance()
|
|
328
|
-
>>> gd(y_obs=[3, 2, 1, 1], y_pred=[2, 1, 1 , 2])
|
|
329
|
-
0.2972674459459178
|
|
330
|
-
"""
|
|
331
|
+
>>> gd(y_obs=[3, 2, 1, 1], y_pred=[2, 1, 1 , 2])
|
|
332
|
+
np.float64(0.2972674459459178)
|
|
333
|
+
"""
|
|
331
334
|
|
|
332
335
|
def __init__(self) -> None:
|
|
333
336
|
super().__init__(degree=0, level=0.5)
|
|
@@ -366,11 +369,9 @@ class LogLoss(_BaseScoringFunction):
|
|
|
366
369
|
Examples
|
|
367
370
|
--------
|
|
368
371
|
>>> ll = LogLoss()
|
|
369
|
-
>>> ll(y_obs=[0, 0.5, 1, 1], y_pred=[0.1, 0.2, 0.8 , 0.9], weights=[1, 2, 1, 1])
|
|
370
|
-
0.17603033705165635
|
|
371
|
-
"""
|
|
372
|
-
|
|
373
|
-
# FIXME: numpy 2.0.0, doctest skip should not be necessary.
|
|
372
|
+
>>> ll(y_obs=[0, 0.5, 1, 1], y_pred=[0.1, 0.2, 0.8 , 0.9], weights=[1, 2, 1, 1])
|
|
373
|
+
np.float64(0.17603033705165635)
|
|
374
|
+
"""
|
|
374
375
|
|
|
375
376
|
@property
|
|
376
377
|
def functional(self):
|
|
@@ -466,9 +467,9 @@ class HomogeneousQuantileScore(_BaseScoringFunction):
|
|
|
466
467
|
Examples
|
|
467
468
|
--------
|
|
468
469
|
>>> hqs = HomogeneousQuantileScore(degree=3, level=0.1)
|
|
469
|
-
>>> hqs(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
470
|
-
0.6083333333333334
|
|
471
|
-
"""
|
|
470
|
+
>>> hqs(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
471
|
+
np.float64(0.6083333333333334)
|
|
472
|
+
"""
|
|
472
473
|
|
|
473
474
|
def __init__(self, degree: float = 2, level: float = 0.5) -> None:
|
|
474
475
|
self.degree = degree
|
|
@@ -569,9 +570,9 @@ class PinballLoss(HomogeneousQuantileScore):
|
|
|
569
570
|
Examples
|
|
570
571
|
--------
|
|
571
572
|
>>> pl = PinballLoss(level=0.9)
|
|
572
|
-
>>> pl(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
573
|
-
0.275
|
|
574
|
-
"""
|
|
573
|
+
>>> pl(y_obs=[0, 0, 1, 1], y_pred=[-1, 1, 1 , 2])
|
|
574
|
+
np.float64(0.275)
|
|
575
|
+
"""
|
|
575
576
|
|
|
576
577
|
def __init__(self, level: float = 0.5) -> None:
|
|
577
578
|
super().__init__(degree=1, level=level)
|
|
@@ -606,6 +607,7 @@ class ElementaryScore(_BaseScoringFunction):
|
|
|
606
607
|
Notes
|
|
607
608
|
-----
|
|
608
609
|
[](){#notes}
|
|
610
|
+
|
|
609
611
|
The elementary scoring or loss function is given by
|
|
610
612
|
|
|
611
613
|
\[
|
|
@@ -644,9 +646,9 @@ class ElementaryScore(_BaseScoringFunction):
|
|
|
644
646
|
Examples
|
|
645
647
|
--------
|
|
646
648
|
>>> el_score = ElementaryScore(eta=2, functional="mean")
|
|
647
|
-
>>> el_score(y_obs=[1, 2, 2, 1], y_pred=[4, 1, 2, 3])
|
|
648
|
-
0.5
|
|
649
|
-
"""
|
|
649
|
+
>>> el_score(y_obs=[1, 2, 2, 1], y_pred=[4, 1, 2, 3])
|
|
650
|
+
np.float64(0.5)
|
|
651
|
+
"""
|
|
650
652
|
|
|
651
653
|
def __init__(
|
|
652
654
|
self, eta: float, functional: str = "mean", level: float = 0.5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/isotonic.py
RENAMED
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/plot_helper.py
RENAMED
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/test_helper.py
RENAMED
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/_utils/tests/test_array.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/calibration/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/__init__.py
RENAMED
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/scoring/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_diagnostics-1.4.2 → model_diagnostics-1.4.3}/src/model_diagnostics/tests/test_config.py
RENAMED
|
File without changes
|