Hashing et catégories
Links: notebook
, html, PDF
, python
, slides, GitHub
Le hashing est utilise lorsque le nombre de catégories est trop grand.
On construit un jeu très simple avec deux catégories, une entière, une
au format texte, distributé selon une loi
multinomial.
import pandas
import numpy
prob = numpy.ones(100) / 100
rnd = numpy.random.binomial(100, 0.5, 10000)
df = pandas.DataFrame(dict(cat_int=rnd, cat_text=['cat%d' % i for i in rnd]))
df.head()
|
cat_int |
cat_text |
0 |
51 |
cat51 |
1 |
51 |
cat51 |
2 |
53 |
cat53 |
3 |
49 |
cat49 |
4 |
56 |
cat56 |
dfc = df.drop('cat_int', axis=1).copy()
dfc['count'] = 1
gr = dfc.groupby('cat_text').sum().sort_values('count', ascending=False)
pandas.concat([gr.head(), gr.tail()])
|
count |
cat_text |
|
cat48 |
780 |
cat49 |
771 |
cat50 |
770 |
cat52 |
755 |
cat51 |
746 |
cat67 |
3 |
cat31 |
2 |
cat32 |
1 |
cat33 |
1 |
cat69 |
1 |
On utilise le module Category
Encoders
implémente la classe
Hashing
qui applique une fonction de hash pour retourner une séquence de
nombre binaires. Il y a 100 catégories distinctes. Il faut au pire 8
colonnes binaires pour représenter l’information. On vérifie avec un
encoder binaire qui encode chaque catégorie de façon binaire.
from category_encoders import BinaryEncoder
BinaryEncoder(cols=['cat_text']).fit_transform(df).head()
|
cat_text_0 |
cat_text_1 |
cat_text_2 |
cat_text_3 |
cat_text_4 |
cat_text_5 |
cat_int |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
51 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
51 |
2 |
0 |
0 |
0 |
0 |
0 |
1 |
53 |
3 |
0 |
0 |
0 |
0 |
1 |
0 |
49 |
4 |
0 |
0 |
0 |
0 |
1 |
1 |
56 |
from category_encoders import HashingEncoder
HashingEncoder(cols=['cat_text']).fit_transform(df).head()
|
col_0 |
col_1 |
col_2 |
col_3 |
col_4 |
col_5 |
col_6 |
col_7 |
cat_int |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
51 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
51 |
2 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
53 |
3 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
49 |
4 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
56 |
On peut réduire le nombre de colonnes. L’information est compressée mais
pas de façon réversible. L’information est codée sur 4 colonnes.
res = HashingEncoder(cols=['cat_text'], n_components=4, hash_method="sha256").fit_transform(df)
res.head()
|
col_0 |
col_1 |
col_2 |
col_3 |
cat_int |
0 |
0 |
1 |
0 |
0 |
51 |
1 |
0 |
1 |
0 |
0 |
51 |
2 |
0 |
0 |
0 |
1 |
53 |
3 |
0 |
0 |
0 |
1 |
49 |
4 |
0 |
1 |
0 |
0 |
56 |
res['col_int'] = res.col_0 + 2 * res.col_1 + 4 * res.col_2 + 8 * res.col_3
res.head()
|
col_0 |
col_1 |
col_2 |
col_3 |
cat_int |
col_int |
0 |
0 |
1 |
0 |
0 |
51 |
2 |
1 |
0 |
1 |
0 |
0 |
51 |
2 |
2 |
0 |
0 |
0 |
1 |
53 |
8 |
3 |
0 |
0 |
0 |
1 |
49 |
8 |
4 |
0 |
1 |
0 |
0 |
56 |
2 |
res[['col_int', 'cat_int']].groupby('col_int').count()
|
cat_int |
col_int |
|
1 |
3913 |
2 |
2021 |
4 |
2 |
8 |
4064 |
Pas tout-à-fait ce à quoi je m’attendais. On peut aussi utiliser quelque
chose comme ceci : hash + encoding binaire.
dfc = df.copy()
dfc['cat_hash'] = df["cat_text"].apply(lambda x: abs(hash(x)) % (2**4))
res = BinaryEncoder(cols=['cat_hash']).fit_transform(dfc)
res.head()
|
cat_hash_0 |
cat_hash_1 |
cat_hash_2 |
cat_hash_3 |
cat_int |
cat_text |
0 |
0 |
0 |
0 |
0 |
51 |
cat51 |
1 |
0 |
0 |
0 |
0 |
51 |
cat51 |
2 |
0 |
0 |
0 |
1 |
53 |
cat53 |
3 |
0 |
0 |
1 |
0 |
49 |
cat49 |
4 |
0 |
0 |
1 |
1 |
56 |
cat56 |
res['col_int'] = res.cat_hash_0 + 2 * res.cat_hash_1 + 4 * res.cat_hash_2 + 8 * res.cat_hash_3
res.head()
|
cat_hash_0 |
cat_hash_1 |
cat_hash_2 |
cat_hash_3 |
cat_int |
cat_text |
col_int |
0 |
0 |
0 |
0 |
0 |
51 |
cat51 |
0 |
1 |
0 |
0 |
0 |
0 |
51 |
cat51 |
0 |
2 |
0 |
0 |
0 |
1 |
53 |
cat53 |
8 |
3 |
0 |
0 |
1 |
0 |
49 |
cat49 |
4 |
4 |
0 |
0 |
1 |
1 |
56 |
cat56 |
12 |
res[['col_int', 'cat_int', 'cat_text']].groupby(['col_int', 'cat_text'], as_index=False) \
.count().pivot('cat_text', 'col_int', 'cat_int').astype(str).replace("nan", "")
col_int |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
cat_text |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cat31 |
|
|
|
|
|
|
2.0 |
|
|
|
|
|
|
|
cat32 |
|
|
|
|
|
|
|
|
|
|
|
|
1.0 |
|
cat33 |
|
|
1.0 |
|
|
|
|
|
|
|
|
|
|
|
cat34 |
|
|
|
|
|
|
|
|
|
|
|
|
6.0 |
|
cat35 |
|
|
|
|
|
|
|
|
|
|
9.0 |
|
|
|
cat36 |
|
|
|
|
13.0 |
|
|
|
|
|
|
|
|
|
cat37 |
|
|
|
|
|
|
|
|
|
|
|
|
23.0 |
|
cat38 |
|
|
|
|
|
|
|
|
|
45.0 |
|
|
|
|
cat39 |
|
|
|
|
|
|
|
|
|
65.0 |
|
|
|
|
cat40 |
|
|
|
|
101.0 |
|
|
|
|
|
|
|
|
|
cat41 |
168.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
cat42 |
|
|
|
|
196.0 |
|
|
|
|
|
|
|
|
|
cat43 |
|
|
|
|
|
|
314.0 |
|
|
|
|
|
|
|
cat44 |
|
|
|
|
|
|
|
|
373.0 |
|
|
|
|
|
cat45 |
|
|
486.0 |
|
|
|
|
|
|
|
|
|
|
|
cat46 |
583.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
cat47 |
|
|
691.0 |
|
|
|
|
|
|
|
|
|
|
|
cat48 |
|
780.0 |
|
|
|
|
|
|
|
|
|
|
|
|
cat49 |
|
|
|
|
771.0 |
|
|
|
|
|
|
|
|
|
cat50 |
770.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
cat51 |
746.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
cat52 |
|
|
|
|
|
|
|
|
|
755.0 |
|
|
|
|
cat53 |
|
|
|
|
|
|
|
668.0 |
|
|
|
|
|
|
cat54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
598.0 |
cat55 |
473.0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
cat56 |
|
|
|
|
|
|
|
|
|
|
|
404.0 |
|
|
cat57 |
|
303.0 |
|
|
|
|
|
|
|
|
|
|
|
|
cat58 |
|
|
|
|
|
220.0 |
|
|
|
|
|
|
|
|
cat59 |
|
|
162.0 |
|
|
|
|
|
|
|
|
|
|
|
cat60 |
|
|
|
|
|
|
|
105.0 |
|
|
|
|
|
|
cat61 |
|
|
|
|
|
|
|
|
|
68.0 |
|
|
|
|
cat62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
43.0 |
cat63 |
|
|
|
|
23.0 |
|
|
|
|
|
|
|
|
|
cat64 |
|
|
|
|
17.0 |
|
|
|
|
|
|
|
|
|
cat65 |
|
|
|
|
|
|
|
|
|
9.0 |
|
|
|
|
cat66 |
|
|
|
4.0 |
|
|
|
|
|
|
|
|
|
|
cat67 |
|
|
3.0 |
|
|
|
|
|
|
|
|
|
|
|
cat69 |
|
|
|
1.0 |
|
|
|
|
|
|
|
|
|
|
Ce qu’on espère : que deux classes sur-représentées ne soient pas
encodées par la même valeur, ce qui est le cas ici. Il faudra donc
augmenter la taille du hash (16 ici).