Repository: scikit-learn/scikit-learn
Pull request: FIX Adds check_array to inverse_transform of StandardScaler
Reference Issues/PRs

Fixes  #19354

What does this implement/fix? Explain your changes.

This PR checks the input array of the `inverse_transform` function of the StandardScaler similar like its done in the MinMaxScaler.



Problem statement:
Input format for StandardScaler
#### Describe the issue linked to the documentation
In the documentation the input array for the StandardScaler is given by:
scale_ndarray of shape (n_features,) or None
I have the following example where I transform an int array and a float array:

```
from sklearn.preprocessing import StandardScaler
import numpy as np

x = np.array([
	[1, 1, 1, 0, 1, 0],
	[1, 1, 1, 0, 1, 0],
	[0, 8, 0, 1, 0, 0],
	[1, 4, 1, 1, 0, 0],
	[0, 1, 0, 0, 1, 0],
	[0, 4, 0, 1, 0, 1]])

preprocess_fn=StandardScaler()
x[:,[1]] = preprocess_fn.fit_transform(x[:,[1]])
print(x)
```
> [[1 0 1 0 1 0]
 [1 0 1 0 1 0]
 [0 1 0 1 0 0]
 [1 0 1 1 0 0]
 [0 0 0 0 1 0]
 [0 0 0 1 0 1]]

In this case I wanted to transform col 1 of my array. The result looks like that the number are rounded or converted to ints.
```
x[:,[1]] = preprocess_fn.inverse_transform(x[:,[1]])
print(x)
```
When I call inverse_transform I am getting:

```Traceback (most recent call last):
  File "../test.py", line 16, in <module>
    x[:,[1]] = preprocess_fn.inverse_transform(x[:,[1]])
  File"../.local/lib/python3.9/site-packages/sklearn/preprocessing/_data.py", line 934, in inverse_transform
    X *= self.scale_
numpy.core._exceptions.UFuncTypeError: Cannot cast ufunc 'multiply' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
```
I would say this can be solved with `X = X * self.scale_` but doing this by hand gives me the following array:

>[[1 0 1 0 1 0]
 [1 0 1 0 1 0]
 [0 2 0 1 0 0]
 [1 0 1 1 0 0]
 [0 0 0 0 1 0]
 [0 0 0 1 0 1]]

Which is different from the original one. Casting the array to float from the beginning I don't have this problems.

<!--
Tell us about the confusion introduced in the documentation.
-->
For me it was not clear from the documentation if only float arrays are allowed. But I am not sure if this is only a documentation problem, because the Traceback error should be resolved as well or there should be a check if the array is of int type.

#### Suggest a potential alternative/fix

<!--
Tell us how we could improve the documentation in this regard.
-->
scale_ndarray of shape (n_features,) or None and type float.

System:
- sklearn 0.24.0
- python 3.9.1
- numpy 1.19.5

Hints from issue discussion:
The output of `preprocess_fn.transform` is a float array:

```py
from sklearn.preprocessing import StandardScaler
import numpy as np

x = np.array([
	[1, 1, 1, 0, 1, 0],
	[1, 1, 1, 0, 1, 0],
	[0, 8, 0, 1, 0, 0],
	[1, 4, 1, 1, 0, 0],
	[0, 1, 0, 0, 1, 0],
	[0, 4, 0, 1, 0, 1]])

preprocess_fn=StandardScaler()
result = preprocess_fn.fit_transform(x[:,[1]])
print(result)
# [[-0.85165832]
# [-0.85165832]
# [ 1.89985317]
# [ 0.32756089]
# [-0.85165832]
# [ 0.32756089]]
```

But the following line converts it to an int when placing it into `X`.

```py
x[:, [1]] = result
print(x)
# [[1 0 1 0 1 0]
# [1 0 1 0 1 0]
# [0 1 0 1 0 0]
# [1 0 1 1 0 0]
# [0 0 0 0 1 0]
# [0 0 0 1 0 1]]
```

This means the input of `inverse_transform` does not match the output of `fit_transform`. Which results in the inverse not mapping back to the original value.

Secondly, it would make sense for `inverse_transform` to cast to an float anyways, as the input to `fit_transform` are converted to floats.
Thank you for the quick replay, I did not saw this.

For the second point: Maybe by adding `X = check_array(X, copy=self.copy, dtype=FLOAT_DTYPES, force_all_finite="allow-nan")` similar like in `inverse_transform` in the MinMaxScaler?
Yup. We can also add `accept_sparse='csr'` to handle the sparse case as well.

Commit message: add check_array to inverse_transform of StandardScaler

Patch to review:
diff --git a/sklearn/preprocessing/_data.py b/sklearn/preprocessing/_data.py
index 5814875e04bff..a1cf54d4ff445 100644
--- a/sklearn/preprocessing/_data.py
+++ b/sklearn/preprocessing/_data.py
@@ -917,6 +917,9 @@ def inverse_transform(self, X, copy=None):
         check_is_fitted(self)
 
         copy = copy if copy is not None else self.copy
+        X = check_array(X, accept_sparse='csr', copy=self.copy, 
+                        dtype=FLOAT_DTYPES, force_all_finite="allow-nan")
+                
         if sparse.issparse(X):
             if self.with_mean:
                 raise ValueError(


Code review comments:
diff_hunk: @@ -917,6 +917,9 @@ def inverse_transform(self, X, copy=None):
         check_is_fitted(self)
 
         copy = copy if copy is not None else self.copy
+        X = check_array(X, accept_sparse='csr', copy=self.copy, 
path: sklearn/preprocessing/_data.py
text: @user1:
```suggestion
        X = check_array(X, accept_sparse='csr', copy=copy, 
```

Some of the code below this that handles the csr conversion and the copying are not needed anymore.

@user2:
You can delete the lines
```python
if not sparse.isspmatrix_csr(X):
    X = X.tocsr()
    copy = False
if copy:
    X = X.copy()
```
and
```python
X = np.asarray(X)
if copy:
    X = X.copy()
```