Etiqueta de codificación en varias columnas en scikit-learn

216

Estoy tratando de usar scikit-learn LabelEncoderpara codificar pandas DataFramede etiquetas de cadena. Como el marco de datos tiene muchas (50+) columnas, quiero evitar crear un LabelEncoderobjeto para cada columna; Prefiero tener un solo LabelEncoderobjeto grande que funcione en todas mis columnas de datos.

Tirar todo el DataFrameen LabelEncodercrea el siguiente error. Tenga en cuenta que estoy usando datos ficticios aquí; en realidad estoy tratando con unas 50 columnas de datos etiquetados con cadenas, así que necesito una solución que no haga referencia a ninguna columna por su 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)

Rastreo (última llamada más reciente): Archivo "", línea 1, en Archivo "/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/preprocessing/label.py", línea 103, en forma y = column_or_1d (y, warn = True) Archivo "/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/utils/validation.py", línea 306, en column_or_1d elevar ValueError ("forma de entrada incorrecta { 0} ". Formato (forma)) ValueError: forma de entrada incorrecta (6, 3)

¿Alguna idea sobre cómo solucionar este problema?

Bryan
fuente
¿Por qué estás tratando de hacer esto?
Fred Foo
Para simplificar la codificación de una columna múltiple dataframede datos de cadena. Estoy haciendo clic en los objetos de codificación, por lo que quiero evitar tener que enredar / desenredar 50 objetos separados. Además, me pregunto si hay una manera de hacer que el codificador simplifique los datos, es decir, simplemente devolviendo una fila con un identificador para cada combinación única de variables en cada columna.
Bryan
Hay una manera simple de hacer todo esto en pandas pasando un diccionario de diccionarios al replacemétodo. Vea esta respuesta a continuación
Ted Petrou el

Respuestas:

451

Sin embargo, puedes hacer esto fácilmente,

df.apply(LabelEncoder().fit_transform)

EDIT2:

En scikit-learn 0.20, la forma recomendada es

OneHotEncoder().fit_transform(df)

ya que OneHotEncoder ahora admite la entrada de cadenas. Es posible aplicar OneHotEncoder solo a ciertas columnas con ColumnTransformer.

EDITAR:

Dado que esta respuesta fue hace más de un año, y generó muchos votos a favor (incluida una recompensa), probablemente debería extender esto más.

Para inverse_transform y transform, tienes que hacer un poco de pirateo.

from collections import defaultdict
d = defaultdict(LabelEncoder)

Con esto, ahora retiene todas las columnas LabelEncodercomo 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))
Napitupulu Jon
fuente
1
Esto es asombroso, pero en este caso, ¿cómo podemos aplicar la transformación inversa?
Supreeth Meka
10
Pero si quiero usar esta solución en una tubería, por ejemplo, ajuste y transformación por separado (ajuste en el tren, y luego usar en el conjunto de pruebas -> reutilizar el diccionario aprendido) ¿es compatible con esto df.apply(LabelEncoder().fit_transform)?
Georg Heiler
2
¿Cómo se puede hacer que esto funcione LabelBinarizery reutilice el diccionario para un conjunto de prueba? Probé d = defaultdict(LabelBinarizer)y después fit = df.apply(lambda x: d[x.name].fit_transform(x)), pero se produce una excepción: Exception: Data must be 1-dimensional. No estoy seguro de cómo espero que se vea el DataFrame resultante ... tal vez cada columna debería contener los vectores binarizados.
Qululu
44
Buena solución ¿Cómo transformar solo en cierta columna?
stenlytw
1
si quiero invertir el juste de codificación para una columna, ¿cómo lo hago?
Ib D
95

Como lo mencionó larsmans, LabelEncoder () solo toma una matriz 1-d como argumento . Dicho esto, es bastante fácil rodar su propio codificador de etiquetas que opera en múltiples 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 aquí .

La creación de un codificador de encargo consiste en la simple creación de una clase que responde a la fit(), transform()y fit_transform()métodos. 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 ( fruity color), dejando weightsolo el atributo numérico . Podríamos hacer esto de la siguiente manera:

