KBinsDiscretizer fails inside GridSearchCV

### Describe the bug

I am trying to create a pipeline, that first imputes the missing value in a specific column using KNN-Imputer, and then uses a column transformer to discretize a specific column with KBinsDiscrtizer. The pipeline fits/transforms perfectly, but when I put that into a grid search I get a ValueError. 

The code below is able to reproduce the problem. 

Also, the code contains a function called TransformerWrap, this function mainly takes the whole DataFrame as an input to the KNNImputer and returns the imputed column with the rest of the original DataFrame. Basically, it enables me to perform imputations only on specific columns instead of the whole DataFrame. 

![ccccc](https://user-images.githubusercontent.com/32943340/177765959-7b241429-a8f1-419b-9b3f-4f407afdb112.JPG)


### Steps/Code to Reproduce

```python
import numpy as np
import pandas as pd
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import SimpleImputer, KNNImputer, IterativeImputer
from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.compose import ColumnTransformer, make_column_selector, make_column_transformer
from sklearn.preprocessing import MinMaxScaler, KBinsDiscretizer
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, make_scorer
from sklearn.model_selection import GridSearchCV, cross_val_score, ShuffleSplit, train_test_split, cross_validate
import re

## Creating synthetic data
df_test = pd.DataFrame(np.random.randint(20, 25,size=(9, 7)),
                        columns=['sex', 'age', 'sibsp', 'parch', 'fare', 'duplicated_weight', 'survived'])
df_test.loc[[1, 3], 'age'] = np.nan
df_test.loc[[5, 7], 'fare'] = np.nan

## Creating the transformer wrapping function
class TransformerWrap(BaseEstimator, TransformerMixin):
    def __init__(self, general_trans, column_name):
        self.general_trans = general_trans
        self.column_name = column_name
    def n_column_separator(self, ip_list): 
        props = [] 
        for value in ip_list: 
            if "__" not in value: 
                props.append(value) 
            elif "__" == value: 
                continue 
            elif len(value.split("__")) == 2 and '' in value.split("__") and '' == value.split("__")[0]: 
                props.append(value.split("__")[1]) 
            elif len(value.split("__")) == 2 and '' in value.split("__") and '' == value.split("__")[1]: 
                continue 
            else: 
                props.append(value.split("__")[1]) 
        return props 
    def fit(self, X, y=None):
        trans_f = self.general_trans.fit(X)
        return self
    def transform(self, X, y=None):
        X1, X2 = X.copy(deep=True), X.copy(deep=True)
        if self.column_name == 'all':
            feat_names = self.general_trans.get_feature_names_out()
            trans_t = self.general_trans.transform(X1)
            X1 = (pd.DataFrame(trans_t, columns = self.n_column_separator(feat_names)))[X1.columns]
            return X1
        else:
            feat_names = self.general_trans.get_feature_names_out()
            trans_t = self.general_trans.transform(X1)
            X1 = (pd.DataFrame(trans_t, columns = self.n_column_separator(feat_names)))[X1.columns]
            X2[self.column_name] = X1[self.column_name]
            return X2


## Creating pipeline
t_wrap = TransformerWrap(KNNImputer(), 'age')
t_wrap.fit_transform(df_test)
ct = ColumnTransformer([('age_line', KBinsDiscretizer(n_bins=3, encode='ordinal'), ['age'])],
                            remainder = 'passthrough')
pipe = make_pipeline(t_wrap, ct)
#pipe.fit_transform(df_test)


## Creating grid attributes
params = {}
cv_split = ShuffleSplit(n_splits = 10, test_size = 0.3, train_size = 0.6, random_state = 0)
scorer = make_scorer(accuracy_score)
grid = GridSearchCV(pipe, param_grid=params, cv=cv_split, scoring=scorer)

grid.fit(df_test)
```


### Expected Results

Complete fit/transform operation.

The output of
```python
grid.fit(df_test)
```
should be the exact same as 
```python
pipe.fit(df_test)
```

### Actual Results

```python-traceback
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_7964\3200302109.py in <cell line: 2>()
      1 grid = GridSearchCV(pipe, param_grid=params, cv=cv_split, scoring=scorer)
----> 2 grid.fit(df_test)

c:\Users\Amr_1\anaconda3\lib\site-packages\sklearn\model_selection\_search.py in fit(self, X, y, groups, **fit_params)
    873                 return results
    874 
--> 875             self._run_search(evaluate_candidates)
    876 
    877             # multimetric is determined here because in the case of a callable

c:\Users\Amr_1\anaconda3\lib\site-packages\sklearn\model_selection\_search.py in _run_search(self, evaluate_candidates)
   1373     def _run_search(self, evaluate_candidates):
...
  File "c:\Users\Amr_1\anaconda3\lib\site-packages\sklearn\utils\validation.py", line 146, in _assert_all_finite
    raise ValueError(msg_err)
ValueError: Input X contains NaN.
KBinsDiscretizer does not accept missing values encoded as NaN natively. For supervised learning, you might want to consider sklearn.ensemble.HistGradientBoostingClassifier and Regressor which accept missing values encoded as NaNs natively. Alternatively, it is possible to preprocess the data, for instance by using an imputer transformer in a pipeline or drop samples with missing values. See https://scikit-learn.org/stable/modules/impute.html You can find a list of all estimators that handle NaN values at the following page: https://scikit-learn.org/stable/modules/impute.html#estimators-that-handle-nan-values
```

### Versions

```shell
Python dependencies:
      sklearn: 1.1.1
          pip: 21.2.4
   setuptools: 61.2.0
        numpy: 1.21.5
        scipy: 1.7.3
       Cython: 0.29.28
       pandas: 1.4.1
   matplotlib: 3.5.1
       joblib: 1.1.0
threadpoolctl: 2.2.0
```

COMMENT:
GridSearchCV uses `ShuffleSplit` to generate this subset for training and this subset fails to `fit` on the pipeline:

```python
# This raises an error
pipe.fit(df_test.iloc[[4, 8, 6, 3, 0]])
```

This is because the `TransformerWrap.fit_transform` returns an dataframe with nans with the same subset:

```python
t_wrap.fit_transform(df_test.iloc[[4, 8, 6, 3, 0]])["age"]
# 4    24.0
# 8     NaN
# 6     NaN
# 3    23.5
# 0    24.0
# Name: age, dtype: float64
```

The issue is in `TransformerWrap` and the fix is:

```python
def transform(...):
    # Note out the index is set to `X2`.
    X1 = (pd.DataFrame(trans_t, columns = self.n_column_separator(feat_names),
                       index=X2.index))[X1.columns]
    X2[self.column_name] = X1[self.column_name]
```

The index of `X1` must be set to `X2.index` because the `X2["age"] = X1["age"]` uses indexes to copy data over.

Another fix is to convert the column into an ndarray:

```python
    X2[self.column_name] = X1[self.column_name].to_numpy()
```

This will resolves the nan issue. Note `GridSearchCV` will still fail because the pipeline does not have a classifier at the last step and there is no `y` data to evaluate accuracy.

Labels: Bug, Needs Triage