Before I say anything else, note that you are fitting your imputer on X and then on X_test. You should never do this. Instead, you should always fit your imputer on the training data and then use that instance to transform both datasets (training and testing data).
Having said that, your problem is that you are fitting and transforming all columns. As a consequence, the imputer is transforming all columns to type object.
I believe this will solve your problem:
# Impute NaNs of numeric columns
X = X.fillna(X.mean())
X_test = X_test.fillna(X_test.mean())
# Subset of categorical columns
cat = ['Loan_ID','Gender','Married','Dependents','Education','Self_Employed',
'Credit_History','Loan_Status']
# Fit Imputer on traing data and ONLY on categorical columns
object_imputer = SimpleImputer(strategy='most_frequent').fit(X[cat])
# Transform ONLY categorical columns
X[cat] = object_imputer.transform(X[cat])
X_test[cat] = object_imputer.transform(X_test[cat])
As you can see, all columns have the correct data type now.
X.dtypes
Loan_ID object
Gender object
Married object
Dependents object
Education object
Self_Employed object
ApplicantIncome int64
CoapplicantIncome float64
LoanAmount float64
Loan_Amount_Term float64
Credit_History float64
Property_Area object
Loan_Status object
dtype: object
Answer from Arturo Sbr on Stack Overflowpython - Impute categorical missing values in scikit-learn - Stack Overflow
python - sklearn SimpleImputer too slow for categorical data represented as string values - Data Science Stack Exchange
Understanding behavior of Simple Imputer with categorical values
SimpleImputer strategy "most_frequent": infinite loop for categorical variables as strings
To use mean values for numeric columns and the most frequent value for non-numeric columns you could do something like this. You could further distinguish between integers and floats. I guess it might make sense to use the median for integer columns instead.
import pandas as pd
import numpy as np
from sklearn.base import TransformerMixin
class DataFrameImputer(TransformerMixin):
def __init__(self):
"""Impute missing values.
Columns of dtype object are imputed with the most frequent value
in column.
Columns of other types are imputed with mean of column.
"""
def fit(self, X, y=None):
self.fill = pd.Series([X[c].value_counts().index[0]
if X[c].dtype == np.dtype('O') else X[c].mean() for c in X],
index=X.columns)
return self
def transform(self, X, y=None):
return X.fillna(self.fill)
data = [
['a', 1, 2],
['b', 1, 1],
['b', 2, 2],
[np.nan, np.nan, np.nan]
]
X = pd.DataFrame(data)
xt = DataFrameImputer().fit_transform(X)
print('before...')
print(X)
print('after...')
print(xt)
which prints,
before...
0 1 2
0 a 1 2
1 b 1 1
2 b 2 2
3 NaN NaN NaN
after...
0 1 2
0 a 1.000000 2.000000
1 b 1.000000 1.000000
2 b 2.000000 2.000000
3 b 1.333333 1.666667
You can use sklearn_pandas.CategoricalImputer for the categorical columns. Details:
First, (from the book Hands-On Machine Learning with Scikit-Learn and TensorFlow) you can have subpipelines for numerical and string/categorical features, where each subpipeline's first transformer is a selector that takes a list of column names (and the full_pipeline.fit_transform() takes a pandas DataFrame):
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names].values
You can then combine these sub pipelines with sklearn.pipeline.FeatureUnion, for example:
full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline)
])
Now, in the num_pipeline you can simply use sklearn.preprocessing.Imputer(), but in the cat_pipline, you can use CategoricalImputer() from the sklearn_pandas package.
note: sklearn-pandas package can be installed with pip install sklearn-pandas, but it is imported as import sklearn_pandas