MultiColumnLabelEncoder(columns = ['fruit','color']).fit_transform(fruit_data)

Que transforma nuestro fruit_dataconjunto de datos de

ingrese la descripción de la imagen aquí a

ingrese la descripción de la imagen aquí

Si le pasa un marco de datos que consiste completamente en variables categóricas y omite el columnsparámetro, se codificará cada columna (que creo que es lo que estaba buscando originalmente):

MultiColumnLabelEncoder().fit_transform(fruit_data.drop('weight',axis=1))

Esto transforma

ingrese la descripción de la imagen aquí a

ingrese la descripción de la imagen aquí.

Tenga en cuenta que probablemente se ahogará cuando intente codificar atributos que ya son numéricos (agregue algún código para manejar esto si lo desea).

Otra característica interesante 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)
PriceHardman
fuente
2
Acabo de darme cuenta de que los datos implican que una naranja es de color verde. Ups ;)
PriceHardman
55
esta es una buena manera de transformar datos una vez, pero ¿qué pasa si quiero reutilizar esta transformación en un conjunto de validación? tendrías que volver a ajustar la transformación y podrían surgir problemas como que mi nuevo conjunto de datos no tenga todas las categorías para todas las variables. por ejemplo, digamos que el color verde no aparece en mi nuevo conjunto de datos. Esto estropeará la codificación.
Ben
3
De acuerdo con @Ben. Esto en realidad no imita sklearn más allá de los nombres de los métodos. Si intentaras poner esto en una tubería, no funcionaría
Tgsmith61591
3
Para asegurarse de que la codificación de la etiqueta sea consistente tanto en el tren como en los conjuntos de prueba, querrá realizar la codificación en todo su conjunto de datos (tren + prueba). Esto puede hacerse antes de dividirlos en el tren y la prueba, o puede combinarlos, realizar la codificación y volver a dividirlos.
PriceHardman
2
¿Qué tal ir en reversa? decodificando de nuevo al original?
user702846
18

Desde scikit-learn 0.20 puedes usar sklearn.compose.ColumnTransformery sklearn.preprocessing.OneHotEncoder:

Si solo tiene variables categóricas, OneHotEncoderdirectamente:

from sklearn.preprocessing import OneHotEncoder

OneHotEncoder(handle_unknown='ignore').fit_transform(df)

Si tiene características de tipo heterogéneo:

from sklearn.compose import make_column_transformer
from sklearn.preprocessing import RobustScaler
from sklearn.preprocessing import OneHotEncoder

