python - sklearn - Etiquetar la codificación en múltiples columnas en scikit-learn
onehotencoder onehotencoder categorical_features 1]) (12)
Intento usar LabelEncoder de scikit-learn para codificar un DataFrame
de DataFrame
de pandas de etiquetas de cadena. Como el marco de datos tiene muchas (más de) columnas, quiero evitar crear un objeto LabelEncoder
para cada columna; Prefiero tener solo un gran objeto LabelEncoder
que funcione en todas mis columnas de datos.
Al DataFrame
todo el DataFrame
en LabelEncoder
crea el siguiente error. Tenga en cuenta que estoy usando datos ficticios aquí; en realidad, estoy tratando con alrededor de 50 columnas de datos etiquetados con cadenas, por lo que necesito una solución que no haga referencia a ninguna columna por nombre.
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)
¿Alguna idea sobre cómo solucionar este problema?
Como lo menciona larsmans, LabelEncoder () solo toma una matriz de 1-d como argumento . Dicho esto, es bastante fácil rodar su propio codificador de etiquetas que opera en varias columnas de su elección, y devuelve un marco de datos transformado. Mi código aquí se basa en parte en la excelente publicación de blog de Zac Stewart que se encuentra here .
Crear un codificador personalizado implica simplemente crear una clase que responda a los métodos fit()
, transform()
y fit_transform()
. En su caso, un buen comienzo podría ser algo como esto:
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)
Supongamos que queremos codificar nuestros dos atributos categóricos ( fruit
y color
), dejando solo el weight
atributo numérico. Podríamos hacer esto de la siguiente manera:
MultiColumnLabelEncoder(columns = [''fruit'',''color'']).fit_transform(fruit_data)
Que transforma nuestro dataset fruit_data
de
a
Pasarle un marco de datos que consista completamente en variables categóricas y omitir el parámetro de columns
dará como resultado que cada columna esté codificada (que creo que es lo que originalmente estabas buscando):
MultiColumnLabelEncoder().fit_transform(fruit_data.drop(''weight'',axis=1))
Esto transforma
a
.
Tenga en cuenta que probablemente se ahogará cuando intente codificar atributos que ya son numéricos (agregue un código para manejar esto si lo desea).
Otra buena característica de esto es que podemos usar este transformador personalizado en una tubería:
encoding_pipeline = Pipeline([
(''encoding'',MultiColumnLabelEncoder(columns=[''fruit'',''color'']))
# add more pipeline steps as needed
])
encoding_pipeline.fit_transform(fruit_data)
Es posible hacer esto todo en pandas directamente y es adecuado para una habilidad única del método de replace
.
Primero, hagamos un diccionario de diccionarios mapeando las columnas y sus valores a sus nuevos valores de reemplazo.
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}}
Como esto siempre será un mapeo de uno a uno, podemos invertir el diccionario interno para obtener un mapeo de los nuevos valores de vuelta al original.
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''}}
Ahora, podemos usar la capacidad única del método de replace
para tomar una lista anidada de diccionarios y usar las claves externas como columnas, y las claves internas como los valores que nos gustaría reemplazar.
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
Podemos volver fácilmente al original volviendo a encadenar el método de 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
Esto es un año y medio después del hecho, pero yo también necesitaba poder .transform()
múltiples columnas de cuadros de datos de pandas a la vez (y también poder .inverse_transform()
). Esto amplía la excelente sugerencia de @PriceHardman anterior:
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
Ejemplo:
Si df
y df_copy()
son dataframes de pandas
tipo mixto, puede aplicar MultiColumnLabelEncoder()
a las columnas dtype=object
de la siguiente manera:
# 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)
Puede acceder a clases de columnas individuales, etiquetas de columnas y codificadores de columnas utilizados para ajustarse a cada columna a través de la indexación:
mcle.all_classes_
mcle.all_encoders_
mcle.all_labels_
No necesitamos un LabelEncoder.
Puede convertir las columnas en categorías y luego obtener sus códigos. Usé una comprensión de diccionario a continuación para aplicar este proceso a cada columna y envolver el resultado en un marco de datos de la misma forma con índices idénticos y nombres de columna.
>>> 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
Para crear un diccionario de mapeo, puede enumerar las categorías usando una comprensión de diccionario:
>>> {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''}}
No, LabelEncoder
no hace esto. Toma arrays de 1-d de etiquetas de clase y produce matrices de 1-d. Está diseñado para manejar etiquetas de clase en problemas de clasificación, no datos arbitrarios, y cualquier intento de forzarlo para otros usos requerirá que el código transforme el problema real al problema que resuelve (y la solución vuelva al espacio original).
Si tiene datos numéricos y categóricos de ambos tipos de datos en el marco de datos Puede usar: aquí X es mi marco de datos con las dos variables categóricas y numéricas
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]])
Nota: Esta técnica es buena si no está interesado en convertirlos de nuevo.
Siguiendo los comentarios sobre la solución de @PriceHardman , propondría la siguiente versión de la clase:
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
Esta clase se ajusta al codificador en el conjunto de entrenamiento y usa la versión ajustada cuando se transforma. La versión inicial del código se puede encontrar here .
Sin embargo, puedes hacer esto fácilmente,
df.apply(LabelEncoder().fit_transform)
EDITAR:
Dado que esta respuesta es hace más de un año, y generó muchos votos ascendentes (incluida una recompensa), probablemente debería extender esto más.
Para inverse_transform y transform, tienes que hacer un poco de hack.
from collections import defaultdict
d = defaultdict(LabelEncoder)
Con esto, ahora conserva todas las columnas LabelEncoder
como diccionario.
# 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))
Suponiendo que simplemente intenta obtener un objeto sklearn.preprocessing.LabelEncoder()
que se puede usar para representar sus columnas, todo lo que tiene que hacer es:
le.fit(df.columns)
En el código anterior, tendrá un número único correspondiente a cada columna. Más precisamente, tendrá una asignación de 1: 1 de df.columns
a le.transform(df.columns.get_values())
. Para obtener la codificación de una columna, simplemente le.transform(...)
a le.transform(...)
. Como ejemplo, lo siguiente obtendrá la codificación para cada columna:
le.transform(df.columns.get_values())
Suponiendo que desea crear un objeto sklearn.preprocessing.LabelEncoder()
para todas sus etiquetas de fila, puede hacer lo siguiente:
le.fit([y for x in df.get_values() for y in x])
En este caso, lo más probable es que tenga etiquetas de fila no exclusivas (como se muestra en su pregunta). Para ver qué clases creó el codificador, puede hacer le.classes_
. set(y for x in df.get_values() for y in x)
que esto debería tener los mismos elementos que en set(y for x in df.get_values() for y in x)
. Una vez más, para convertir una etiqueta de fila en una etiqueta codificada, use le.transform(...)
. Como ejemplo, si desea recuperar la etiqueta de la primera columna en la matriz df.columns
y la primera fila, puede hacer esto:
le.transform([df.get_value(0, df.columns[0])])
La pregunta que tenía en su comentario es un poco más complicada, pero aún se puede lograr:
le.fit([str(z) for z in set((x[0], y) for x in df.iteritems() for y in x[1])])
El código anterior hace lo siguiente:
- Haz una combinación única de todos los pares de (columna, fila)
- Represente cada par como una versión de cadena de la tupla. Esta es una solución para superar la clase
LabelEncoder
no admite tuplas como nombre de clase. - Se adapta a los nuevos elementos en
LabelEncoder
.
Ahora usar este nuevo modelo es un poco más complicado. Suponiendo que queremos extraer la representación para el mismo artículo que buscamos en el ejemplo anterior (la primera columna en df.columns y la primera fila), podemos hacer esto:
le.transform([str((df.columns[0], df.get_value(0, df.columns[0])))])
Recuerde que cada búsqueda ahora es una representación de cadena de una tupla que contiene la (columna, fila).
Una breve forma de LabelEncoder()
columnas múltiples con un dict()
:
from sklearn.preprocessing import LabelEncoder
le_dict = {col: LabelEncoder() for col in columns }
for col in columns:
le_dict[col].fit_transform(df[col])
y puede usar este le_dict
para etiquetar cualquier otra columna enCódigo
le_dict[col].transform(df_another[col])
esto no responde directamente a su pregunta (para lo cual Naputipulu Jon y PriceHardman tienen respuestas fantásticas)
Sin embargo, con el propósito de algunas tareas de clasificación, etc. podrías usar
pandas.get_dummies(input_df)
esto puede ingresar dataframe con datos categóricos y devolver un dataframe con valores binarios. los valores variables se codifican en nombres de columna en el marco de datos resultante. more
si tenemos una sola columna para hacer la codificación de la etiqueta y su transformación inversa es fácil cómo hacerlo cuando hay múltiples columnas en 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