[PYTHON] J'ai essayé de créer OneHotEncoder, qui est souvent utilisé pour l'analyse des données, afin qu'il puisse atteindre l'endroit qui démange.

Messages de Hisabisa

Fond fait

Il existe des category_encoders et des pandas get_dummies () pour OneHot Encoding, mais je trouve difficile à utiliser dans les points suivants.

  1. get_dummies () ne peut être qu'un seul coup pour la série d'entrée → Incohérent lorsque les données sont séparées en train et testées dans l'analyse des données
  2. Dans category_encoders, le 1 ci-dessus peut être résolu car il est au format d'ajustement et de transformation, mais il est difficile à comprendre car le nom de la colonne et le nom de la catégorie sont incohérents.

Je ne pouvais pas atteindre l'endroit qui me démangeait et c'était des démangeaisons

J'ai créé mon propre encodeur ohehot pour résoudre ce problème

code

class BaseEncoder():
    def __init__(self):
        pass
    
    def fit(self):
        raise Exception('not implemented')
    
    def transform(self):
        raise Exception('not implemented')
    
    def fit_transform(self):
        raise Exception('not implemented')


class OneHotEncoder(BaseEncoder):
    # library requirement
    # import pandas as pd
    # import numpy as np
    # mojimoji
    
    def __init__(self, 
                 col_name=None, 
                 categories=None, 
                 handle_unknown="summarize", 
                 handle_nan="onehot", 
                 col_order="name", 
                 col_name_type="category",
                 force_hankaku=True,
                 return_type="df",
                 handle_rare=None,
                 dummy=None,
                ):
        
        import pandas as pd
        import numpy as np
        import mojimoji
        
        #---
        #  args
        #    col_name : target column [str, default : None]      get column name from training data. If training data is np values, col is None
        #
        #    categories : encoded category list [list, default : None]
        #
        #    handle_unknown : handle unknown category method [str, default : "summarize"] 
        #      "summarize" : unknown category (not appeared in training data) is treated as "unknownCategory"
        #      "ignore" : unknown category is ignored  
        #
        #    handle_nan : handle nan method [str, default : "onehot"]  
        #      "onehot" : nan is treated as onehot
        #      "ignore" : nan is ignored
        #
        #    col_order : output order [str, default : "name"]  
        #      "name" : sorted by category name
        #      "count_asc" : sorted by ascending appearance count
        #      "count_des" : sorted by descending appearance count
        #
        #    col_name_type : column name type [str, default : "category"]
        #      "name" : return column name is category name
        #      "index" : return column name is index number (rare : -1, nan : -2, impute : -3)
        #
        #    force_hankaku : whether apply hankaku or not [bool , default : True]
        #
        #    return_type : return values type [str, default : "df"]   "pd" : pd.DataFrame , "np" : np.values
        #
        #    handle_rare : rare category  treat method [float, list, default : None]
        #      float : rare threshold of appearance category , list : this list category is treated as rare
        #
        #   dummy : dummy method [str, bool, None, defult: None]
        #      str : category name , this category is treated as dummy
        #      True : dummy is valid, and dummy category is selected automatically
        #
        
        self.col_name = col_name
        
        if type(categories) is list:
            raise Exception(f"[Error] argument categories is invalid , shuold be list, but>> {categories}")
        self.categories = categories
        
        checks = ["summarize" , "ignore"]
        if handle_unknown not in checks:
            raise Exception(f"[Error] argument handle_unknown is invalid , shuold be {checks}, but {handle_unknown}")
        self.handle_unknown = handle_unknown
        
        checks = ["onehot" , "ignore"]
        if handle_nan not in checks:
            raise Exception(f"[Error] argument handle_nan is invalid , shuold be {checks}, but {handle_nan}")
        self.handle_nan = handle_nan
        
        checks = ["name" , "count_asc", "count_des"]
        if col_order not in checks:
            raise Exception(f"[Error] argument col_order is invalid , shuold be {checks}, but {col_order}")
        self.col_order = col_order
        
        checks = ["category" , "index"]
        if col_name_type not in checks:
            raise Exception(f"[Error] argument col_name_type is invalid , shuold be {checks}, but {col_name_type}")
        self.col_name_type = col_name_type
        
        checks = [bool]
        if type(force_hankaku) not in checks:
            raise Exception(f"[Error] argument force_hankaku should be {checks} type , but {force_hankaku}")
        self.force_hankaku = force_hankaku
        
        checks = ["df" , "np"]
        if return_type not in checks:
            raise Exception(f"[Error] argument return_type is invalid , shuold be {checks}, but {return_type}")
        self.return_type = return_type
        
        checks = [int, float, list]
        if type(handle_rare) not in checks and handle_rare is not None:
            raise Exception(f"[Error] argument handle_rare should be {checks} type or None, but {handle_rare}")
        
        if type(handle_rare) in [int, float]:
            if handle_rare >= 1 or handle_rare <= 0:
                print(f"[Warning] handle_rare may be meaningless value >> {handle_rare}")
        self.handle_rare = handle_rare if handle_rare is not None else -1.
    
        checks = [str, bool]
        if type(dummy) not in checks and dummy is not None:
            raise Exception(f"[Error] argument force hankaku should be {checks} type or None , but {dummy}")
        self.dummy = dummy # True only
        
        self.encode_map = {}
        self.unknown_categories = []
        self.dummy_category = None
        
        
    def fit(self, Xs):
        _Xs = pd.Series(Xs.copy()).astype(str)
        _Xs = _Xs.apply(lambda x : mojimoji.zen_to_han(x))
            
        # get column name
        if self.col_name is None:
            self.col_name = _Xs.name
            if self.col_name is None:
                self.col_name = "onehotEncode"
                print(f"[Warning] column name is {self.col_name}")
                
        new_cols = []
                
        # if categories is inputted
        if self.categories is not None:
            cats = pd.Series(self.categories).astype(str)
            if self.force_hankaku:
                cats = _Xs.apply(lambda x : mojimoji.zen_to_han(x))
                
            for c in [x for x in cats if x not in ["nan", "None"]]:
                onehot_name = f"{self.col_name}_{c}"
                self.encode_map[c] = onehot_name
                new_cols.append(onehot_name)
            
            # handle nan
            if self.handle_nan == "onehot":
                for nan_v in ["nan", "None"]:
                    if nan_v in cats:
                        onehot_name = f"{self.col_name}_nan"
                        self.encode_map[nan_v] = onehot_name
                        new_cols.append(onehot_name)
                    

            # handle unknown
            if self.handle_unknown == "summarize":
                new_cols.append(f"{self.col_name}_unknownCategory")
                
            self.new_cols = new_cols
            
            return
            
        # get category
        vc = _Xs.value_counts(dropna=False, normalize=True)
        
        # sort category
        if self.col_order == "name":
            vc.sort_index(inplace=True)
        elif self.col_order == "count_asc":
            vc.sort_values(inplace=True, ascending=True)
        elif self.col_order == "count_des":
            vc.sort_values(inplace=True, ascending=False)

        # rare category (threshold)
        if type(self.handle_rare) is float:
            for c_ind, c in enumerate([x for x in vc[vc > self.handle_rare].index if x not in ["nan", "None"]]):
                
                # skip dummy
                if (self.dummy == True and c_ind == 0) or (self.dummy == c):
                    self.dummy_category = c
                    self.encode_map[c] = "DUMMY_CATEGORY"
                    continue
                
                if self.col_name_type == "category":
                    onehot_name = f"{self.col_name}_{c}"
                elif self.col_name_type == "index":
                    onehot_name = f"{self.col_name}_{c_ind}"
                self.encode_map[c] = onehot_name
                new_cols += [onehot_name]
                
            for c in [x for x in vc[vc <= self.handle_rare].index if x not in ["nan", "None"]]:
                if self.col_name_type == "category":
                    onehot_name = f"{self.col_name}_rareCategory"
                elif self.col_name_type == "index":
                    onehot_name = f"{self.col_name}_-1"
                    
                self.encode_map[c] = onehot_name
                if onehot_name not in new_cols:
                    new_cols += [onehot_name]
                    
        # rare category (list)
        if type(self.handle_rare) is list:
            for c_ind, c in enumerate([x for x in vc.index if x not in ["nan" , "None"] + self.handle_rare]):
                
                # skip dummy
                if (self.dummy and c_ind == 0) or (self.dummy == c):
                    self.dummy_category = c
                    continue
                
                if self.col_name_type == "category":
                    onehot_name = f"{self.col_name}_{c}"
                elif self.col_name_type == "index":
                    onehot_name = f"{self.col_name}_{c_ind}"
                    
                self.encode_map[c] = onehot_name
                new_cols += [onehot_name]
                
            for c in self.handle_rare:
                if self.col_name_type == "category":
                    onehot_name = f"{self.col_name}_rareCategory"
                elif self.col_name_type == "index":
                    onehot_name = f"{self.col_name}_-1"
                    
                self.encode_map[c] = onehot_name
                if onehot_name not in new_cols:
                    new_cols += [onehot_name]

        # handle nan
        if self.handle_nan == "onehot":
            for nan_v in ["nan", "None"]:
                if nan_v in vc.index:
                    if self.col_name_type == "category":
                        onehot_name = f"{self.col_name}_nan"
                    elif self.col_name_type == "index":
                        onehot_name = f"{self.col_name}_-2"
                    self.encode_map[nan_v] = onehot_name
                    if onehot_name not in new_cols:
                        new_cols += [onehot_name]
                
        # handle unknown
        if self.handle_unknown == "summarize":
            if self.col_name_type == "category":
                new_cols.append(f"{self.col_name}_unknownCategory")
            elif self.col_name_type == "index":
                new_cols.append(f"{self.col_name}_-3")
                
        encode_map_inv = {}
        
        for k, v in self.encode_map.items():
            if v in encode_map_inv.keys():
                encode_map_inv[v] += [k]
            else:
                encode_map_inv[v] = [k]
                
        self.new_cols = new_cols
        self.categories = list(self.encode_map.keys())
        self.encode_map_inv = encode_map_inv
        
        del _Xs
        
        
    def transform(self, Xs):
        _Xs = pd.Series(Xs.copy()).astype(str)
        if self.force_hankaku:
            _Xs = _Xs.apply(lambda x : mojimoji.zen_to_han(x))
            
        # return dataframe
        res_df = pd.DataFrame(index=range(len(_Xs)))

        for k, v in self.encode_map_inv.items():
            if k == "DUMMY_CATEGORY":
                continue
            
            res_df[k] = 0 # fill 0
            res_df.loc[_Xs.isin(v), k] = 1 # one hot
                
        # handle unknown
        if self.handle_unknown == "summarize":
            new_col = f"{self.col_name}_unknownCategory"
            res_df[new_col] = 0 # fill 0
            
            known_cats = self.categories
            if self.handle_nan == "ignore":
                known_cats += ["nan", "None"]
            
            res_df.loc[~_Xs.isin(known_cats), new_col] = 1 # one hot
            for cat in list(set(_Xs.values) - set(known_cats)):
                if cat not in self.unknown_categories:
                    self.unknown_categories += [cat]
        
        del _Xs
        
        # return type redefine
        if self.return_type == "np":
            res_df = res_df.values
            
        return res_df
    
    
    def fit_transform(self, Xs):
        self.fit(Xs)
        return self.transform(Xs)

