Nous avons appris dans les sections précédentes que la haute cardinalité et les étiquettes rares peuvent faire que certaines catégories n’apparaissent que dans le jeu d’entrainement, entraînant ainsi un sur-ajustement, ou seulement dans le jeu de test, et que nos modèles ne sauraient alors pas comment noter ces observations.
Nous avons également appris précedemment, que si les variables catégorielles contiennent plusieurs étiquettes, alors en les recodant avec des variables fictives, nous élargirons considérablement l’espace des caractéristiques.
Pour éviter ces complications, nous pouvons créer des variables factices uniquement pour les catégories les plus fréquentes
Cette procédure est également appelée “encodage à chaud des catégories fréquentes”.
En fait, dans la solution gagnante de la coupe KDD 2009 : “Winning the KDD Cup Orange Challenge with Ensemble Selection”, les auteurs limitent un encodage à chaud aux 10 étiquettes les plus fréquentes de la variable. Cela signifie qu’ils ne créeraient qu’une seule variable binaire pour chacun des 10 labels les plus fréquents.
OHE des catégories les plus fréquentes ou les plus élevées équivaut à regrouper toutes les catégories restantes sous une nouvelle catégorie. Nous verrons plus en détail comment regrouper les valeurs rares dans une nouvelle catégorie dans un notebook.
Avantages de l’OHE des catégories supérieures
- Simple à mettre en œuvre
- Ne nécessite pas d’heures d’exploration des variables
- N’élargit pas massivement l’espace de présentation
- Convient aux modèles linéaires
Limitations
- N’ajoute aucune information qui pourrait rendre la variable plus prédictive
- Ne conserve pas les informations des étiquettes ignorées
Souvent, les variables catégorielles montrent quelques catégories dominantes tandis que les autres étiquettes n’ajoutent que peu d’informations. Par conséquent, l’OHE des catégories dominantes est une technique simple et utile.
Note
Le nombre de variables supérieures est fixé arbitrairement. Dans le cadre du concours KDD, les auteurs en ont sélectionné 10, mais il aurait pu y en avoir 15 ou 5 également. Ce nombre peut être choisi arbitrairement ou être dérivé de l’exploration des données.
Dans cette démo :
Nous allons voir comment effectuer un encodage à chaud avec :
- Feature-Engine
import numpy as np
import pandas as pd
# to split the datasets
from sklearn.model_selection import train_test_split
# for one hot encoding with feature-engine
from feature_engine.categorical_encoders import OneHotCategoricalEncoder
data = pd.read_csv('../exportfeature.csv',usecols=['place', 'M1', 'M2', 'Hippodrome','idJockey'],sep=";",encoding='ANSI')
data.head()
# examinons le nombre d'étiquettes de chaque variable
for col in data.columns:
print(col, ': ', len(data[col].unique()), ' labels')
place : 2 labels
M1 : 67 labels
M2 : 71 labels
Hippodrome : 199 labels
# explorons les catégories uniques
data['M1'].unique()
array(['3a', '1a', 'Da', '8a', '2a', '9a', '4a', '5a', '10a', 'Aa', 'Dm',
'13a', '7a', '6m', '11a', '6a', '2Da', '0a', '1Disqa', '10m', '3m',
'3Da', '14a', '5m', '4Dista', '1m', '1Dista', '2Dm', '4Da', '15a',
'1Da', '10Dista', '3Dista', '5Da', '4m', '6Da', '12a', '0m', '2m',
'7Da', '8m', '2Dista', 'Rpa', '9m', '7m', '4Disqa', 'Dista',
'7Dista', '4Distm', '2Dpga', '6Dista', '5Dista', '1Dpga', '1Dm',
'Am', 'Ta', '16a', '17a', '8Dista', '2Disqa', 'Dpga', '12m', '3Dm',
'11m', '3Dpga', '9Da', '8Dm'], dtype=object)
data['M2'].unique()
array(['3m', '9Da', 'Da', '7a', '6a', '5a', '2a', '1a', '10a', '3a', '4m',
'0a', '4a', '8a', '9a', '2m', '14a', 'Aa', '5Da', '5m', 'Dm',
'7Dista', '12a', '6m', '2Da', '11a', '7m', '15a', '9m', '4Dm',
'4Dista', '8m', '1Da', '16a', 'Am', 'Dista', '3Da', '3Dista', '1m',
'1Dista', '0m', '8Dpga', '13a', '1Disqa', '3Disqa', '5Dista',
'1Dm', '4Da', '3Distm', '10m', '6Da', 'Ta', '2Dpga', '7Da', '13m',
'2Dista', '1Dpga', '5Dm', '12m', '8Da', '8Dista', '11m', '6Dista',
'9Dista', '17a', '12Dist.a', '14m', '5Dpga', '2Dm', 'Dpga', '8Dm'],
dtype=object)
data['Hippodrome'].unique()
array(['Vincennes', 'Wolvega', 'Argentan', 'Toulouse', 'Cagnes-sur-Mer',
'Mons', 'Cagnes', 'Cordemais', 'Marseille', 'Geelong',
'Marseille-Vivaux', 'Gelsenkirchen', 'Son Pardo Majorque',
'Nantes', 'Lyon', 'Lyon-La Soie', 'Son Pardo', 'Agen',
'Vienne Krieau', 'Saint', 'Saint-Galmier', 'Bordeaux',
'Bordeaux Le Bouscat', 'Mauquenchy', 'Châteaubriant', 'Kuurne',
'Angers', 'Grenade-sur-Garonne', 'Graignes', 'Laval', 'Vire',
'Enghien', 'Le Croisé-Laroche', 'Berlin-Mariendorf', 'Avenches',
'Le Croisé', 'Pontchâteau', 'Chartres', 'Caen', 'Amiens',
'Munich-Daglfing', 'Machecoul', 'Lyon-Parilly', 'Lisieux',
'Hyères', 'Fougères', 'Reims', 'Castillonnès', 'Cherbourg',
'Marseille-Borély', 'Maure', 'Maure-de-Bretagne',
'Ebreichsdorf (Magna Racino)', 'La Capelle', 'Beaumont', 'Tours',
'Beaumont-de-Lomagne', 'Tours-Chambray', 'Nort-sur-Erdre',
'Le Mans', 'Tongres', 'Chatillon', 'Strasbourg',
'Chatillon-sur-Chalaronne', 'Challans', 'Meslay',
'Meslay-du-Maine', 'Cavaillon', 'Saint-Malo', 'Rambouillet',
'Oraison', 'Aby', 'Alençon', 'Cholet', 'Montluçon', 'Nîmes',
'Vitré', 'Avignon', 'Charlottenlund', 'Paray-le-Monial',
'Montauban', 'Vichy', 'Laon', 'Villeneuve-sur-Lot', 'Solvalla',
'Vannes', 'Nancy', 'La Roche', 'La Roche-Posay', 'Frauenfeld',
'Lignières', 'Saint-Brieuc', 'Chatelaillon',
'Chatelaillon-La Rochelle', 'Feurs', 'Ostersund', 'Erbray',
'Marsa', 'Bjerke', 'Kouvola', 'Boden', 'Arras', 'Segré',
'Duindigt', 'La Gacilly', 'Agon-Coutainville', 'Sablé-sur-Sarthe',
'Cabourg', 'Pornichet', 'Biarritz', 'Jarlsberg', 'Eauze', 'Arjang',
'Royan-la Palmyre', 'Mikkeli', 'Clairefontaine',
'Les Sables-d'Olonne', 'Bernay', 'L'Isle-sur-La Sorgue',
'Jägersro', 'Le Touquet', 'Aix-les-Bains', 'Saint-Jean-de-Monts',
'Carpentras', 'Bréhal', 'Vittel', 'Auch', 'Dieppe',
'Langon-Libourne', 'Villeréal', 'Zonza', 'Le Mont-Saint-Michel',
'Craon', 'Montier-en-Der', 'Divonne-les-Bains', 'Bergsaker',
'Carentan', 'Waregem', 'Ajaccio', 'Baden', 'Ecommoy', 'Angoulême',
'Salon-de-Provence', 'Castera-Verduzan', 'Eskilstuna', 'Ballarat',
'Melton', 'Loudéac', 'Vermo', 'Hambourg Bahrenfeld', 'Niort',
'Dundalk', 'Saint-Omer', 'Montluçon-Néris-les-Bains', 'Straubing',
'Ostende', 'Dielsdorf', 'Landivisiau', 'Prunelli-di-Fiumorbo',
'Dinslaken', 'Yonkers-New York', 'Kilmore', 'Saint-Moritz',
'Hambourg Horn', '(Q+ du mardi 25', 'Moenchengladbach', '<br',
'lundi 02', '(R1C5) lundi 14', 'Singapour', 'Farjestad',
'Halmstad', 'Axevalla', 'Gavle', 'Hagmyren', 'Mantorp', 'Orebro',
'Bollnas', 'Romme', 'Umaker', 'Dannero', 'Rattvik', 'Vaggeryd',
'Aalborg', 'Leangen', 'Biri', 'Skive', 'Bergen', 'Momarken',
'Amal', 'Forus', 'Odense', 'Harstad', 'Visby', 'Skelleftea',
'Kalmar', 'Meadowlands', '(Gr.II) jeudi 12', '(R1C1) Vendredi 04'],
dtype=object)
Encodage important
Il est important de sélectionner les catégories les plus importantes ou les plus fréquentes sur la base des données relatives aux trains. Ensuite, nous utiliserons ces catégories supérieures pour coder les variables dans les données de test
# séparons en ensemble de formation et de test
X_train, X_test, y_train, y_test = train_test_split(
data[['M1', 'M2', 'Hippodrome','idJockey']],
data['place'],
test_size=0.3,
random_state=0)
X_train.shape, X_test.shape
((14352, 4), (6152, 4))
# Examinons d'abord comment OHE élargit l'espace des fonctionnalités
pd.get_dummies(X_train, drop_first=True).shape
(14352, 1994)
Des 3 variables catégorielles initiales, nous arrivons à 1994 variables.
Ces chiffres ne sont pas encore énormes et, dans la pratique, nous pourrions travailler avec eux relativement facilement. Toutefois, dans les ensembles de données de la vie réelle, les variables catégorielles peuvent être très cardinales, et avec l’OHE, nous pouvons nous retrouver avec des ensembles de données comportant des milliers de colonnes.
Un encodage à chaud des catégories supérieures avec Feature-Engine
Avantages
- rapide
- crée le même nombre de caractéristiques dans le train et le banc d’essai
Limitations
- Aucune à ma connaissance
**Note
Si la variable argument est laissée à None, alors le codeur identifiera automatiquement toutes les variables catégorielles. N’est-ce pas adorable ?
Le codeur ne codera pas les variables numériques. Donc, si certaines de vos variables numériques sont en fait des catégories, vous devrez les reformuler en tant qu’objet avant d’utiliser l’encodeur. C’est le cas pour notre catégorie idJockey
X_train['idJockey'] = X_train['idJockey'].astype(object)
X_test['idJockey'] = X_test['idJockey'].astype(object)
ohe_enc = OneHotCategoricalEncoder(
top_categories=10, # Par défaut on prend 10 mais vous pouvez l'augmenter
# Nous pouvons sélectionner spécifiquement les variables que l'on veut utiliser
variables=['M1', 'M2', 'Hippodrome','idJockey'],
drop_last=False)
ohe_enc.fit(X_train)
# dans l'encodeur, nous pouvons observer chacune des catégories supérieures
# sélectionné pour chacune des variables
ohe_enc.encoder_dict_
{'M1': ['1a', '2a', 'Da', '3a', '4a', '5a', '6a', '7a', '8a', '9a'],
'M2': ['1a', 'Da', '2a', '3a', '4a', '5a', '6a', '7a', '8a', '9a'],
'Hippodrome': ['Vincennes',
'Cagnes-sur-Mer',
'Enghien',
'Mons',
'Avenches',
'Cabourg',
'Vichy',
'Laval',
'Caen',
'Mauquenchy'],
'idJockey': [2, 34, 6, 4, 28, 360, 1931, 143, 3, 22]}
# voici la liste des variables que le codeur va transformer
ohe_enc.variables
['M1', 'M2', 'Hippodrome', 'idJockey']
X_train = ohe_enc.transform(X_train)
X_test = ohe_enc.transform(X_test)
# explorons le résultat
X_train.head()
M1_1a M1_2a M1_Da M1_3a M1_4a M1_5a M1_6a M1_7a M1_8a M1_9a ... idJockey_2 idJockey_34 idJockey_6 idJockey_4 idJockey_28 idJockey_360 idJockey_1931 idJockey_143 idJockey_3 idJockey_22
2540 0 0 0 0 0 1 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
19008 1 0 0 0 0 0 0 0 0 0 ... 0 0 0 1 0 0 0 0 0 0
7624 0 0 0 0 0 0 0 1 0 0 ... 0 0 0 0 0 0 0 0 0 0
2575 0 1 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
852 0 0 0 0 0 0 1 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
5 rows × 40 columns