categorical_columns = ['pets', 'owner', 'location']
numerical_columns = ['age', 'weigth', 'height']
column_trans = make_column_transformer(
    (categorical_columns, OneHotEncoder(handle_unknown='ignore'),
    (numerical_columns, RobustScaler())
column_trans.fit_transform(df)

Más opciones en la documentación: http://scikit-learn.org/stable/modules/compose.html#columntransformer-for-heterogeneous-data

ogrisel
fuente
inverse_transform()Sin embargo, no es compatible con ColumnTransformer. Al menos, no por el momento: github.com/scikit-learn/scikit-learn/issues/11463 . Esa es una gran desventaja para mi aplicación, y probablemente también lo será para otros.
Sander Vanden Hautte
16

No necesitamos un LabelEncoder.

Puede convertir las columnas a categorías y luego obtener sus códigos. Utilicé un diccionario de comprensión 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 y nombres de columna idénticos.

>>> 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, solo puede enumerar las categorías usando una comprensión del 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'}}
Alejandro
fuente
Si quiero retroceder (invertir) una columna (ejemplo, variable de destino: Y), ¿cómo lo hago?
Ib D
9

esto no responde directamente a su pregunta (para lo cual Naputipulu Jon y PriceHardman tienen respuestas fantásticas)

Sin embargo, para algunas tareas de clasificación, etc., podría usar

pandas.get_dummies(input_df) 

esto puede ingresar un marco de datos con datos categóricos y devolver un marco de datos con valores binarios. Los valores variables se codifican en nombres de columna en el marco de datos resultante. más

Anurag Priyadarshi
fuente
6

Suponiendo que simplemente está tratando de obtener un sklearn.preprocessing.LabelEncoder()objeto que pueda usarse 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á un mapeo 1: 1 de df.columnsto le.transform(df.columns.get_values()). Para obtener la codificación de una columna, simplemente pásala le.transform(...). Como ejemplo, lo siguiente obtendrá la codificación para cada columna:

le.transform(df.columns.get_values())

Suponiendo que desea crear un sklearn.preprocessing.LabelEncoder()objeto 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 únicas (como se muestra en su pregunta). Para ver qué clases creó el codificador puede hacer le.classes_. Notarás 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 le.transform(...). Como ejemplo, si desea recuperar la etiqueta para la primera columna de la df.columnsmatriz 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:

  1. Haga una combinación única de todos los pares de (columna, fila)
  2. Representa cada par como una versión de cadena de la tupla. Esta es una solución alternativa para superar la LabelEncoderclase que no admite tuplas como nombre de clase.
  3. Ajusta los nuevos elementos a la LabelEncoder.

Ahora usar este nuevo modelo es un poco más complicado. Suponiendo que deseamos extraer la representación del mismo elemento que buscamos en el ejemplo anterior (la primera columna en columnas df 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 es ahora una representación de cadena de una tupla que contiene la (columna, fila).

TehTechGuy
fuente
5

No, LabelEncoderno hace esto. Toma matrices 1-d de etiquetas de clase y produce matrices 1-d. Está diseñado para manejar etiquetas de clase en problemas de clasificación, no datos arbitrarios, y cualquier intento de forzarlo a otros usos requerirá código para transformar el problema real en el problema que resuelve (y la solución de vuelta al espacio original).

Fred Foo
fuente
Ok, dado esto, ¿cuál es su sugerencia sobre la mejor manera en que puedo codificar etiquetas de cadena por un todo DataFramea la vez?
Bryan
@Bryan Mira el LabelEncodercódigo y adáptalo . Yo no uso Pandas, así que no sé lo difícil que será.
Fred Foo
También dejaré que otras pandaspersonas se ocupen de esta pregunta: estoy seguro de que no soy la única persona con este desafío, así que espero que haya una solución preconstruida.
Bryan
5

Esto es un año y medio después del hecho, pero yo también necesitaba poder crear .transform()múltiples columnas de marcos de datos de pandas a la vez (y también poder hacerlo .inverse_transform()). Esto amplía la excelente sugerencia de @PriceHardman arriba:

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.loc[:, self.columns].values

    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.loc[:, self.columns].values

Ejemplo:

Si dfy df_copy()son pandasmarcos de datos de tipo mixto, puede aplicarlos MultiColumnLabelEncoder()a las dtype=objectcolumnas 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 columna individuales, etiquetas de columna y codificadores de columna utilizados para ajustar cada columna mediante indexación:

mcle.all_classes_
mcle.all_encoders_
mcle.all_labels_

Jason Wolosonovich
fuente
Hola Jason, mcle.all_labels_ no parece funcionar (Python 3.5, Conda 4.3.29, Sklearn 0.18.1, Pandas 0.20.1. Obtengo: AttributeError: el objeto 'MultiColumnLabelEncoder' no tiene atributo 'all_labels_'
Jason
@ Jason Hola, lo siento, no vi esto hasta hoy: / pero si tuviera que adivinar, diría que acabas de usar el fitmétodo de arriba que en realidad no producirá ninguna etiqueta hasta que lo apliques ( transform/ fit_transform) a los datos.
Jason Wolosonovich
Creo que necesitas poner un mejor ejemplo: no pude volver a ejecutar todos tus códigos.
usuario702846
2

Siguiendo los comentarios planteados 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 utiliza la versión ajustada cuando se transforma. La versión inicial del código se puede encontrar aquí .

Dror
fuente
2

Un corto camino a LabelEncoder()múltiples columnas 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 esto le_dictpara etiquetar Codificar cualquier otra columna:

le_dict[col].transform(df_another[col])
Tom
fuente
2

Es posible hacer todo esto en pandas directamente y es adecuado para una habilidad única del replacemétodo.

Primero, hagamos un diccionario de diccionarios que asignen 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 esta siempre será una asignación uno a uno, podemos invertir el diccionario interno para obtener una asignación de los nuevos valores 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 replacemétodo para tomar una lista anidada de diccionarios y usar las teclas externas como columnas y las teclas 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 encadenando nuevamente el replacemétodo

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
Ted Petrou
fuente
2

Después de mucha búsqueda y experimentación con algunas respuestas aquí y en otros lugares, creo que su respuesta está aquí :

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

Esto preservará los nombres de categoría en las columnas:

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
Christopher
fuente
2

Verifiqué el código fuente ( https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/label.py ) de LabelEncoder. Se basó en un conjunto de transformaciones numpy, una de las cuales es np.unique (). Y esta función solo toma entrada de matriz 1-d. (corrígeme si estoy equivocado).

Ideas muy aproximadas ... primero, identifique qué columnas necesitaban LabelEncoder, luego recorra cada columna.

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

El df devuelto sería el que aparece después de la codificación, y label_list le mostrará lo que significan todos esos valores en la columna correspondiente. Este es un fragmento de un script de proceso de datos que escribí para trabajar. Avíseme si cree que podría haber alguna mejora adicional.

EDITAR: Solo quiero mencionar aquí que los métodos anteriores funcionan con el marco de datos sin perder lo mejor. No estoy seguro de cómo funciona para que el marco de datos contenga datos faltantes. (Tuve un trato con el procedimiento faltante antes de ejecutar los métodos anteriores)

willaccc
fuente
1

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 varias 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
Puja Sharma
fuente
1

Si tiene ambos tipos de datos numéricos y categóricos en el marco de datos Puede usar: aquí X es mi marco de datos que tiene ambas 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 nuevamente.

Vikas Gupta
fuente
1

Usando Neuraxle

TLDR; Aquí puede utilizar el FlattenForEach contenedor de clase simplemente transformar su df como: FlattenForEach(LabelEncoder(), then_unflatten=True).fit_transform(df).

Con este método, su codificador de etiquetas podrá ajustarse y transformarse dentro de una tubería de aprendizaje de scikit normal . Simplemente importemos:

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

El mismo codificador compartido para columnas:

Así es como se aplicará un LabelEncoder compartido a todos los datos para codificarlo:

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

Resultado:

    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)

Diferentes codificadores por columna:

Y así es como se aplicará un primer LabelEncoder independiente a las mascotas, y se compartirá un segundo para el propietario y la ubicación de las columnas. Entonces, para ser precisos, aquí tenemos una combinación de codificadores de etiquetas diferentes y compartidos:

    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)

Resultado:

    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)
Guillaume Chevalier
fuente
0

Utilicé principalmente la respuesta de @Alexander pero tuve que hacer algunos cambios:

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])

Luego, para reutilizar en el futuro, puede guardar la salida en un documento json y cuando lo necesite, leerlo y usar la .map()función como lo hice anteriormente.

bbennett36
fuente
0

El problema es la forma de los datos (pd dataframe) que está pasando a la función de ajuste. Tienes que pasar la 1ª lista.

Ali Sadr
fuente
0
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)

Aquí estoy leyendo un csv desde la ubicación y, en función, paso la lista de columnas que quiero etiquetar y el marco de datos que quiero aplicar.


fuente
0

¿Qué tal esto?

def MultiColumnLabelEncode(choice, columns, X):
    LabelEncoders = []
    if choice == 'encode':
        for i in enumerate(columns):
            LabelEncoders.append(LabelEncoder())
        i=0    
        for cols in columns:
            X[:, cols] = LabelEncoders[i].fit_transform(X[:, cols])
            i += 1
    elif choice == 'decode': 
        for cols in columns:
            X[:, cols] = LabelEncoders[i].inverse_transform(X[:, cols])
            i += 1
    else:
        print('Please select correct parameter "choice". Available parameters: encode/decode')

No es el más eficiente, sin embargo funciona y es súper simple.

Dominik Novotný
fuente