Comment utiliser

Créez des exemples de données pour la formation et les tests comme suit

Le test a des catégories (éléphant, oiseau, etc.) qui ne se trouvent pas dans l'apprentissage (l'encodeur créé a également une fonction pour les mettre dans la catégorie inconnue).

# generate sample category
import random
random.seed(42)

vals1 = ['salamander'] * 10 + ['snake'] * 8 + ['cameleon'] * 5 + ['rizard'] * 7 + ['frog'] * 2 + ['jellyfish'] * 3 + [np.nan] * 3 + [None] * 2
vals2 = ['cute'] * 4 + ['cool'] * 12 + ['colurful'] * 3 + ['nice'] * 2 + ['Wonderful'] * 3 + ['foooo'] * 3 + ['Excellent'] * 3 + [np.nan] * 6 + [None] * 4

vals3 = ['salamander'] * 13 + ['snake'] * 5 + ['cameleon'] * 7 + ['rizard'] * 5 + ['turtle'] * 3 + ['bird'] * 1 + ['elephant'] * 1 + ["jellyfish"] * 2 + [np.nan] * 1 + [None] * 2
vals4 = ['cute'] * 4 + ['cool'] * 12 + ['colorful'] * 3 + ['nice'] * 2 + ['Wonderful'] * 3 + ['foooo'] * 3 + ['Excellent'] * 1 + ['good'] * 1 + ['OK'] * 1  + [np.nan] * 3 + [None] * 7 

