Étiqueter l'encodage sur plusieurs colonnes dans scikit-learn

216

J'essaie d'utiliser scikit-learn LabelEncoderpour encoder un pandas DataFramed'étiquettes de chaîne. Comme la trame de données contient de nombreuses (50+) colonnes, je veux éviter de créer un LabelEncoderobjet pour chaque colonne; Je préfère simplement avoir un gros LabelEncoderobjet qui fonctionne dans toutes mes colonnes de données.

Lancer le tout DataFramedans LabelEncodercrée l'erreur ci-dessous. Veuillez garder à l'esprit que j'utilise ici des données factices; en réalité, je traite environ 50 colonnes de données étiquetées sous forme de chaîne, donc j'ai besoin d'une solution qui ne référence aucune colonne par son nom.

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 (dernier appel le plus récent): Fichier "", ligne 1, dans le fichier "/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/preprocessing/label.py", ligne 103, en forme y = column_or_1d (y, warn = True) Fichier "/Users/bbalin/anaconda/lib/python2.7/site-packages/sklearn/utils/validation.py", ligne 306, dans column_or_1d, augmenter ValueError ("mauvaise forme d'entrée { 0} ". Format (forme)) ValueError: mauvaise forme d'entrée (6, 3)

Avez-vous des idées sur la façon de contourner ce problème?

Bryan
la source
Pourquoi essayez-vous de faire ça?
Fred Foo
Pour simplifier le codage d'une multi-colonne dataframede données de chaîne. Je piclque le (s) objet (s) d'encodage, donc je veux éviter d'avoir à décaper / décaper 50 objets séparés. De plus, je me demande s'il existe un moyen de faire en sorte que l'encodeur simplifie les données, c'est-à-dire en renvoyant simplement une ligne avec un identifiant pour chaque combinaison unique de variables dans chaque colonne.
Bryan
Il existe un moyen simple de tout faire dans les pandas en passant un dictionnaire de dictionnaires à la replaceméthode. Voir cette réponse ci
Ted Petrou

Réponses:

451

Mais vous pouvez facilement le faire,

df.apply(LabelEncoder().fit_transform)

EDIT2:

Dans scikit-learn 0.20, la méthode recommandée est

OneHotEncoder().fit_transform(df)

car OneHotEncoder prend désormais en charge la saisie de chaîne. L'application de OneHotEncoder uniquement à certaines colonnes est possible avec ColumnTransformer.

ÉDITER:

Étant donné que cette réponse remonte à plus d'un an et qu'elle a généré de nombreux votes positifs (y compris une prime), je devrais probablement prolonger cela.

Pour inverse_transform et transform, vous devez faire un peu de hack.

from collections import defaultdict
d = defaultdict(LabelEncoder)

Avec cela, vous conservez maintenant toutes les colonnes en LabelEncodertant que dictionnaire.

# 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
la source
1
C'est incroyable, mais dans ce cas, comment pouvons-nous appliquer la transformation inverse?
Supreeth Meka
10
Mais si je veux utiliser cette solution dans un pipeline, par exemple un ajustement et une transformation séparés (ajustement sur le train, puis utilisation sur un ensemble de tests -> réutiliser le dictionnaire appris) est-ce pris en charge avec df.apply(LabelEncoder().fit_transform)?
Georg Heiler
2
Comment peut-on utiliser cela à la LabelBinarizerplace et réutiliser le dictionnaire pour un ensemble de tests? J'ai essayé d = defaultdict(LabelBinarizer)puis , fit = df.apply(lambda x: d[x.name].fit_transform(x))mais une exception est soulevée: Exception: Data must be 1-dimensional. Je ne sais pas à quoi je pense que le DataFrame résultant ressemblera ... peut-être que chaque colonne devrait contenir les vecteurs binarisés.
Qululu
4
Belle solution. Comment transformer dans certaines colonnes uniquement?
stenlytw
1
si je veux inverser le codage juste pour une colonne, comment faire?
Ib D
95

Comme mentionné par larsmans, LabelEncoder () prend uniquement un tableau 1-d comme argument . Cela dit, il est assez facile de faire rouler votre propre encodeur d'étiquette qui fonctionne sur plusieurs colonnes de votre choix et renvoie une trame de données transformée. Mon code ici est basé en partie sur l'excellent article de blog de Zac Stewart trouvé ici .

Création d' un encodeur personnalisé consiste à créer simplement une classe qui répond aux fit(), transform()et des fit_transform()méthodes. Dans votre cas, un bon début pourrait être quelque chose comme ceci:

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)

Supposons que nous voulons encoder nos deux attributs catégoriels ( fruitet color), tout en laissant l'attribut numérique weightseul. Nous pourrions le faire comme suit:

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

