.. _artificielcategory2rst: ================================== Traitement amélioré des catégories ================================== .. only:: html **Links:** :download:`notebook `, :downloadlink:`html `, :download:`PDF `, :download:`python `, :downloadlink:`slides `, :githublink:`GitHub|_doc/notebooks/lectures/artificiel_category_2.ipynb|*` Ce notebook présenté des encoding différents de ceux implémentées dans `scikit-learn `__. .. code:: ipython3 from jyquickhelper import add_notebook_menu add_notebook_menu() .. contents:: :local: .. code:: ipython3 %matplotlib inline On construit un jeu très simple avec deux catégories, une entière, une au format texte. .. code:: ipython3 import pandas import numpy df = pandas.DataFrame(dict(cat_int=[10, 20, 10, 39, 10, 10, numpy.nan], cat_text=['catA', 'catB', 'catA', 'catDD', 'catB', numpy.nan, 'catB'])) df .. raw:: html
cat_int cat_text
0 10.0 catA
1 20.0 catB
2 10.0 catA
3 39.0 catDD
4 10.0 catB
5 10.0 NaN
6 NaN catB
Une API un peu différente ------------------------- Le module `Category Encoders `__ implémente d’autres options avec une `API `__ un peu différente puisqu’il est possible de spécifier la colonne sur laquelle s’applique l’encoding. .. code:: ipython3 from category_encoders import OneHotEncoder OneHotEncoder(cols=['cat_text']).fit_transform(df) .. raw:: html
cat_text_1 cat_text_2 cat_text_3 cat_text_4 cat_text_-1 cat_int
0 1 0 0 0 0 10.0
1 0 1 0 0 0 20.0
2 1 0 0 0 0 10.0
3 0 0 1 0 0 39.0
4 0 1 0 0 0 10.0
5 0 0 0 1 0 10.0
6 0 1 0 0 0 NaN
Autres options -------------- .. code:: ipython3 import category_encoders encoders = [] for k, enc in category_encoders.__dict__.items(): if 'Encoder' in k: encoders.append(enc) encoders .. parsed-literal:: [category_encoders.backward_difference.BackwardDifferenceEncoder, category_encoders.binary.BinaryEncoder, category_encoders.hashing.HashingEncoder, category_encoders.helmert.HelmertEncoder, category_encoders.one_hot.OneHotEncoder, category_encoders.ordinal.OrdinalEncoder, category_encoders.sum_coding.SumEncoder, category_encoders.polynomial.PolynomialEncoder, category_encoders.basen.BaseNEncoder, category_encoders.leave_one_out.LeaveOneOutEncoder, category_encoders.target_encoder.TargetEncoder, category_encoders.woe.WOEEncoder] .. code:: ipython3 dfi = df[['cat_text']].copy() dfi["copy"] = dfi['cat_text'] for encoder in encoders: if 'Leave' in encoder.__name__ or \ 'Target' in encoder.__name__ or \ 'WOE' in encoder.__name__: continue enc = encoder(cols=['cat_text']) try: out = enc.fit_transform(dfi) except Exception as e: print("Issue with '{0}' due to {1}".format(encoder.__name__, e)) continue print('-----', encoder.__name__) print(out) print('-----') .. parsed-literal:: ----- BackwardDifferenceEncoder intercept cat_text_0 cat_text_1 cat_text_2 copy 0 1 -0.75 -0.5 -0.25 catA 1 1 0.25 -0.5 -0.25 catB 2 1 -0.75 -0.5 -0.25 catA 3 1 0.25 0.5 -0.25 catDD 4 1 0.25 -0.5 -0.25 catB 5 1 0.25 0.5 0.75 NaN 6 1 0.25 -0.5 -0.25 catB ----- ----- BinaryEncoder cat_text_0 cat_text_1 cat_text_2 copy 0 0 0 1 catA 1 0 1 0 catB 2 0 0 1 catA 3 0 1 1 catDD 4 0 1 0 catB 5 1 0 0 NaN 6 0 1 0 catB ----- ----- HashingEncoder col_0 col_1 col_2 col_3 col_4 col_5 col_6 col_7 copy 0 0 0 0 0 0 0 1 0 catA 1 0 0 0 0 0 0 0 1 catB 2 0 0 0 0 0 0 1 0 catA 3 1 0 0 0 0 0 0 0 catDD 4 0 0 0 0 0 0 0 1 catB 5 1 0 0 0 0 0 0 0 NaN 6 0 0 0 0 0 0 0 1 catB ----- ----- HelmertEncoder intercept cat_text_0 cat_text_1 cat_text_2 copy 0 1 -1.0 -1.0 -1.0 catA 1 1 1.0 -1.0 -1.0 catB 2 1 -1.0 -1.0 -1.0 catA 3 1 0.0 2.0 -1.0 catDD 4 1 1.0 -1.0 -1.0 catB 5 1 0.0 0.0 3.0 NaN 6 1 1.0 -1.0 -1.0 catB ----- ----- OneHotEncoder cat_text_1 cat_text_2 cat_text_3 cat_text_4 cat_text_-1 copy 0 1 0 0 0 0 catA 1 0 1 0 0 0 catB 2 1 0 0 0 0 catA 3 0 0 1 0 0 catDD 4 0 1 0 0 0 catB 5 0 0 0 1 0 NaN 6 0 1 0 0 0 catB ----- ----- OrdinalEncoder cat_text copy 0 1 catA 1 2 catB 2 1 catA 3 3 catDD 4 2 catB 5 4 NaN 6 2 catB ----- ----- SumEncoder intercept cat_text_0 cat_text_1 cat_text_2 copy 0 1 1.0 0.0 0.0 catA 1 1 0.0 1.0 0.0 catB 2 1 1.0 0.0 0.0 catA 3 1 0.0 0.0 1.0 catDD 4 1 0.0 1.0 0.0 catB 5 1 -1.0 -1.0 -1.0 NaN 6 1 0.0 1.0 0.0 catB ----- ----- PolynomialEncoder intercept cat_text_0 cat_text_1 cat_text_2 copy 0 1 -0.670820 0.5 -0.223607 catA 1 1 -0.223607 -0.5 0.670820 catB 2 1 -0.670820 0.5 -0.223607 catA 3 1 0.223607 -0.5 -0.670820 catDD 4 1 -0.223607 -0.5 0.670820 catB 5 1 0.670820 0.5 0.223607 NaN 6 1 -0.223607 -0.5 0.670820 catB ----- ----- BaseNEncoder cat_text_0 cat_text_1 cat_text_2 copy 0 0 0 1 catA 1 0 1 0 catB 2 0 0 1 catA 3 0 1 1 catDD 4 0 1 0 catB 5 1 0 0 NaN 6 0 1 0 catB ----- Utilisation de la cible ----------------------- Certains encoding optimise l’encoding en fonction de la cible à prédire lors d’un apprentissage supervisé. Les deux encoders suivant prédisent la cible en fonction de la catégorie ou essayent d’optimiser l’encoding de la catégorie en fonction de la cible à prédire. En particulier, l’encoder `LeaveOneOut `__ associe à chaque modéalité la moyenne des valeurs observées sur une autre colonne pour chaque ligne associée à cette modalité. .. code:: ipython3 dfy = df.sort_values('cat_text').reset_index(drop=True).copy() dfy['cat_text_copy'] = dfy['cat_text'] dfy['y'] = dfy.index * dfy.index + 10 dfy['y_copy'] = dfy.y dfy .. raw:: html
cat_int cat_text cat_text_copy y y_copy
0 10.0 catA catA 10 10
1 10.0 catA catA 11 11
2 20.0 catB catB 14 14
3 10.0 catB catB 19 19
4 NaN catB catB 26 26
5 39.0 catDD catDD 35 35
6 10.0 NaN NaN 46 46
.. code:: ipython3 categories = dfy.drop('y', axis=1) label = dfy.y binary_label = label == 10 # dummy one for encoder in encoders: enc = encoder(cols=['cat_text']) try: out = enc.fit_transform(categories) except Exception as e: out = pandas.DataFrame() try: outy = enc.fit_transform(categories, label) except ValueError as e: if "must be binary" not in str(e): continue outy = enc.fit_transform(categories, binary_label) if not out.equals(outy): print('-----', encoder.__name__) print(outy) print('-----') .. parsed-literal:: ----- LeaveOneOutEncoder cat_int cat_text cat_text_copy y_copy 0 10.0 11.0 catA 10 1 10.0 10.0 catA 11 2 20.0 22.5 catB 14 3 10.0 20.0 catB 19 4 NaN 16.5 catB 26 5 39.0 23.0 catDD 35 6 10.0 23.0 NaN 46 ----- ----- TargetEncoder cat_int cat_text cat_text_copy y_copy 0 10.0 13.861768 catA 10 1 10.0 13.861768 catA 11 2 20.0 20.064010 catB 14 3 10.0 20.064010 catB 19 4 NaN 20.064010 catB 26 5 39.0 23.000000 catDD 35 6 10.0 23.000000 NaN 46 ----- ----- WOEEncoder cat_int cat_text cat_text_copy y_copy 0 10.0 0.980829 catA 10 1 10.0 0.980829 catA 11 2 20.0 -0.405465 catB 14 3 10.0 -0.405465 catB 19 4 NaN -0.405465 catB 26 5 39.0 0.000000 catDD 35 6 10.0 0.000000 NaN 46 ----- A propos du LeaveOneOut ----------------------- Cet encoder ne produit qu’une seule colonne. Dans le cas d’une régression linéaire, la valeur est la moyenne de la cible :math:`y` sur l’ensemble des lignes associées à cette catégorie. On reprend un exemple déjà utilisé. .. code:: ipython3 perm = numpy.random.permutation(list(range(10))) n = 1000 X1 = numpy.random.randint(0, 10, (n,1)) X2 = numpy.array([perm[i] for i in X1]) eps = numpy.random.random((n, 1)) Y = X1 * (-10) - 7 + eps data = pandas.DataFrame(dict(X1=X1.ravel(), X2=X2.ravel(), Y=Y.ravel())) data.head() .. raw:: html
X1 X2 Y
0 7 3 -76.045854
1 3 4 -36.468473
2 2 7 -26.752507
3 0 2 -6.294129
4 4 0 -46.812676
.. code:: ipython3 from sklearn.model_selection import train_test_split data_train, data_test = train_test_split(data) .. code:: ipython3 data_train = data_train.reset_index(drop=True) .. code:: ipython3 from category_encoders import LeaveOneOutEncoder le = LeaveOneOutEncoder(cols=['X2']) X = data_train.drop('Y', axis=1) le.fit(X, data_train.Y) data_train2 = le.transform(X) data_train2.head() .. raw:: html
X1 X2
0 9 -96.513580
1 9 -96.513580
2 8 -86.548834
3 7 -76.475048
4 9 -96.513580
.. code:: ipython3 data_train2 = data_train2.reset_index(drop=True) .. code:: ipython3 from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(data_train2[["X2"]], data_train['Y']) .. parsed-literal:: LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False) .. code:: ipython3 data_test = data_test.reset_index(drop=True) .. code:: ipython3 from sklearn.metrics import r2_score data_test2 = le.transform(data_test.drop("Y", axis=1)) r2_score(data_test['Y'], model.predict(data_test2[['X2']])) .. parsed-literal:: 0.9999033020848935 Le coefficient :math:`R^2` est proche de 1, la régression est quasi parfaite. L’encodeur ``LeaveONeOutEncode`` utilise la cible pour maximiser le coefficient :math:`R^2` si le modèle utilisé pour prédire est une régression linéaire. Vous trouverez une idée de la démonstration dans cet énoncé : `ENSAE TD noté 2016 `__.