random.shuffle(vals1)
random.shuffle(vals2)

random.shuffle(vals3)
random.shuffle(vals4)

train_df =  pd.DataFrame({'animal' : vals1, 'feature' : vals2})
test_df =  pd.DataFrame({'animal' : vals3, 'feature' : vals4})

Utilisez-le simplement

Essayez une colonne d'animaux chauds

#Créer une instance
ohe = OneHotEncoder()

# train data de
Encoder le train
ohe.fit(train_df['animal'])
#En fait, encodez. Mettez les données d'entraînement en transformation,

ohe.transform(train_df['animal'])

image.png

Concater avec les données d'origine et voir le résultat

pd.concat([train_df, ohe.transform(train_df['animal'])], axis=1)

image.png

Certains nans sont correctement onehot et il existe également des catégories inconnues.

Regardons les données de test

pd.concat([test_df, ohe.transform(test_df['animal'])], axis=1)

image.png

Puisque la même colonne que le train est préparée, elle peut être utilisée telle quelle avec gbm léger ou filet élastique

Je veux vérifier dans quelle colonne la catégorie a été encodée

Cette fonction était rare comme elle l'était, alors je l'ai implémentée dict retourne avec ohe.encode_map

ohe.encode_map

Vous pouvez également voir la version inversée

ohe.encode_map_inv

