Description
Describe the bug
When one of the scorers in _MultimetricScorer
is not a Scorer
object, it is handled incorrectly.
See line here. If the scorers passed to MultimetricScorer
are of the following type: [Scorer
, function
], it raises an error because the attribute _accept_sample_weight
does not exist for the second scorer (function
in this case). This (possibly) bug is present only in 1.7.0 since before this, the sample_weight
kwarg was being passed to all functions without a check of accepting sample weights.
Possible fix: Use if hasattr(scorer, '_accept_sample_weight'
) or if isinstance(scorer, _BaseScorer)
before checking for the _accept_sample_weight
attribute.
Steps/Code to Reproduce
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.metrics._scorer import _BaseScorer, _MultimetricScorer
# Step 1: Define a simple estimator
class SimpleEstimator(BaseEstimator, RegressorMixin):
def fit(self, X, y):
self.mean_ = np.mean(y)
return self
def predict(self, X):
return np.full(X.shape[0], self.mean_)
# Step 2: Define a custom scorer inheriting from _BaseScorer and a function which estimates score
class SimpleScorer(_BaseScorer):
def _score(self, method_caller, estimator, X, y_true, sample_weight=None):
y_pred = method_caller(estimator, "predict", X)
return self._score_func(y_true, y_pred, **self._kwargs)
def default_score(estimator, X, y, sample_weight=None, **kws):
return estimator.score(X, y, sample_weight=sample_weight)
def mse(y, y_pred):
return np.mean((y - y_pred)**2)
# Step 3: Create a _MultimetricScorer with multiple scorers
scorers = {
"mse": SimpleScorer(mse, sign=1, kwargs={}),
"default": default_score
}
multi_scorer = _MultimetricScorer(scorers=scorers)
# Step 4: Generate sample data
X = np.random.rand(10, 2)
y = np.random.rand(10)
sample_weight = np.random.rand(10)
# Step 5: Fit the estimator and evaluate using the multi-scorer
estimator = SimpleEstimator()
estimator.fit(X, y)
scores = multi_scorer(estimator, X, y, sample_weight=sample_weight)
print("Scores:", scores)
Expected Results
Expected result would be scores for each of the scorers without an error being raised.
Actual Results
AttributeError Traceback (most recent call last)
Cell In[6], line 4
2 estimator = SimpleEstimator()
3 estimator.fit(X, y)
----> 4 scores = multi_scorer(estimator, X, y, sample_weight=sample_weight)
6 print("Scores:", scores)
File ~/anaconda3/envs/scoring-311-2/lib/python3.11/site-packages/sklearn/metrics/_scorer.py:144, in _MultimetricScorer.__call__(self, estimator, *args, **kwargs)
142 if "sample_weight" in kwargs:
143 for name, scorer in self._scorers.items():
--> 144 if scorer._accept_sample_weight():
145 routed_params[name].score["sample_weight"] = kwargs[
146 "sample_weight"
147 ]
149 for name, scorer in self._scorers.items():
AttributeError: 'function' object has no attribute '_accept_sample_weight'
Versions
The `***` below is to obscure some personal details.
System:
python: 3.11.13 | packaged by conda-forge | (main, Jun 4 2025, 14:52:34) [Clang 18.1.8 ]
executable: /Users/***/anaconda3/envs/***/bin/python3.11
machine: macOS-15.5-arm64-arm-64bit
Python dependencies:
sklearn: 1.7.0
pip: 25.1.1
setuptools: 80.9.0
numpy: 2.2.6
scipy: 1.15.2
Cython: None
pandas: 2.3.0
matplotlib: None
joblib: 1.5.1
threadpoolctl: 3.6.0
Built with OpenMP: True
threadpoolctl info:
user_api: blas
internal_api: openblas
num_threads: 14
prefix: libopenblas
filepath: /Users/***/anaconda3/envs/***/lib/libopenblas.0.dylib
version: 0.3.29
threading_layer: openmp
architecture: VORTEX
user_api: openmp
internal_api: openmp
num_threads: 14
prefix: libomp
filepath: /Users/***/anaconda3/envs/***/lib/libomp.dylib
version: None