解决sklearn中使用OrdinalEncoder编码测试集的类别特征中的未知类别时会报错的问题

解决sklearn中使用OrdinalEncoder方法将类别特征(categorical/discrete features)变为有序数值特征(ordinal integers)时无法编码测试集的类别特征中的未知类别的问题

当数据集中存在类别特征时(categorical/discrete features),我们一般的想法是将其转变为数值型的特征,比如如果是不存在内在高低顺序的类别特征,便可以使用sklearn中的OneHotEncoder方法将其转变为数值型的特征,但是OneHotEncoder也会令数据集中的特征数量激增,以至于模型复杂度升高。

而另一种将类别特征转变为数值型特征的方法则为OrdinalEncoder。OrdinalEncoder方法的一个特点是其可以根据标签y来对类别特征进行顺序编码,比如[ [“北京”, 9], [“上海”,11], [“深圳”, 8] ]这个数据中,第一个特征为地点类别特征,第二个假设为标签,在这里可以看出不同的地点其标签是有顺序上的差异的,这种情况下的类别特征就很适合使用OrdinalEncoder方法来进行数值型编码。

但是sklearn中的OrdinalEncoder方法根据训练集的数据调用其内部的fit_transform()函数进行拟合之后,如果在用其对测试集中的类别特征进行数值型编码转换时,测试集的类别特征中有未知类别(即训练集中此类别特征中未出现过的类别)时,OrdinalEncoder方法内的transform函数则会报错。sklearn中的OrdinalEncoder方法如下所示,在初始化时是没有指定怎么处理测试集中未见类别的参数的 (而OneHotEncoder方法在初始化时则有指定处理测试集中未见类别的参数)。
在这里插入图片描述

解决这个问题的方法可以从OrdinalEncoder的源码中找到, OrdinalEncoder方法中的transform函数如下所示:
在这里插入图片描述
从上图可以看出OrdinalEncoder类中的transform函数是直接调用OrdinalEncoder类的父类中的self._transform()函数以及一些后续操作来对测试集中的类别特征进行转化的,而OrdinalEncoder类则是继承自_BaseEncoder类:
在这里插入图片描述
因此去查看_BaseEncoder类中的_transform()函数时可以发现其实_BaseEncoder类中的_transform()函数中是有指定怎么处理测试集中未见类别的参数 handle_unknown 的,如下图:
在这里插入图片描述
从_BaseEncoder类中的_transform()函数源码中可以看出,当将handle_unknown参数由"error"值改成别的任意值时,测试集类别特征中的未见类别将被编码为0,而不是直接报错。
因此当使用OrdinalEncoder去对含有未见类别特征的测试集进行编码时,可以不使用OrdinalEncoder类中的transform()函数,而是直接调用OrdinalEncoder类所继承的_BaseEncoder类中的_transform()函数来对测试集进行编码。此时_transform()函数返回的为一个元组对象,这个元组对象中的第一个元素为我们想要的对于测试集的编码结果;而第二个元素则为编码过程中的副产品mask矩阵,可以舍弃。再对测试集的编码结果进行astype()操作,最后得到的即为最终的测试集编码结果。

详细操作代码如下:
初始化训练集与测试集:

from sklearn.preprocessing import OrdinalEncoder
import pandas as pd
import numpy as np

train_data = [["北京","互联网" ,9],
        ["北京","金融" ,10],
        ["上海","金融" ,11],
        ["深圳","互联网" ,8],
        ["广州","互联网" ,7.5],
        ["天津","银行" ,6],]
        
test_data = [["杭州","金融"],]

train = pd.DataFrame()
test = pd.DataFrame()

for item in train_data:
    train = train.append({"location": item[0], "occupation": item[1], "salary":item[2]}, ignore_index=True)

for item in test_data:
    test = test.append({"location": item[0], "occupation": item[1]}, ignore_index=True)

# OrdinalEncoder()先拟合训练集
ordinal_encoder = OrdinalEncoder()
train = ordinal_encoder.fit_transform(X=train.iloc[:,0:-1], y=train.iloc[:,-1])

当用OrdinalEncoder类中的transform()函数对含有未见类别特征的测试集进行编码时,会报如下错误,可以看出测试集的地点特征中的 “杭州” 为未见类别。

test = ordinal_encoder.transform(test)
# 报错如下
ValueError                                Traceback (most recent call last)
<ipython-input-17-7c9a92d31607> in <module>
----> 1 test = ordinal_encoder.transform(test)
      2 test

/opt/conda/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in transform(self, X)
    955 
    956         """
--> 957         X_int, _ = self._transform(X)
    958         return X_int.astype(self.dtype, copy=False)
    959 

/opt/conda/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in _transform(self, X, handle_unknown)
    120                     msg = ("Found unknown categories {0} in column {1}"
    121                            " during transform".format(diff, i))
--> 122                     raise ValueError(msg)
    123                 else:
    124                     # Set the problematic rows to an acceptable value and

ValueError: Found unknown categories ['杭州'] in column 0 during transform

当使用OrdinalEncoder类所继承的_BaseEncoder类中的_transform()函数,令handle_unknown参数为 ‘ignore’ ,再对测试集的编码结果进行astype()操作,结果如下:

test = ordinal_encoder._transform(test, handle_unknown='ignore')[0].astype(np.float64, copy=False)
test
# 结果如下
array([[0., 1.]])

可以看出_transform()函数将测试集中地点类别特征中的未见类别 “杭州” 可以编码为0,而不会直接报错。

以上过程即为解决方法, 在最后再重复强调一下解决办法:
使用OrdinalEncoder去对含有未见类别特征的测试集进行编码时,可以不使用OrdinalEncoder类中的transform()函数,而是直接调用OrdinalEncoder类所继承的_BaseEncoder类中的_transform()函数来对测试集进行编码。此时_transform()函数返回的为一个元组对象,这个元组对象中的第一个元素为我们想要的对于测试集的编码结果;而第二个元素则为编码过程中的副产品mask矩阵,可以舍弃。再对测试集的编码结果进行astype()操作,最后得到的即为最终的测试集编码结果。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值