Qui transforme notre fruit_dataensemble de données de

entrez la description de l'image ici à

entrez la description de l'image ici

En lui passant une trame de données composée entièrement de variables catégorielles et en omettant le columnsparamètre, chaque colonne sera codée (ce qui, je crois, est ce que vous cherchiez à l'origine):

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

Cela transforme

entrez la description de l'image ici à

entrez la description de l'image ici.

Notez qu'il s'étouffe probablement quand il essaie d'encoder des attributs qui sont déjà numériques (ajoutez du code pour gérer cela si vous le souhaitez).

Une autre fonctionnalité intéressante à ce sujet est que nous pouvons utiliser ce transformateur personnalisé dans un pipeline:

encoding_pipeline = Pipeline([
    ('encoding',MultiColumnLabelEncoder(columns=['fruit','color']))
    # add more pipeline steps as needed
])
encoding_pipeline.fit_transform(fruit_data)
PriceHardman
la source
2
Je viens de réaliser que les données impliquent qu'un orange est de couleur verte. Oups. ;)
PriceHardman
5
c'est un bon moyen de transformer les données une fois, mais que faire si je veux réutiliser cette transformation sur un ensemble de validation. vous devrez adapter fit_transform à nouveau et des problèmes pourraient survenir, comme mon nouvel ensemble de données n'ayant pas toutes les catégories pour toutes les variables. Par exemple, disons que la couleur verte n'apparaît pas dans mon nouvel ensemble de données. cela gâchera l'encodage.
Ben
3
D'accord avec @Ben. Cela n'imite pas du tout sklearn au-delà des noms de méthode. Si vous essayez de mettre cela dans un Pipeline, cela ne fonctionnera pas
Tgsmith61591
3
Pour vous assurer que l'encodage des étiquettes est cohérent à la fois dans le train et dans les ensembles de tests, vous souhaiterez effectuer l'encodage sur l'ensemble de vos données (train + test). Cela peut être fait avant de les diviser en train et test, ou vous pouvez les combiner, effectuer l'encodage et les diviser à nouveau.
PriceHardman
2
Que diriez-vous de faire marche arrière? décodage de retour à l'original?
user702846
18

Depuis scikit-learn 0.20, vous pouvez utiliser sklearn.compose.ColumnTransformeret sklearn.preprocessing.OneHotEncoder:

Si vous ne disposez que de variables catégorielles, OneHotEncoderdirectement:

from sklearn.preprocessing import OneHotEncoder

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

Si vous avez des fonctionnalités typées de manière hétérogène:

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)

Plus d'options dans la documentation: http://scikit-learn.org/stable/modules/compose.html#columntransformer-for-heterogeneous-data

ogrisel
la source
inverse_transform()n'est cependant pas pris en charge sur ColumnTransformer. Du moins, pas pour le moment: github.com/scikit-learn/scikit-learn/issues/11463 . C'est un gros inconvénient pour ma candidature, et le sera probablement aussi pour d'autres.
Sander Vanden Hautte
16

Nous n'avons pas besoin d'un LabelEncoder.

Vous pouvez convertir les colonnes en catégories, puis obtenir leurs codes. J'ai utilisé une compréhension de dictionnaire ci-dessous pour appliquer ce processus à chaque colonne et envelopper le résultat dans une trame de données de la même forme avec des indices et des noms de colonne identiques.

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

Pour créer un dictionnaire de cartographie, vous pouvez simplement énumérer les catégories à l'aide d'une compréhension de dictionnaire:

