Repository navigation
MRG : add support for sample_weights in StandardScaler when X is sparse - #18682
Conversation
glemaitre
left a comment
There was a problem hiding this comment.
Just a quick partial review. I need more time to have a better look.
ogrisel
left a comment
There was a problem hiding this comment.
Besides @glemaitre review and the comments below, this LGTM.
I think we should also add a test that checks that the results of fitting a scaler on sparse data with missing values and sample weights is the same as fitting a scaler with a dense version of this data. I don't think the current tests cover this comparison.
| The number of samples processed by the estimator for each feature. | ||
| If there are not missing samples, the ``n_samples_seen`` will be an | ||
| integer, otherwise it will be an array. | ||
| integer, otherwise it will be an array. If sample_weights are |
There was a problem hiding this comment.
| integer, otherwise it will be an array. If sample_weights are | |
| integer, otherwise it will be an array of dtype=int. If sample_weights are |
| If there are not missing samples, the ``n_samples_seen`` will be an | ||
| integer, otherwise it will be an array. | ||
| integer, otherwise it will be an array. If sample_weights are | ||
| used it will be an array that sums the weights seen so far. |
There was a problem hiding this comment.
| used it will be an array that sums the weights seen so far. | |
| used it will be an array of dtype=float that sums the weights seen so far. |
There was a problem hiding this comment.
I would also update the dtype test (test_standard_scaler_dtype) to include sparse in test_data.py (it revealed some problems when we did it for dense).
This could work:
@pytest.mark.parametrize("sparse_constructor",
[None, sparse.csc_matrix, sparse.csr_matrix])
@pytest.mark.parametrize("add_sample_weight", [False, True])
def test_standard_scaler_dtype(add_sample_weight, sparse_constructor):
# Ensure scaling does not affect dtype
rng = np.random.RandomState(0)
n_samples = 10
n_features = 3
if add_sample_weight:
sample_weight = np.ones(n_samples)
else:
sample_weight = None
for dtype in [np.float16, np.float32, np.float64]:
with_mean = True
X = rng.randn(n_samples, n_features).astype(dtype)
if sparse_constructor is not None:
X = sparse_constructor(X)
with_mean = False
scaler = StandardScaler(with_mean=with_mean)
X_scaled = scaler.fit(X, sample_weight=sample_weight).transform(X)
assert X.dtype == X_scaled.dtype
assert scaler.mean_.dtype == np.float64
assert scaler.scale_.dtype == np.float64
|
@maikia @ogrisel @glemaitre I think I addressed all comments here. |
| integer, otherwise it will be an array. | ||
| If there are no missing samples, the ``n_samples_seen`` will be an | ||
| integer, otherwise it will be an array of dtype int. If | ||
| sample_weights are used it will be a float (if no missing data) |
There was a problem hiding this comment.
| sample_weights are used it will be a float (if no missing data) | |
| `sample_weights` are used it will be a float (if no missing data) |
| X = self._validate_data(X, accept_sparse=('csr', 'csc'), | ||
| estimator=self, dtype=FLOAT_DTYPES, | ||
| force_all_finite='allow-nan', reset=first_call) | ||
| n_samples, n_features = X.shape |
There was a problem hiding this comment.
| n_samples, n_features = X.shape | |
| n_features = X.shape[1] |
| elif (hasattr(self, 'n_samples_seen_') and | ||
| np.size(self.n_samples_seen_) == 1): |
There was a problem hiding this comment.
| elif (hasattr(self, 'n_samples_seen_') and | |
| np.size(self.n_samples_seen_) == 1): | |
| elif np.size(self.n_samples_seen_) == 1: |
| @pytest.mark.parametrize("sparse_constructor", | ||
| [None, sparse.csc_matrix, sparse.csr_matrix]) | ||
| def test_standard_scaler_sample_weight( | ||
| Xw, X, sample_weight, sparse_constructor): | ||
|
|
||
| with_mean = True | ||
| if sparse_constructor is not None: | ||
| X = sparse_constructor(X) | ||
| Xw = sparse_constructor(Xw) | ||
| with_mean = False | ||
| else: | ||
| Xw = np.array(Xw) | ||
| X = np.array(X) |
There was a problem hiding this comment.
| @pytest.mark.parametrize("sparse_constructor", | |
| [None, sparse.csc_matrix, sparse.csr_matrix]) | |
| def test_standard_scaler_sample_weight( | |
| Xw, X, sample_weight, sparse_constructor): | |
| with_mean = True | |
| if sparse_constructor is not None: | |
| X = sparse_constructor(X) | |
| Xw = sparse_constructor(Xw) | |
| with_mean = False | |
| else: | |
| Xw = np.array(Xw) | |
| X = np.array(X) | |
| @pytest.mark.parametrize( | |
| "array_constructor", ["array", "sparse_csr", "sparse_csc"] | |
| ) | |
| def test_standard_scaler_sample_weight( | |
| Xw, X, sample_weight, array_constructor): | |
| with_mean = not array_constructor.startswith("sparse") | |
| X, = _convert_container(X, array_constructor) | |
| Xw = _convert_container(Xw, array_constructor) |
There was a problem hiding this comment.
You need to add the following line sklearn.utils._testing._convert_container:
elif constructor_name == 'sparse_csr':
return sp.sparse.csr_matrix(container)
elif constructor_name == 'sparse_csc':
return sp.sparse.csc_matrix(container)| Axis along which the axis should be computed. | ||
|
|
||
| weights : ndarray of shape (n_samples,) or (n_features,), default=None | ||
| if axis is set to 0 shape is (n_samples,) or |
There was a problem hiding this comment.
| if axis is set to 0 shape is (n_samples,) or | |
| If axis is set to 0 shape is (n_samples,) or |
| variances : float array with shape (n_features,) | ||
| Feature-wise variances | ||
|
|
||
| sum_weights : float array with shape (n_features,) |
There was a problem hiding this comment.
| sum_weights : float array with shape (n_features,) | |
| sum_weights : ndarray of shape (n_features,), dtype=floating |
| Feature-wise variances | ||
|
|
||
| sum_weights : float array with shape (n_features,) | ||
| Returned if return_sum_weights is True. |
There was a problem hiding this comment.
| Returned if return_sum_weights is True. | |
| Returned if `return_sum_weights` is `True`. |
|
|
||
| return_sum_weights : bool, default=False | ||
| If True, returns the sum of weights seen for each feature | ||
| if axis=0 or each sample if axis=1. |
There was a problem hiding this comment.
| if axis=0 or each sample if axis=1. | |
| if `axis=0` or each sample if `axis=1`. |
|
Thanks @agramfort @maikia |
cc @maikia
Towards #15601.