在Scikit-learn中跨多列进行标签编码

323

我正在尝试使用scikit-learn的LabelEncoder对pandas中的字符串标签DataFrame进行编码。由于数据帧有许多(50+)列,我想避免为每个列创建一个LabelEncoder对象;我宁愿只有一个可以在所有数据列上工作的大型LabelEncoder对象。

将整个DataFrame放入LabelEncoder会产生以下错误。请注意,这里我使用的是虚拟数据;实际上,我正在处理大约50个字符串标记数据列,因此需要一种不引用任何列名称的解决方案。

import pandas
from sklearn import preprocessing 

df = pandas.DataFrame({
    'pets': ['cat', 'dog', 'cat', 'monkey', 'dog', 'dog'], 
    'owner': ['Champ', 'Ron', 'Brick', 'Champ', 'Veronica', 'Ron'], 
    'location': ['San_Diego', 'New_York', 'New_York', 'San_Diego', 'San_Diego', 
                 'New_York']
})

le = preprocessing.LabelEncoder()

le.fit(df)

Traceback (most recent call last): File "", line 1, in File "/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/preprocessing/label.py", line 103, in fit y = column_or_1d(y, warn=True) File "/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/utils/validation.py", line 306, in column_or_1d raise ValueError("bad input shape {0}".format(shape)) ValueError: bad input shape (6, 3)

您有什么想法可以解决这个问题吗?


为了简化对包含字符串数据的多列dataframe进行编码,我正在选择编码对象,因此希望避免对50个独立对象进行pickle/unpickle操作。另外,我想知道是否有一种方法可以让编码器简化数据,例如只返回每列中变量的唯一组合的一个标识符所在的行。 - Bryan
可以通过将一个字典的字典传递给 replace 方法,在 pandas 中以简单的方式完成所有操作。请参见下面的答案 - Ted Petrou
1
scikit-learn 0.20 开始,不需要实现自定义类来对多列进行标签编码。您可以直接使用OrdinalEncoder - Ric S
25个回答

5
我查看了LabelEncoder的源代码(https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/label.py)。它是基于一组numpy变换构建的,其中之一是np.unique()。而这个函数只接受1维数组输入。(如果我错了,请纠正我)。
粗略的想法是... 首先,确定哪些列需要使用LabelEncoder,然后循环遍历每一列。
def cat_var(df): 
    """Identify categorical features. 

    Parameters
    ----------
    df: original df after missing operations 

    Returns
    -------
    cat_var_df: summary df with col index and col name for all categorical vars
    """
    col_type = df.dtypes
    col_names = list(df)

    cat_var_index = [i for i, x in enumerate(col_type) if x=='object']
    cat_var_name = [x for i, x in enumerate(col_names) if i in cat_var_index]

    cat_var_df = pd.DataFrame({'cat_ind': cat_var_index, 
                               'cat_name': cat_var_name})

    return cat_var_df



from sklearn.preprocessing import LabelEncoder 

def column_encoder(df, cat_var_list):
    """Encoding categorical feature in the dataframe

    Parameters
    ----------
    df: input dataframe 
    cat_var_list: categorical feature index and name, from cat_var function

    Return
    ------
    df: new dataframe where categorical features are encoded
    label_list: classes_ attribute for all encoded features 
    """

    label_list = []
    cat_var_df = cat_var(df)
    cat_list = cat_var_df.loc[:, 'cat_name']

    for index, cat_feature in enumerate(cat_list): 

        le = LabelEncoder()

        le.fit(df.loc[:, cat_feature])    
        label_list.append(list(le.classes_))

        df.loc[:, cat_feature] = le.transform(df.loc[:, cat_feature])

    return df, label_list

返回的 df 是编码后的数据,而 label_list 将向您展示相应列中所有值的含义。这是我为工作编写的数据处理脚本的一部分。如果您认为还有进一步改进的地方,请告诉我。 编辑: 在此提醒,上述方法适用于没有缺失数据的数据框架。不确定它如何处理包含缺失数据的数据框架。(在执行上述方法之前,我需要处理缺失程序)