>>> {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'}}
Alexandre
la source
Si je veux revenir en arrière (inverser) pour une colonne (exemple de variable cible: Y) comment faire?
Ib D
9

cela ne répond pas directement à votre question (pour laquelle Naputipulu Jon et PriceHardman ont des réponses fantastiques)

Cependant, aux fins de quelques tâches de classification, etc., vous pouvez utiliser

pandas.get_dummies(input_df) 

cela peut entrer un cadre de données avec des données catégorielles et retourner un cadre de données avec des valeurs binaires. les valeurs des variables sont codées en noms de colonnes dans la trame de données résultante. plus

Anurag Priyadarshi
la source
6

En supposant que vous essayez simplement d'obtenir un sklearn.preprocessing.LabelEncoder()objet pouvant être utilisé pour représenter vos colonnes, tout ce que vous avez à faire est de:

le.fit(df.columns)

Dans le code ci-dessus, vous aurez un numéro unique correspondant à chaque colonne. Plus précisément, vous aurez un mappage 1: 1 de df.columnsto le.transform(df.columns.get_values()). Pour obtenir le codage d'une colonne, passez-le simplement à le.transform(...). À titre d'exemple, les éléments suivants obtiendront le codage pour chaque colonne:

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

En supposant que vous souhaitez créer un sklearn.preprocessing.LabelEncoder()objet pour toutes vos étiquettes de ligne, vous pouvez effectuer les opérations suivantes:

le.fit([y for x in df.get_values() for y in x])

Dans ce cas, vous avez très probablement des étiquettes de ligne non uniques (comme indiqué dans votre question). Pour voir quelles classes le codeur a créé, vous pouvez le faire le.classes_. Vous remarquerez que cela devrait avoir les mêmes éléments que dans set(y for x in df.get_values() for y in x). Encore une fois pour convertir une étiquette de ligne en une utilisation d'étiquette codée le.transform(...). Par exemple, si vous souhaitez récupérer l'étiquette de la première colonne du df.columnstableau et de la première ligne, vous pouvez procéder comme suit:

le.transform([df.get_value(0, df.columns[0])])

La question que vous vous posiez dans votre commentaire est un peu plus compliquée, mais peut toujours être accomplie:

le.fit([str(z) for z in set((x[0], y) for x in df.iteritems() for y in x[1])])

Le code ci-dessus fait ce qui suit:

  1. Faire une combinaison unique de toutes les paires de (colonne, ligne)
  2. Représentez chaque paire comme une version chaîne du tuple. Il s'agit d'une solution de contournement pour surmonter la LabelEncoderclasse ne prenant pas en charge les tuples en tant que nom de classe.
  3. Adapte les nouveaux éléments au LabelEncoder.

Maintenant, pour utiliser ce nouveau modèle, c'est un peu plus compliqué. En supposant que nous voulons extraire la représentation pour le même élément que nous avons recherché dans l'exemple précédent (la première colonne dans df.columns et la première ligne), nous pouvons le faire:

le.transform([str((df.columns[0], df.get_value(0, df.columns[0])))])

N'oubliez pas que chaque recherche est maintenant une représentation sous forme de chaîne d'un tuple qui contient la (colonne, ligne).

TehTechGuy
la source
5

Non, LabelEncoderne fait pas ça. Il prend des tableaux 1D d'étiquettes de classe et produit des tableaux 1D. Il est conçu pour gérer les étiquettes de classe dans les problèmes de classification, et non les données arbitraires, et toute tentative de le forcer à d'autres utilisations nécessitera du code pour transformer le problème réel en problème qu'il résout (et la solution dans l'espace d'origine).

Fred Foo
la source
Ok, étant donné cela, quelle est votre suggestion sur la meilleure façon de coder les étiquettes de chaîne par un tout DataFrameà la fois?
Bryan
@Bryan Regardez le LabelEncodercode et adaptez-le. Je n'utilise pas les Pandas moi-même, donc je ne sais pas à quel point ce sera difficile.
Fred Foo
Je laisserai d'autres pandaspersonnes tenter de répondre à cette question également - je suis sûr que je ne suis pas la seule personne à relever ce défi, alors j'espère qu'il pourrait y avoir une solution préconfigurée.
Bryan
5

C'est un an et demi après le fait, mais moi aussi, je devais être capable de .transform()plusieurs colonnes de trames de données pandas à la fois (et de pouvoir les utiliser .inverse_transform()également). Cela développe l'excellente suggestion de @PriceHardman ci-dessus:

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

Exemple:

Si dfet df_copy()sont des pandascadres de données de type mixte, vous pouvez appliquer le MultiColumnLabelEncoder()aux dtype=objectcolonnes de la manière suivante:

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

Vous pouvez accéder aux classes de colonnes individuelles, aux étiquettes de colonnes et aux encodeurs de colonnes utilisés pour ajuster chaque colonne via l'indexation:

mcle.all_classes_
mcle.all_encoders_
mcle.all_labels_

Jason Wolosonovich
la source
Bonjour Jason, mcle.all_labels_ ne semble pas fonctionner (Python 3.5, Conda 4.3.29, Sklearn 0.18.1, Pandas 0.20.1. J'obtiens: AttributeError: l'objet 'MultiColumnLabelEncoder' n'a pas d'attribut 'all_labels_'
Jason
@Jason Salut, désolé, je n'ai pas vu cela jusqu'à aujourd'hui: / mais si je devais deviner, je dirais que vous venez d'utiliser la fitméthode ci-dessus qui ne produira pas d'étiquettes avant de l'appliquer ( transform/ fit_transform) à les données.
Jason Wolosonovich
Je pense que vous devez donner un meilleur exemple - je n'ai pas pu réexécuter tous vos codes.
user702846
2

Dans le prolongement des commentaires soulevés sur la solution de @PriceHardman, je proposerais la version suivante de la classe:

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

Cette classe s'adapte à l'encodeur sur l'ensemble d'entraînement et utilise la version adaptée lors de la transformation. La version initiale du code peut être trouvée ici .

Dror
la source
2

Un court chemin vers LabelEncoder()plusieurs colonnes avec 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])