Obtenez le nom de colonne ajouté

ohe.new_cols

Si vous ignorez nan

Spécifiez handle_nan = "ignorer"

ohe = OneHotEncoder(handle_nan="ignore")
ohe.fit(train_df['animal'])

la colonne nan est partie

image.pngimage.png

Gestion de catégories rares et peu fréquentes

Vous pouvez spécifier une catégorie comme étant une catégorie rare telle que handle_rare = 0,1 (le nombre 0,1 est%)

Voir à quelle fréquence les animaux apparaissent

essayer d'encoder

ohe = OneHotEncoder(handle_rare=0.1)
ohe.fit(train_df['animal'])

rareCategory a été ajouté

image.png

Si vous regardez encode_map, vous pouvez voir ce qui est devenu rare

De plus, si vous mettez une liste de catégories dans handle_rare, les catégories saisies seront encodées en rareCategory.

ohe = OneHotEncoder(handle_rare=["cameleon", "frog"])
ohe.fit(train_df['animal'])

image.png

Je veux ignorer la catégorie inconnue

handle_unknown = "ignorer", unknown n'est pas codé

ohe = OneHotEncoder(handle_unknown="ignore")
ohe.fit(train_df['animal'])

image.png

Lorsque vous ne voulez pas que les noms de colonnes soient des catégories

Si col_name_type = "index" est défini, il devient un index (identique à category_encoders)

ohe = OneHotEncoder(col_name_type="index")
ohe.fit(train_df['animal'])

image.png

Spécifiez le nom de la colonne

Par défaut, le nom de colonne de dataframe est prefix, mais vous pouvez le changer avec col_name = "XXXX".