4
如果你的数据框中有数值和分类数据,你可以使用以下代码:这里 X 是我的数据框,包含分类和数值变量。
from sklearn import preprocessing
le = preprocessing.LabelEncoder()

for i in range(0,X.shape[1]):
    if X.dtypes[i]=='object':
        X[X.columns[i]] = le.fit_transform(X[X.columns[i]])

注意:如果您不想将它们转换回来,这种技术非常适用。

4

经过大量的搜索和尝试,结合这里和其他一些答案,我认为你的答案在这里

pd.DataFrame(columns=df.columns, data=LabelEncoder().fit_transform(df.values.flatten()).reshape(df.shape))

这将在列之间保留类别名称:

import pandas as pd
from sklearn.preprocessing import LabelEncoder

df = pd.DataFrame([['A','B','C','D','E','F','G','I','K','H'],
                   ['A','E','H','F','G','I','K','','',''],
                   ['A','C','I','F','H','G','','','','']], 
                  columns=['A1', 'A2', 'A3','A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'A10'])

pd.DataFrame(columns=df.columns, data=LabelEncoder().fit_transform(df.values.flatten()).reshape(df.shape))

    A1  A2  A3  A4  A5  A6  A7  A8  A9  A10
0   1   2   3   4   5   6   7   9   10  8
1   1   5   8   6   7   9   10  0   0   0
2   1   3   9   6   8   7   0   0   0   0

4
使用字典(dict())快速对多个列进行LabelEncoder()处理的方法如下:
from sklearn.preprocessing import LabelEncoder
le_dict = {col: LabelEncoder() for col in columns }
for col in columns:
    le_dict[col].fit_transform(df[col])

你可以使用这个le_dict来进行任何其他列的标签编码:

le_dict[col].transform(df_another[col])

4

可以使用scikit learn中的OrdinalEncoder替代LabelEncoder,它允许对多列进行编码。

将分类特征编码为整数数组。 该转换器的输入应为整数或字符串数组,表示分类(离散)特征所采用的值。这些特征被转换为序数整数。结果是每个特征生成一个整数列(从 0 到 n_categories - 1)。

>>> from sklearn.preprocessing import OrdinalEncoder
>>> enc = OrdinalEncoder()
>>> X = [['Male', 1], ['Female', 3], ['Female', 2]]
>>> enc.fit(X)
OrdinalEncoder()
>>> enc.categories_
[array(['Female', 'Male'], dtype=object), array([1, 2, 3], dtype=object)]
>>> enc.transform([['Female', 3], ['Male', 1]])
array([[0., 2.],
       [1., 0.]])

这段描述和示例都是从它的文档页面复制而来,你可以在这里找到:

https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OrdinalEncoder.html#sklearn.preprocessing.OrdinalEncoder


1
这应该是从 scikit-learn 0.20 开始的正确方式。感谢您的建议! - Ric S

3

使用Neuraxle

简而言之,您可以使用FlattenForEach包装类来简单地转换您的df,如下所示:FlattenForEach(LabelEncoder(), then_unflatten=True).fit_transform(df)

使用此方法,您的标签编码器将能够在常规scikit-learn Pipeline中拟合和转换。让我们简单导入:

from sklearn.preprocessing import LabelEncoder
from neuraxle.steps.column_transformer import ColumnTransformer
from neuraxle.steps.loop import FlattenForEach

所有列共享同一个编码器:

以下是应用单个共享的LabelEncoder对所有数据进行编码的方法:

    p = FlattenForEach(LabelEncoder(), then_unflatten=True)

结果:

    p, predicted_output = p.fit_transform(df.values)
    expected_output = np.array([
        [6, 7, 6, 8, 7, 7],
        [1, 3, 0, 1, 5, 3],
        [4, 2, 2, 4, 4, 2]
    ]).transpose()
    assert np.array_equal(predicted_output, expected_output)

每列使用不同的编码器:

这里介绍了如何对宠物使用第一个独立的LabelEncoder,并在所有者和位置列之间共享第二个编码器。因此,确切地说,我们在这里使用了不同的和共享的标签编码器:

    p = ColumnTransformer([
        # A different encoder will be used for column 0 with name "pets":
        (0, FlattenForEach(LabelEncoder(), then_unflatten=True)),
        # A shared encoder will be used for column 1 and 2, "owner" and "location":
        ([1, 2], FlattenForEach(LabelEncoder(), then_unflatten=True)),
    ], n_dimension=2)

结果:

    p, predicted_output = p.fit_transform(df.values)
    expected_output = np.array([
        [0, 1, 0, 2, 1, 1],
        [1, 3, 0, 1, 5, 3],
        [4, 2, 2, 4, 4, 2]
    ]).transpose()
    assert np.array_equal(predicted_output, expected_output)

2
针对@PriceHardman的解决方案所提出的评论,我建议采用以下版本的类:
class LabelEncodingColoumns(BaseEstimator, TransformerMixin):
def __init__(self, cols=None):
    pdu._is_cols_input_valid(cols)
    self.cols = cols
    self.les = {col: LabelEncoder() for col in cols}
    self._is_fitted = False

def transform(self, df, **transform_params):
    """
    Scaling ``cols`` of ``df`` using the fitting

    Parameters
    ----------
    df : DataFrame
        DataFrame to be preprocessed
    """
    if not self._is_fitted:
        raise NotFittedError("Fitting was not preformed")
    pdu._is_cols_subset_of_df_cols(self.cols, df)

    df = df.copy()

    label_enc_dict = {}
    for col in self.cols:
        label_enc_dict[col] = self.les[col].transform(df[col])

    labelenc_cols = pd.DataFrame(label_enc_dict,
        # The index of the resulting DataFrame should be assigned and
        # equal to the one of the original DataFrame. Otherwise, upon
        # concatenation NaNs will be introduced.
        index=df.index
    )

    for col in self.cols:
        df[col] = labelenc_cols[col]
    return df

def fit(self, df, y=None, **fit_params):
    """
    Fitting the preprocessing

    Parameters
    ----------
    df : DataFrame
        Data to use for fitting.
        In many cases, should be ``X_train``.
    """
    pdu._is_cols_subset_of_df_cols(self.cols, df)
    for col in self.cols:
        self.les[col].fit(df[col])
    self._is_fitted = True
    return self

这个类适用于训练集上的编码器,并在转换时使用已适配的版本。代码的初始版本可以在这里找到。

1

主要使用了@Alexander的答案,但不得不进行一些更改 -

cols_need_mapped = ['col1', 'col2']

mapper = {col: {cat: n for n, cat in enumerate(df[col].astype('category').cat.categories)} 
     for col in df[cols_need_mapped]}

for c in cols_need_mapped :
    df[c] = df[c].map(mapper[c])

然后,要在将来重新使用它,您只需将输出保存到 json 文档中,当您需要时读取它并像上面一样使用 .map() 函数。


1
import pandas as pd
from sklearn.preprocessing import LabelEncoder

train=pd.read_csv('.../train.csv')

#X=train.loc[:,['waterpoint_type_group','status','waterpoint_type','source_class']].values
# Create a label encoder object 
def MultiLabelEncoder(columnlist,dataframe):
    for i in columnlist:

        labelencoder_X=LabelEncoder()
        dataframe[i]=labelencoder_X.fit_transform(dataframe[i])
columnlist=['waterpoint_type_group','status','waterpoint_type','source_class','source_type']
MultiLabelEncoder(columnlist,train)

在这里,我正在从位置读取CSV文件,并在函数中传递我想要进行标签编码的列列表和我想要应用此操作的数据帧。


1
如果您拥有类型对象的所有功能,则上面写的第一个答案很有效。https://dev59.com/RWAf5IYBdhLWcg3wnDwI#31939145。但是,假设我们有混合类型列。然后,我们可以通过编程方式获取类型对象类型的特征名称列表,然后对它们进行标签编码。
#Fetch features of type Object
objFeatures = dataframe.select_dtypes(include="object").columns

#Iterate a loop for features of type object
from sklearn import preprocessing
le = preprocessing.LabelEncoder()

for feat in objFeatures:
    dataframe[feat] = le.fit_transform(dataframe[feat].astype(str))
 

dataframe.info()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接