在scikit-learn中对多个列进行标签编码
我试图使用scikit学习的LabelEncoder
来编码一个pandasDataFrame
的string标签。 由于数据框有许多(50+)列,我想避免为每列创build一个LabelEncoder
对象; 我宁愿只有一个大的LabelEncoder
对象,可以在我所有的数据列中工作。
将整个DataFrame
扔到LabelEncoder
会产生下面的错误。 请记住,我在这里使用虚拟数据; 实际上我正在处理约50列string标签的数据,所以需要一个解决scheme,不要按名称引用任何列。
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 "<stdin>", line 1, in <module> 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)
有关如何解决这个问题的任何想法?
你可以很容易地做到这一点,
df.apply(LabelEncoder().fit_transform)
编辑:
由于这个答案是一年多以前,并且产生了许多赞扬(包括赏金),所以我可能会进一步扩大这一点。
对于inverse_transform和transform,你必须做一些破解。
from collections import defaultdict d = defaultdict(LabelEncoder)
有了这个,你现在保留所有列LabelEncoder
作为字典。
# Encoding the variable fit = df.apply(lambda x: d[x.name].fit_transform(x)) # Inverse the encoded fit.apply(lambda x: d[x.name].inverse_transform(x)) # Using the dictionary to label future data df.apply(lambda x: d[x.name].transform(x))
正如larsmans所提到的, LabelEncoder()只需要一个一维数组作为参数 。 也就是说,在您select的多个列上运行自己的标签编码器并返回转换后的dataframe相当容易。 我的代码部分基于Zac Stewart在这里find的优秀博客文章。
创build自定义编码器涉及到简单地创build一个响应fit()
, transform()
和fit_transform()
方法的类。 就你而言,一个好的开始可能是这样的:
import pandas as pd from sklearn.preprocessing import LabelEncoder from sklearn.pipeline import Pipeline # Create some toy data in a Pandas dataframe fruit_data = pd.DataFrame({ 'fruit': ['apple','orange','pear','orange'], 'color': ['red','orange','green','green'], 'weight': [5,6,3,4] }) class MultiColumnLabelEncoder: def __init__(self,columns = None): self.columns = columns # array of column names to encode def fit(self,X,y=None): return self # not relevant here def transform(self,X): ''' Transforms columns of X specified in self.columns using LabelEncoder(). If no columns specified, transforms all columns in X. ''' output = X.copy() if self.columns is not None: for col in self.columns: output[col] = LabelEncoder().fit_transform(output[col]) else: for colname,col in output.iteritems(): output[colname] = LabelEncoder().fit_transform(col) return output def fit_transform(self,X,y=None): return self.fit(X,y).transform(X)
假设我们要编码我们的两个分类属性( fruit
和color
),同时保留数字属性的weight
。 我们可以这样做,如下所示:
MultiColumnLabelEncoder(columns = ['fruit','color']).fit_transform(fruit_data)
其中转换我们的fruit_data
数据集
至
传递一个完全由分类variables组成的数据框并省略columns
参数将导致每一列都被编码(我相信这是你最初寻找的):
MultiColumnLabelEncoder().fit_transform(fruit_data.drop('weight',axis=1))
这转换
至
。
请注意,它可能会呛,当它试图编码已经是数字的属性(如果你喜欢,添加一些代码来处理这个)。
另一个很好的特点就是我们可以在stream水线中使用这个自定义转换器:
encoding_pipeline = Pipeline([ ('encoding',MultiColumnLabelEncoder(columns=['fruit','color'])) # add more pipeline steps as needed ]) encoding_pipeline.fit_transform(fruit_data)
我们不需要一个LabelEncoder。
您可以将列转换为分类,然后获取他们的代码。 我使用了下面的字典理解,将这个过程应用到每一列,并将结果换回到具有相同索引和列名称的相同形状的数据框中。
>>> pd.DataFrame({col: df[col].astype('category').cat.codes for col in df}, index=df.index) location owner pets 0 1 1 0 1 0 2 1 2 0 0 0 3 1 1 2 4 1 3 1 5 0 2 1
要创build映射字典,可以使用字典理解枚举类别:
>>> {col: {n: cat for n, cat in enumerate(df[col].astype('category').cat.categories)} for col in df} {'location': {0: 'New_York', 1: 'San_Diego'}, 'owner': {0: 'Brick', 1: 'Champ', 2: 'Ron', 3: 'Veronica'}, 'pets': {0: 'cat', 1: 'dog', 2: 'monkey'}}
这并不能直接回答你的问题(Naputipulu Jon和PriceHardman有很棒的回复)
但是,为了几个分类任务的目的,你可以使用
pandas.get_dummies(input_df)
这可以input具有分类数据的dataframe并返回具有二进制值的dataframe。 variables值被编码到结果数据框的列名中。 更多
事实上,这是一年半之后,但我也需要能够同时.transform()
多个pandas数据.transform()
列(也能够.inverse_transform()
)。 这扩展了以上@PriceHardman的出色build议:
class MultiColumnLabelEncoder(LabelEncoder): """ Wraps sklearn LabelEncoder functionality for use on multiple columns of a pandas dataframe. """ def __init__(self, columns=None): self.columns = columns def fit(self, dframe): """ Fit label encoder to pandas columns. Access individual column classes via indexig `self.all_classes_` Access individual column encoders via indexing `self.all_encoders_` """ # if columns are provided, iterate through and get `classes_` if self.columns is not None: # ndarray to hold LabelEncoder().classes_ for each # column; should match the shape of specified `columns` self.all_classes_ = np.ndarray(shape=self.columns.shape, dtype=object) self.all_encoders_ = np.ndarray(shape=self.columns.shape, dtype=object) for idx, column in enumerate(self.columns): # fit LabelEncoder to get `classes_` for the column le = LabelEncoder() le.fit(dframe.loc[:, column].values) # append the `classes_` to our ndarray container self.all_classes_[idx] = (column, np.array(le.classes_.tolist(), dtype=object)) # append this column's encoder self.all_encoders_[idx] = le else: # no columns specified; assume all are to be encoded self.columns = dframe.iloc[:, :].columns self.all_classes_ = np.ndarray(shape=self.columns.shape, dtype=object) for idx, column in enumerate(self.columns): le = LabelEncoder() le.fit(dframe.loc[:, column].values) self.all_classes_[idx] = (column, np.array(le.classes_.tolist(), dtype=object)) self.all_encoders_[idx] = le return self def fit_transform(self, dframe): """ Fit label encoder and return encoded labels. Access individual column classes via indexing `self.all_classes_` Access individual column encoders via indexing `self.all_encoders_` Access individual column encoded labels via indexing `self.all_labels_` """ # if columns are provided, iterate through and get `classes_` if self.columns is not None: # ndarray to hold LabelEncoder().classes_ for each # column; should match the shape of specified `columns` self.all_classes_ = np.ndarray(shape=self.columns.shape, dtype=object) self.all_encoders_ = np.ndarray(shape=self.columns.shape, dtype=object) self.all_labels_ = np.ndarray(shape=self.columns.shape, dtype=object) for idx, column in enumerate(self.columns): # instantiate LabelEncoder le = LabelEncoder() # fit and transform labels in the column dframe.loc[:, column] =\ le.fit_transform(dframe.loc[:, column].values) # append the `classes_` to our ndarray container self.all_classes_[idx] = (column, np.array(le.classes_.tolist(), dtype=object)) self.all_encoders_[idx] = le self.all_labels_[idx] = le else: # no columns specified; assume all are to be encoded self.columns = dframe.iloc[:, :].columns self.all_classes_ = np.ndarray(shape=self.columns.shape, dtype=object) for idx, column in enumerate(self.columns): le = LabelEncoder() dframe.loc[:, column] = le.fit_transform( dframe.loc[:, column].values) self.all_classes_[idx] = (column, np.array(le.classes_.tolist(), dtype=object)) self.all_encoders_[idx] = le return dframe def transform(self, dframe): """ Transform labels to normalized encoding. """ if self.columns is not None: for idx, column in enumerate(self.columns): dframe.loc[:, column] = self.all_encoders_[ idx].transform(dframe.loc[:, column].values) else: self.columns = dframe.iloc[:, :].columns for idx, column in enumerate(self.columns): dframe.loc[:, column] = self.all_encoders_[idx]\ .transform(dframe.loc[:, column].values) return dframe.loc[:, self.columns].values def inverse_transform(self, dframe): """ Transform labels back to original encoding. """ if self.columns is not None: for idx, column in enumerate(self.columns): dframe.loc[:, column] = self.all_encoders_[idx]\ .inverse_transform(dframe.loc[:, column].values) else: self.columns = dframe.iloc[:, :].columns for idx, column in enumerate(self.columns): dframe.loc[:, column] = self.all_encoders_[idx]\ .inverse_transform(dframe.loc[:, column].values) return dframe
例:
如果df
和df_copy()
是混合types的pandas
数据df_copy()
,则可以按照以下方式将MultiColumnLabelEncoder()
应用于dtype=object
列:
# get `object` columns df_object_columns = df.iloc[:, :].select_dtypes(include=['object']).columns df_copy_object_columns = df_copy.iloc[:, :].select_dtypes(include=['object'].columns # instantiate `MultiColumnLabelEncoder` mcle = MultiColumnLabelEncoder(columns=object_columns) # fit to `df` data mcle.fit(df) # transform the `df` data mcle.transform(df) # returns output like below array([[1, 0, 0, ..., 1, 1, 0], [0, 5, 1, ..., 1, 1, 2], [1, 1, 1, ..., 1, 1, 2], ..., [3, 5, 1, ..., 1, 1, 2], # transform `df_copy` data mcle.transform(df_copy) # returns output like below (assuming the respective columns # of `df_copy` contain the same unique values as that particular # column in `df` array([[1, 0, 0, ..., 1, 1, 0], [0, 5, 1, ..., 1, 1, 2], [1, 1, 1, ..., 1, 1, 2], ..., [3, 5, 1, ..., 1, 1, 2], # inverse `df` data mcle.inverse_transform(df) # outputs data like below array([['August', 'Friday', '2013', ..., 'N', 'N', 'CA'], ['April', 'Tuesday', '2014', ..., 'N', 'N', 'NJ'], ['August', 'Monday', '2014', ..., 'N', 'N', 'NJ'], ..., ['February', 'Tuesday', '2014', ..., 'N', 'N', 'NJ'], ['April', 'Tuesday', '2014', ..., 'N', 'N', 'NJ'], ['March', 'Tuesday', '2013', ..., 'N', 'N', 'NJ']], dtype=object) # inverse `df_copy` data mcle.inverse_transform(df_copy) # outputs data like below array([['August', 'Friday', '2013', ..., 'N', 'N', 'CA'], ['April', 'Tuesday', '2014', ..., 'N', 'N', 'NJ'], ['August', 'Monday', '2014', ..., 'N', 'N', 'NJ'], ..., ['February', 'Tuesday', '2014', ..., 'N', 'N', 'NJ'], ['April', 'Tuesday', '2014', ..., 'N', 'N', 'NJ'], ['March', 'Tuesday', '2013', ..., 'N', 'N', 'NJ']], dtype=object)
您可以通过索引访问用于适合每列的各个列类,列标签和列编码器:
mcle.all_classes_
mcle.all_encoders_
mcle.all_labels_
假设你只是试图获得一个sklearn.preprocessing.LabelEncoder()
对象,可以用来表示你的列,所有你需要做的是:
le.fit(df.columns)
在上面的代码中,您将拥有与每列对应的唯一编号。 更确切地说,你将有一个1:1的df.columns
到le.transform(df.columns.get_values())
映射。 要获得列的编码,只需将其传递给le.transform(...)
。 作为一个例子,下面将得到每列的编码:
le.transform(df.columns.get_values())
假设您想要为所有行标签创build一个sklearn.preprocessing.LabelEncoder()
对象,您可以执行以下操作:
le.fit([y for x in df.get_values() for y in x])
在这种情况下,你很可能有非唯一的行标签(如你的问题所示)。 要查看编码器创build的类,您可以执行le.classes_
。 你会注意到它应该和set(y for x in df.get_values() for y in x)
元素相同set(y for x in df.get_values() for y in x)
。 再次将行标签转换为编码标签使用le.transform(...)
。 例如,如果要检索df.columns
数组中第一列的标签和第一行,可以这样做:
le.transform([df.get_value(0, df.columns[0])])
你在评论中的问题有点复杂,但仍然可以完成:
le.fit([str(z) for z in set((x[0], y) for x in df.iteritems() for y in x[1])])
上面的代码执行以下操作:
- 使所有的对(列,行)的独特组合
- 将每个对表示为元组的string版本。 这是一个解决方法来克服不支持元组作为类名称的
LabelEncoder
类。 - 将新项目
LabelEncoder
到LabelEncoder
。
现在要使用这个新的模型有点复杂。 假设我们想要提取我们在前面的例子(df.columns中的第一列和第一行)中查find的相同项目的表示forms,我们可以这样做:
le.transform([str((df.columns[0], df.get_value(0, df.columns[0])))])
请记住,每个查找现在都是包含(列,行)的元组的string表示forms。
不, LabelEncoder
不会这样做。 它需要一维数组的类标签,并产生一维数组。 它被devise用来处理分类问题中的类标签,而不是任意的数据,任何将其强制用于其他用途的尝试都需要代码将实际问题转换为解决的问题(解决scheme返回原始空间)。
如果我们有单列来做标签编码和它的逆变换,那么当python有多个列的时候,它很容易做到这一点
def stringtocategory(dataset): ''' @author puja.sharma @see The function label encodes the object type columns and gives label encoded and inverse tranform of the label encoded data @param dataset dataframe on whoes column the label encoding has to be done @return label encoded and inverse tranform of the label encoded data. ''' data_original = dataset[:] data_tranformed = dataset[:] for y in dataset.columns: #check the dtype of the column object type contains strings or chars if (dataset[y].dtype == object): print("The string type features are : " + y) le = preprocessing.LabelEncoder() le.fit(dataset[y].unique()) #label encoded data data_tranformed[y] = le.transform(dataset[y]) #inverse label transform data data_original[y] = le.inverse_transform(data_tranformed[y]) return data_tranformed,data_original
继续对@PriceHardman的解决scheme提出的意见,我会提出以下版本的类:
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
该类适合训练集上的编码器,并在转换时使用合适的版本。 代码的初始版本可以在这里find。
使用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])
可以直接在pandas身上做到这一点,并且非常适合replace
方法的独特能力。
首先,我们制作一个词典,将字段和它们的值映射到新的replace值。
transform_dict = {} for col in df.columns: cats = pd.Categorical(df[col]).categories d = {} for i, cat in enumerate(cats): d[cat] = i transform_dict[col] = d transform_dict {'location': {'New_York': 0, 'San_Diego': 1}, 'owner': {'Brick': 0, 'Champ': 1, 'Ron': 2, 'Veronica': 3}, 'pets': {'cat': 0, 'dog': 1, 'monkey': 2}}
由于这将始终是一对一的映射,所以我们可以反转内部字典以获得新值映射回原始。
inverse_transform_dict = {} for col, d in transform_dict.items(): inverse_transform_dict[col] = {v:k for k, v in d.items()} inverse_transform_dict {'location': {0: 'New_York', 1: 'San_Diego'}, 'owner': {0: 'Brick', 1: 'Champ', 2: 'Ron', 3: 'Veronica'}, 'pets': {0: 'cat', 1: 'dog', 2: 'monkey'}}
现在,我们可以使用replace
方法的独特能力来获取字典的嵌套列表,并使用外部键作为列,并使用内部键作为我们想要replace的值。
df.replace(transform_dict) location owner pets 0 1 1 0 1 0 2 1 2 0 0 0 3 1 1 2 4 1 3 1 5 0 2 1
我们可以通过再次链接replace
方法轻松地回到原来的状态
df.replace(transform_dict).replace(inverse_transform_dict) location owner pets 0 San_Diego Champ cat 1 New_York Ron dog 2 New_York Brick cat 3 San_Diego Champ monkey 4 San_Diego Veronica dog 5 New_York Ron dog