(Si vous entrez une valeur numpy au lieu d'un dataframe, onehotEncode devient un préfixe)

ohe = OneHotEncoder(col_name="new_col")
ohe.fit(train_df['animal'])

image.png

Je veux coder factice

Si vous voulez un encodage factice (encodage qui réduit le nombre d'entités en ne faisant pas d'une catégorie une colonne) set dummy = True

ohe = OneHotEncoder(dummy=True)
ohe.fit(train_df['animal'])

image.png

Les catégories factices peuvent être trouvées dans ohe.dummy_category

Si dummy = "xxx", la catégorie sera factice

Conclusion

Je voudrais créer une bibliothèque d'encodage catégorique qui peut être atteinte là où ça démange

Je suis heureux si vous pouvez avoir une impression en utilisant ce qui précède

Il existe d'autres fonctions détaillées en plus de celles ci-dessus, mais je suis fatigué d'écrire, alors quand j'aurai plus de likes, je prévois de créer une bibliothèque et de rassembler des utilisations telles que git.

Recommended Posts

J'ai essayé de créer OneHotEncoder, qui est souvent utilisé pour l'analyse des données, afin qu'il puisse atteindre l'endroit qui démange.
J'ai essayé de l'étendre pour que la base de données puisse être utilisée avec le logiciel d'analyse de Wiire
J'ai essayé de prédire le match de la J League (analyse des données)
J'ai essayé de gratter YouTube, mais je peux utiliser l'API, alors ne le faites pas.
J'ai essayé de résumer le code souvent utilisé dans Pandas
J'ai essayé de résumer les commandes souvent utilisées en entreprise
Je veux voir quelque chose de beau, alors j'ai essayé de visualiser la fonction utilisée pour comparer la fonction d'optimisation.
[Flask] J'ai essayé de résumer la "configuration docker-compose" qui peut être créée rapidement pour les applications Web
J'ai essayé de créer un site qui permet de voir facilement les informations mises à jour d'Azure
J'ai essayé de résumer les méthodes qui sont souvent utilisées lors de l'implémentation d'algo de base dans Quantx Factory
Une histoire qui vire au bleu lorsque les données lues par Pillow sont converties pour pouvoir être gérées par OpenCV
Je pensais que je pouvais créer un bon éditeur gitignore, alors j'ai essayé de faire quelque chose comme MVP pour le moment
J'ai essayé de résumer les opérations susceptibles d'être utilisées avec numpy-stl
J'ai essayé de vérifier le théorème du Big Bang [Est-il sur le point de revenir?]
J'ai essayé de traiter et de transformer l'image et d'élargir les données pour l'apprentissage automatique
J'ai essayé de récupérer les données de l'ordinateur portable en le démarrant sur Ubuntu
Je n'ai pas compris le redimensionnement de TensorFlow, alors je l'ai résumé visuellement.
J'ai essayé de sauvegarder les données avec discorde
J'ai essayé de faire de l'IA pour Smash Bra
J'ai essayé de visualiser les paroles de GReeeen, que j'écoutais de façon folle dans ma jeunesse mais que je ne l'écoutais plus.
[LPIC 101] J'ai essayé de résumer les options de commande qui sont faciles à faire une erreur
J'ai essayé de faire sonner le téléphone lorsqu'il a été publié sur le poste IoT
[Mis à jour de temps en temps] Mémos Python souvent utilisés pour l'analyse des données [Division N, etc.]
J'ai essayé de faire une application mémo qui peut être pomodoro, mais un enregistrement de réflexion
Les débutants en Python ont créé un chat BOT alors j'ai essayé de résumer comment le faire
C'est le jour du chat, alors j'ai essayé de créer quelque chose qui se traduise par des mots semblables à ceux d'un chat.
Je me suis rendu compte qu'il était absurde de l'utiliser sans réfléchir car le module est pratique
Le problème d'équation tangente (niveau lycée) est gênant, donc je peux le résoudre
J'ai essayé de faciliter la modification du paramètre du proxy authentifié sur Jupyter
Il fait froid, j'ai donc essayé de permettre d'allumer / d'éteindre automatiquement le chauffage AC avec Raspberry Pi!
AI Gaming Je l'ai essayé pour la première fois
Je veux dire qu'il y a un prétraitement des données ~
J'ai essayé "Streamlit" qui transforme le code Python en une application web tel quel
Quelle est la courbe ROC? Pourquoi ne devrait-il pas être utilisé pour des données déséquilibrées? Explication facile à comprendre
Comment définir des variables pouvant être utilisées dans toute l'application Django ~ Utile pour les modèles, etc. ~
[Même dans la vidéo] La science des données au célèbre niveau universitaire que vous pouvez apprendre gratuitement [Oui, si c'est coursera. ]
J'ai essayé de porter le code écrit pour TensorFlow sur Theano
J'ai essayé de créer diverses "données factices" avec Python faker
Faisons l'analyse des données de naufrage du Titanic comme ça
Lequel dois-je étudier, R ou Python, pour l'analyse des données?
Notes diverses sur l'utilisation de python pour les projets
J'ai réussi le test d'analyse de données Python, j'ai donc résumé les points
J'ai essayé d'analyser les données scRNA-seq en utilisant l'analyse des données topologiques (TDA)
Je l'ai fait parce que je veux des données JSON qui peuvent être utilisées librement dans les démos et les prototypes
[Premier grattage] J'ai essayé de créer un personnage VIP pour Smash Bra [Beautiful Soup] [En plus, analyse de données]
J'ai essayé de rendre possible l'envoi automatique d'un e-mail en double-cliquant simplement sur l'icône [Python]
Hypothèse / Vérification (176) Comment créer un manuel plus simple que "Le manuel le plus simple pour les ordinateurs quantiques"
J'ai essayé de trouver la différence entre A + = B et A = A + B en Python, alors notez
[Python] J'ai essayé de créer un programme simple qui fonctionne sur la ligne de commande en utilisant argparse
[Shell script] C'est ennuyeux d'envoyer le même contenu chaque semaine, alors j'ai essayé de l'automatiser! !! !!
Je souhaite spécifier un fichier qui n'est pas une certaine chaîne de caractères comme cible logrotate, mais est-ce impossible?