et vous pouvez l'utiliser le_dictpour étiqueterEncode toute autre colonne:

le_dict[col].transform(df_another[col])
À M
la source
2

Il est possible de faire tout cela directement chez les pandas et est bien adapté à une capacité unique de la replaceméthode.

Commençons par créer un dictionnaire de dictionnaires mappant les colonnes et leurs valeurs à leurs nouvelles valeurs de remplacement.

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}}

Comme ce sera toujours un mappage un à un, nous pouvons inverser le dictionnaire interne pour obtenir un mappage des nouvelles valeurs à l'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'}}

Maintenant, nous pouvons utiliser la capacité unique de la replaceméthode de prendre une liste imbriquée de dictionnaires et d'utiliser les clés externes comme colonnes et les clés internes comme valeurs que nous aimerions remplacer.

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

On peut facilement revenir à l'original en enchaînant à nouveau la replaceméthode

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
la source
2

Après beaucoup de recherches et d'expérimentation avec quelques réponses ici et ailleurs, je pense que votre réponse est ici :

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

Cela préservera les noms des catégories dans les colonnes:

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
Christophe
la source
2

J'ai vérifié le code source ( https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/label.py ) de LabelEncoder. Il était basé sur un ensemble de transformations numpy, dont l'une est np.unique (). Et cette fonction ne prend qu'une entrée de tableau 1-d. (Corrigez-moi si je me trompe).

Idées très approximatives ... identifiez d'abord les colonnes qui nécessitent LabelEncoder, puis parcourez chaque colonne.

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

Le rendu df serait celui après le codage, et label_list vous montrera ce que toutes ces valeurs signifient dans la colonne correspondante. Ceci est un extrait d'un script de traitement des données que j'ai écrit pour le travail. Faites-moi savoir si vous pensez qu'il pourrait y avoir d'autres améliorations.

EDIT: Je veux juste mentionner ici que les méthodes ci-dessus fonctionnent avec une trame de données sans manquer le meilleur. Je ne sais pas comment cela fonctionne vers le bloc de données contient des données manquantes. (J'ai eu un accord avec la procédure manquante avant d'exécuter les méthodes ci-dessus)

willaccc
la source
1

si nous avons une seule colonne pour faire l'encodage des étiquettes et sa transformation inverse, il est facile de le faire quand il y a plusieurs colonnes 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
la source
1

Si vous avez les deux types de données numériques et catégoriques dans la trame de données Vous pouvez utiliser: ici X est ma trame de données ayant les deux variables catégorielles et numériques

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

Remarque: Cette technique est bonne si vous n'êtes pas intéressé à les reconvertir.

Vikas Gupta
la source
1

Utilisation de Neuraxle

TLDR; Vous pouvez utiliser ici la FlattenForEach classe enveloppe pour transformer simplement votre df comme: FlattenForEach(LabelEncoder(), then_unflatten=True).fit_transform(df).

Avec cette méthode, votre encodeur d'étiquette pourra s'adapter et se transformer au sein d'un Pipeline Scikit-Learn régulier . Importons simplement:

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

Même encodeur partagé pour les colonnes:

Voici comment un LabelEncoder partagé sera appliqué à toutes les données pour le coder:

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

Résultat:

    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)

Encodeurs différents par colonne:

Et voici comment un premier LabelEncoder autonome sera appliqué sur les animaux de compagnie, et un second sera partagé pour le propriétaire et l'emplacement des colonnes. Donc, pour être précis, nous avons ici un mélange d'encodeurs d'étiquettes différents et partagés:

    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)

Résultat:

    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
la source
0

Principalement utilisé la réponse @Alexander mais a dû faire quelques changements -

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

Ensuite, pour réutiliser à l'avenir, vous pouvez simplement enregistrer la sortie dans un document json et lorsque vous en avez besoin, vous le lisez et utilisez la .map()fonction comme je l'ai fait ci-dessus.

bbennett36
la source
0

Le problème est la forme des données (trame de données pd) que vous transmettez à la fonction d'ajustement. Vous devez passer la liste 1d.

Ali sadr
la source
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)

Ici, je lis un csv de l'emplacement et en fonction je passe la liste des colonnes que je veux étiqueter et le dataframe que je veux appliquer.


la source
0

Que dis-tu de ça?

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

Ce n'est pas le plus efficace, mais ça marche et c'est super simple.

Dominik Novotný
la source