机器学习缺失值处理实例——加州housing数据集

这里写自定义目录标题

    • 一、数据集描述
    • 二、实验方向
    • 三、中位数补足
    • 四、比例补足与回归预测补足
    • 五、在不同缺失值比例下的预处理方式比较
    • 六、结论

一、数据集描述

这个数据集原来是上学期课程中的一个实例,是由9个数字特征+1个分类特征组成的数据集。简而言之,是通过加州地产的一些特征来预测房价的回归问题。9个输入特征有: 
X={‘longitude’,‘latitude’,‘housing_median_age’,‘total_rooms’, ‘total_bedrooms’,‘population’,‘households’,‘median_income’,‘ocean_proximity’}
其中,‘ocean_proximity’为分类特征,共有五类{‘NEAR BAY’, ‘<1H OCEAN’, ‘INLAND’, ‘NEAR OCEAN’, ‘ISLAND’}。
输出为:
Y={‘median_house_value’},

二、实验方向

在数据集中,total_bedrooms存在1%的缺失值。

// An highlighted block
housing.count()
longitude             20640
latitude              20640
housing_median_age    20640
total_rooms           20640
total_bedrooms        20433
population            20640
households            20640
median_income         20640
median_house_value    20640
ocean_proximity       20640
dtype: int64

而在《Hands On Machine Learning with Sklearn and Tensorflow》的例子中,选择直接使用中位数补足空值。如果缺失的特征独立于其他特征,难以估计的话,中位数补足不失为一种简单快捷的方式。然而在这个数据集中,从住宅建筑性质上来看,total_bedrooms和total_rooms存在必然的正相关。因此,我们不妨先踢出所有的缺失行,用完整行计算卧室房间比即sum(total_bedrooms)/sum(total_rooms),按ratio*total_rooms来补足缺失的值。如果再更进一步,我们不妨假设total_bedrooms这个特征和其他所有特征都是相关的,在预测y之前,先通过除了y和total_bedrooms外的8个特征对total_bedrooms做一次预测,使用预测值来补足缺失值。

综上,本文将尝试使用三种缺失值处理方式(中位数补足,比例补足,回归预测补足)预处理原数据集,并通过最终模型的性能比较不同处理方式的效果。

三、中位数补足

建立中位数补足数值处理管道。(注:CombinedAttributesAdder为一个特征拓展类,这里不多讨论)

### Standardize numerical attibutes and create pipelines

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
        ('imputer', SimpleImputer(strategy="median")),
        ('attribs_adder', CombinedAttributesAdder(add_bedrooms_per_room = False)),
        ('std_scaler', StandardScaler()),
    ])

housing_num_tr = num_pipeline.fit_transform(housing_num)

将分类特征转化为稀疏矩阵,并建立完整管道。

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder

num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

full_pipeline = ColumnTransformer([
        ("num", num_pipeline, num_attribs),
        ("cat", OneHotEncoder(), cat_attribs),
    ])

使用中位数补足管道预处理数据并训练模型。

housing_prepared = full_pipeline.fit_transform(housing)
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)

将模型应用在测试集,最终r2_score=0.63667。

X_test = test_set.drop("median_house_value", axis=1)
y_test = test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)
lin_reg.score(X_test_prepared,y_test)
0.6366701440357952

四、比例补足与回归预测补足

为了控制变量,分类特征和其他数值特征的处理方法与上一管道保持一致。因此只需新建两个对象来替换SimpleImputer来处理缺失值即可。
新建比例补足器:

### Use bedrooms/rooms ratio to fill Nan

class FillBedrooms(BaseEstimator, TransformerMixin):
    def __init__(self):
        pass
    def fit(self,X,y=None):
        X_complete = X.dropna(how = 'any')
        self.bedrooms_per_room = X_complete['total_bedrooms'].sum()/X_complete['total_rooms'].sum()
        return self
    def transform(self,X,y=None):
        X_returned = X.fillna(0)
        for i in range(len(X)):
            if X_returned.iloc[i,bedrooms_ix] == 0:
                X_returned.iloc[i,bedrooms_ix] = np.around(X_returned.iloc[i,rooms_ix] * self.bedrooms_per_room)
        return X_returned.values

新建回归预测补足器:

### Use machine learning to fillna

class FillBedrooms3(BaseEstimator, TransformerMixin):
    def __init__(self):
        pass
    def fit(self,X,y=None):
        X_complete = X.dropna(how = 'any')
        X_train = X_complete.drop('total_bedrooms',axis=1)
        X_labels = X_complete['total_bedrooms'].copy()
        self.X_lin_reg = LinearRegression()
        self.X_lin_reg.fit(X_train,X_labels)
        return self
    def transform(self,X,y=None):
        X_returned = X.fillna(0)
        X_all = X.drop('total_bedrooms',axis=1)
        X_pred = self.X_lin_reg.predict(X_all)
        for i in range(len(X)):
            if X_returned.iloc[i,bedrooms_ix] == 0:
                X_returned.iloc[i,bedrooms_ix] = np.around(X_pred[i])
        return X_returned.values

将新的补足器替换到管道中,在测试集中的r2_score分别为:

lin_reg2.score(housing_prepared2, housing_labels)
0.6372553330188473
lin_reg3.score(housing_prepared3, housing_labels)
0.6375454198371918

从模型性能上看,使用比例补足器和回归补足器替换中位数补足器后,确定系数都有所提高。但是提升并不明显,于是我猜测是否因为是缺失值占比仅1%,提升的空间不大造成的。为了验证这个猜想,我设计了第二个实验。

五、在不同缺失值比例下的预处理方式比较

在第二个实验中,我尝试通过额外drop掉一些值的方式来控制缺失值占数据集的比例。通过制造更多的缺失值来给补足器更大的表演空间,并通过r2score和rmse来评价最终模型性能。

创建结果输出list:

median_filler_score=[]
median_filler_rmse=[]
ratio_filler_score=[]
ratio_filler_rmse=[]
ml_filler_score=[]
ml_filler_rmse=[]

记录缺失值比例在1%~40%范围内时,中位数补足器的性能指标:

for perc in range(40):
    housing = pd.read_csv('housing.csv')
    drop_ix = range(206*perc)
    for i in drop_ix:
        housing.loc[i,'total_bedrooms'] = None
    train_set,test_set = train_test_split(housing,test_size=0.2,random_state=0)
    housing = train_set.drop('median_house_value',axis=1)
    housing_labels = train_set['median_house_value'].copy()
    X_test = test_set.drop("median_house_value", axis=1)
    y_test = test_set["median_house_value"].copy()
    
    ### Let's see how median filler works
    num_pipeline = Pipeline([
        ('imputer', SimpleImputer(strategy="median")),
        ('attribs_adder', CombinedAttributesAdder(add_bedrooms_per_room = False)),
        ('std_scaler', StandardScaler()),
    ])
    housing_num = housing.select_dtypes(include = 'float')
    num_attribs = list(housing_num)
    cat_attribs = ["ocean_proximity"]
    full_pipeline = ColumnTransformer([
            ("num", num_pipeline, num_attribs),
            ("cat", OneHotEncoder(), cat_attribs),
        ])
    housing_prepared = full_pipeline.fit_transform(housing)
    lin_reg = LinearRegression()
    lin_reg.fit(housing_prepared, housing_labels)
    X_test_prepared = full_pipeline.transform(X_test)
    median_filler_score.append(lin_reg.score(X_test_prepared,y_test))
    y_pred = lin_reg.predict(X_test_prepared)
    lin_mse = mean_squared_error(y_test, y_pred)
    lin_rmse = np.sqrt(lin_mse)
    median_filler_rmse.append(lin_rmse)

同样的,记录其他两种补足器的性能指标:

    ratio_filler_score.append(lin_reg2.score(X_test_prepared2,y_test))
    ratio_filler_rmse.append(lin_rmse2) 
    ml_filler_score.append(lin_reg3.score(X_test_prepared3,y_test))
    ml_filler_rmse.append(lin_rmse3) 

r2score曲线(x轴为缺失值比例,y轴为r2score):

fig,ax = plt.subplots(1,1)
ax.plot(range(40),median_filler_score,label='median')
ax.plot(range(40),ratio_filler_score,label='ratio')
ax.plot(range(40),ml_filler_score,label='ml')
ax.legend()

机器学习缺失值处理实例——加州housing数据集_第1张图片
以中位数补足为基准的r2score提升曲线:

ratio_median = []
ml_median = []
for i in range(40):
    ratio_median.append(ratio_filler_score[i]/median_filler_score[i]) 
    ml_median.append(ml_filler_score[i]/median_filler_score[i])
fig,ax1 = plt.subplots(1,1)
ax1.plot(range(40),np.ones(40),label='median')
ax1.plot(range(40),ratio_median,label='ratio')
ax1.plot(range(40),ml_median,label='ml')
ax1.legend()

机器学习缺失值处理实例——加州housing数据集_第2张图片
从确定系数来看,比例补足在1%~20%的区间内补足效果和回归补足差不多,但是在更高的确实比例下还是回归补足更好。当数据集过大计算力不够时,在合适的缺失比例区间使用比例补足也是合理的选择。

再来看看rmse的比较。rmse曲线(x轴为缺失值比例,y轴为rmse):
机器学习缺失值处理实例——加州housing数据集_第3张图片
机器学习缺失值处理实例——加州housing数据集_第4张图片
rmse的表现和r2score的表现一致。

六、结论

在算力允许的情况下,回归补足是本数据集中性能最理想的补足方式。但这一结论是基于在这个数据集中缺失值与其他值的强相关性的条件下的,如果在另一个各个特征完全独立的数据集下,使用回归补足可能会带了计算力的浪费且并不一定能带来模型性能的提升。归根结底,数据预处理带来的模型性能提升,是从避免缺失行成为噪点的角度提升的。如果特征相对独立,回归补足不能补充一个合理值到缺失值的位置时,处理后的缺失行仍然是数据集中的一个噪点。在这种情况下,中位数或者均值补足或许是最好的选择。即在一个完全独立的特征中,预测依据的来源最好是这个特征本身,即它的分布特征,中位数,均值等。

你可能感兴趣的:(机器学习,数据预处理,数据挖掘,缺失值处理)