我试图使用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)
回溯(最近一次调用):
文件“”,第1行,在
文件"/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/预处理/label.py",第103行
y = column_or_1d(y, warn=True)
文件"/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/utils/validation.py",第306行,在column_or_1d中
raise ValueError("错误的输入形状{0}".format(形状))
ValueError:错误的输入形状(6,3)
对于如何解决这个问题有什么想法吗?
我们不需要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
要创建映射字典,你可以使用字典理解式枚举类别:
>>> {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'}}
正如larsmans提到的,LabelEncoder()只接受1维数组作为参数。也就是说,可以很容易地滚动自己的标签编码器,对您选择的多个列进行操作,并返回转换后的数据框架。我在这里的代码部分基于Zac Stewart的优秀博客文章。
创建自定义编码器只需要创建一个响应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)进行编码,而不使用数字属性权重。我们可以这样做:
MultiColumnLabelEncoder(columns = ['fruit','color']).fit_transform(fruit_data)
它转换了我们的fruit_data数据集
to
传递给它一个完全由分类变量组成的数据框架,省略columns参数将导致每个列都被编码(我相信这是你最初寻找的):
MultiColumnLabelEncoder().fit_transform(fruit_data.drop('weight',axis=1))
这个转换
to
.
请注意,当它试图编码已经是数值的属性时可能会阻塞(如果您愿意,可以添加一些代码来处理这个问题)。
另一个很好的特性是我们可以在管道中使用这个自定义转换器:
encoding_pipeline = Pipeline([
('encoding',MultiColumnLabelEncoder(columns=['fruit','color']))
# add more pipeline steps as needed
])
encoding_pipeline.fit_transform(fruit_data)
我查看了LabelEncoder的源代码(https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/label.py)。它基于一组numpy变换,其中一个是np.unique()。这个函数只接受一维数组输入。(如果我说错了请指正)。
非常粗略的想法……
首先,确定哪些列需要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将是编码后的df, label_list将显示所有这些值在相应列中的含义。
这是我为工作编写的数据处理脚本的一个片段。如果你觉得还有什么改进的地方,请告诉我。
编辑:
这里只想提一下,上述方法在处理数据帧时不会遗漏最佳数据。不确定它是如何工作的数据帧包含丢失的数据。(在执行上述方法之前,我已经处理了缺失过程)
我们可以使用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
假设你只是想获得一个sklearn.预处理. labelencoder()对象,可以用来表示你的列,你所要做的就是:
le.fit(df.columns)
在上面的代码中,每一列都有一个唯一的数字。
更精确地说,你将得到df的1:1映射。列到le.transform(df.columns.get_values())。要获得列的编码,只需将其传递给le.transform(…)。作为一个例子,下面将得到每一列的编码:
le.transform(df.columns.get_values())
假设你想为你所有的行标签创建一个sklearn.预处理. labelencoder()对象,你可以这样做:
le.fit([y for x in df.get_values() for y in x])
在本例中,您很可能拥有非唯一的行标签(如您的问题所示)。要查看编码器创建了哪些类,可以执行le.classes_。你会注意到,这应该具有与set中相同的元素(y for x in df.get_values() for y in x)。再次使用le.transform(…)将行标签转换为编码标签。例如,如果您想检索df. xml文件中第一列的标签。列数组和第一行,你可以这样做:
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])])
上面的代码实现了以下功能:
使所有(列,行)对的唯一组合
将每个对表示为元组的字符串版本。这是克服LabelEncoder类不支持元组作为类名的一种变通方法。
将新项目贴合到LabelEncoder。
现在要使用这个新模型就有点复杂了。假设我们想要提取在前一个例子中查找的同一项的表示(df中的第一列)。列和第一行),我们可以这样做:
le.transform([str((df.columns[0], df.get_value(0, df.columns[0])))])
记住,现在每个查找都是一个元组的字符串表示
包含(列、行)。
这是有可能做到这一切直接在熊猫,是非常适合的独特能力的替代方法。
首先,让我们创建一个字典的字典,将列及其值映射到新的替换值。
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方法的独特功能来获取一个嵌套的字典列表,并使用外部键作为列,使用内部键作为我们想要替换的值。
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