Traitement des catégories

Links: notebook, html, PDF, python, slides, GitHub

Ce notebook présente différentes options pour gérer les catégories au format entier ou texte.

from jyquickhelper import add_notebook_menu
add_notebook_menu()
%matplotlib inline

On construit un jeu très simple avec deux catégories, une entière, une au format texte.

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

Transformations d’une catégorie

Les premières opérations consiste à convertir une catégorie au format entier ou au format texte en un entier. Les valeurs manquantes ne sont toujours traitées de la même façon.

from sklearn.preprocessing import LabelEncoder
LabelEncoder().fit_transform(df['cat_int'])
array([0, 1, 0, 2, 0, 0, 3], dtype=int64)
try:
    LabelEncoder().fit_transform(df['cat_text'])
except Exception as e:
    print(e)
LabelEncoder().fit_transform(df['cat_text'].dropna())
'<' not supported between instances of 'float' and 'str'
array([0, 1, 0, 2, 1, 1], dtype=int64)

On peut récupérer l’association entre catégorie et catégorie codée.

le = LabelEncoder()
le.fit(df['cat_text'].dropna())
le.classes_
array(['catA', 'catB', 'catDD'], dtype=object)

La seconde opération permet de transformer une catégorie au format entier en plusieurs colonnes au format binaire.

from sklearn.preprocessing import OneHotEncoder
try:
    OneHotEncoder().fit_transform(df[['cat_int']]).todense()
except Exception as e:
    print(e)
OneHotEncoder().fit_transform(df[['cat_int']].dropna()).todense()
Input contains NaN, infinity or a value too large for dtype('float64').
matrix([[1., 0., 0.],
        [0., 1., 0.],
        [1., 0., 0.],
        [0., 0., 1.],
        [1., 0., 0.],
        [1., 0., 0.]])
from sklearn.preprocessing import LabelBinarizer
try:
    LabelBinarizer().fit_transform(df[['cat_int']])
except Exception as e:
    print(e)
LabelBinarizer().fit_transform(df[['cat_int']].dropna())
Unknown label type: (   cat_int
0     10.0
1     20.0
2     10.0
3     39.0
4     10.0
5     10.0
6      NaN,)
array([[1, 0, 0],
       [0, 1, 0],
       [1, 0, 0],
       [0, 0, 1],
       [1, 0, 0],
       [1, 0, 0]])
from sklearn.preprocessing import LabelBinarizer
try:
    LabelBinarizer().fit_transform(df[['cat_text']])
except Exception as e:
    print(e)
LabelBinarizer().fit_transform(df[['cat_text']].dropna())
'<' not supported between instances of 'float' and 'str'
array([[1, 0, 0],
       [0, 1, 0],
       [1, 0, 0],
       [0, 0, 1],
       [0, 1, 0],
       [0, 1, 0]])

D’autres options qui ne fonctionnent pas tout à fait de la même manière en terme d’implémentation.

from sklearn.feature_extraction import DictVectorizer
DictVectorizer().fit_transform(df.to_dict('records')).todense()
matrix([[10.,  0.,  1.,  0.,  0.],
        [20.,  0.,  0.,  1.,  0.],
        [10.,  0.,  1.,  0.,  0.],
        [39.,  0.,  0.,  0.,  1.],
        [10.,  0.,  0.,  1.,  0.],
        [10., nan,  0.,  0.,  0.],
        [nan,  0.,  0.,  1.,  0.]])
from sklearn.feature_extraction import FeatureHasher
FeatureHasher(n_features=5).fit_transform(df.to_dict('records')).todense()
matrix([[ 0.,  0.,  0.,  1., 10.],
        [ 1.,  0.,  0.,  0., 20.],
        [ 0.,  0.,  0.,  1., 10.],
        [ 0., -1.,  0.,  0., 39.],
        [ 1.,  0.,  0.,  0., 10.],
        [ 0.,  0.,  0.,  0., nan],
        [ 1.,  0.,  0.,  0., nan]])

Méthodes à gradient et ensemblistes

On construit un simple jeu de données pour une régression linéaire, Y = -10 X - 7 puis on cale une régression linéaire avec Y \sim \alpha X_2 + \epsilonX_2 est une permutation de X. Le lien est en quelque sorte brisé.

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()
X1 X2 Y
0 4 7 -46.420964
1 6 9 -66.321194
2 3 5 -36.001053
3 0 2 -6.802070
4 8 3 -86.044988
from sklearn.model_selection import train_test_split
data_train, data_test = train_test_split(data)
data_train = data_train.copy()
data_test = data_test.copy()

On transforme la catégorie :

le = LabelEncoder().fit(data_train['X2'])
data_train['X3'] = le.transform(data_train['X2'])
data_test['X3'] = le.transform(data_test['X2'])
data_train.head()
X1 X2 Y X3
943 1 8 -16.020453 8
686 2 0 -26.664813 0
312 9 6 -96.833801 6
861 1 8 -16.090882 8
784 4 7 -46.693131 7
data_train.corr()
X1 X2 Y X3
X1 1.000000 0.145122 -0.999946 0.145122
X2 0.145122 1.000000 -0.144419 1.000000
Y -0.999946 -0.144419 1.000000 -0.144419
X3 0.145122 1.000000 -0.144419 1.000000

On cale une régression linéaire de Y sur X_3 la catégorie encodée :

from sklearn.linear_model import LinearRegression
clr = LinearRegression()
clr.fit(data_train[['X3']], data_train['Y'])
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
from sklearn.metrics import r2_score
r2_score(data_test['Y'], clr.predict(data_test[['X3']]))
0.009306055721419959

Autrement dit, elle n’a rien appris. On cale un arbre de décision :

from sklearn.tree import DecisionTreeRegressor
clr = DecisionTreeRegressor()
clr.fit(data_train[['X3']], data_train['Y'])
DecisionTreeRegressor(criterion='mse', max_depth=None, max_features=None,
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None, min_samples_leaf=1,
           min_samples_split=2, min_weight_fraction_leaf=0.0,
           presort=False, random_state=None, splitter='best')
r2_score(data_test['Y'], clr.predict(data_test[['X3']]))
0.9998898230391275

L’arbre de décision a saisi la permutation alors que la régression linéaire n’a pas fonctionné. La régression linéaire n’est pas estimée à l’aide d’une méthode à base de gradient mais elle possède les mêmes contraintes, il est préférable que la cible Y soit une fonction le plus possible monotone de X. Avec une colonne par modalité de la catégorie, le résultat est tout autre.

one = OneHotEncoder().fit(data_train[['X2']])
feat = one.transform(data_train[['X2']])
feat[:5].todense()
matrix([[0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
        [1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.]])
clr = LinearRegression()
clr.fit(feat, data_train['Y'])
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
r2_score(data_test['Y'], clr.predict(one.transform(data_test[['X2']])))
0.9998898230391275