Pandas 一种热编码:将频率较低的类别捆绑在一起

Pandas One hot encoding: Bundling together less frequent categories

我正在对具有大约 18 种不同类型值的分类列进行热编码。我只想为那些出现超过某个阈值(比方说 1%)的值创建新列,并创建另一个名为 other values 的列,如果值不是那些频繁值,则该列具有 1。

我正在使用 Pandas 和 Sci-kit 学习。我探索了 pandas get_dummies 和 sci-kit learn 的 one hot encoder,但无法弄清楚如何将频率较低的值捆绑到一列中。

像下面这样的东西怎么样:

创建数据框

df = pd.DataFrame(data=list('abbgcca'), columns=['x'])
df

    x
0   a
1   b
2   b
3   g
4   c 
5   c
6   a

替换出现频率低于给定阈值的值。我将创建该列的副本,这样我就不会修改原始数据框。第一步是创建 value_counts 的字典,然后用这些计数替换实际值,以便将它们与阈值进行比较。将低于该阈值的值设置为 'other values',然后使用 pd.get_dummies 获取虚拟变量

#set the threshold for example 20%
thresh = 0.2
x = df.x.copy()
#replace any values present less than the threshold with 'other values'
x[x.replace(x.value_counts().to_dict()) < len(x)*thresh] = 'other values'
#get dummies
pd.get_dummies(x)

        a       b       c       other values
    0   1.0     0.0     0.0     0.0
    1   0.0     1.0     0.0     0.0
    2   0.0     1.0     0.0     0.0
    3   0.0     0.0     0.0     1.0
    4   0.0     0.0     1.0     0.0
    5   0.0     0.0     1.0     0.0
    6   1.0     0.0     0.0     0.0

或者您可以使用 Counter 它可能更干净一些

from collections import Counter
x[x.replace(Counter(x)) < len(x)*thresh] = 'other values'

计划

  • pd.get_dummies 正常进行一次热编码
  • sum() < threshold 标识聚合的列
    • 我使用 pd.value_counts 和参数 normalize=True 来获得出现的百分比。
  • join

def hot_mess2(s, thresh):
    d = pd.get_dummies(s)
    f = pd.value_counts(s, sort=False, normalize=True) < thresh
    if f.sum() == 0:
        return d
    else:
        return d.loc[:, ~f].join(d.loc[:, f].sum(1).rename('other'))

考虑 pd.Series s

s = pd.Series(np.repeat(list('abcdef'), range(1, 7)))

s

0     a
1     b
2     b
3     c
4     c
5     c
6     d
7     d
8     d
9     d
10    e
11    e
12    e
13    e
14    e
15    f
16    f
17    f
18    f
19    f
20    f
dtype: object

hot_mess(s, 0)

    a  b  c  d  e  f
0   1  0  0  0  0  0
1   0  1  0  0  0  0
2   0  1  0  0  0  0
3   0  0  1  0  0  0
4   0  0  1  0  0  0
5   0  0  1  0  0  0
6   0  0  0  1  0  0
7   0  0  0  1  0  0
8   0  0  0  1  0  0
9   0  0  0  1  0  0
10  0  0  0  0  1  0
11  0  0  0  0  1  0
12  0  0  0  0  1  0
13  0  0  0  0  1  0
14  0  0  0  0  1  0
15  0  0  0  0  0  1
16  0  0  0  0  0  1
17  0  0  0  0  0  1
18  0  0  0  0  0  1
19  0  0  0  0  0  1
20  0  0  0  0  0  1

hot_mess(s, .1)

    c  d  e  f  other
0   0  0  0  0      1
1   0  0  0  0      1
2   0  0  0  0      1
3   1  0  0  0      0
4   1  0  0  0      0
5   1  0  0  0      0
6   0  1  0  0      0
7   0  1  0  0      0
8   0  1  0  0      0
9   0  1  0  0      0
10  0  0  1  0      0
11  0  0  1  0      0
12  0  0  1  0      0
13  0  0  1  0      0
14  0  0  1  0      0
15  0  0  0  1      0
16  0  0  0  1      0
17  0  0  0  1      0
18  0  0  0  1      0
19  0  0  0  1      0
20  0  0  0  1      0
pip install siuba 
#( in python or anaconda prompth shell)

#use library as:
from siuba.dply.forcats import fct_lump, fct_reorder 

#just like fct_lump of R :

df['Your_column'] = fct_lump(df['Your_column'], n= 10)

df['Your_column'].value_counts() # check your levels

#it reduces the level to 10, lumps all the others as 'Other'

R 有一个很好的功能 fct_lump 用于此目的,现在它被复制到 python,只需您 select 要保留的级别数和所有其他级别将是捆绑为 'others' .

改进版:

  • 以前的解决方案在数据帧时不能很好地扩展 很大。

  • 当您只想对一列执行 one-hot 编码并且您的原始数据帧有多于一列时,情况也会变得复杂。

这是一个更通用、可扩展(更快)的解决方案。

用两列一百万行的例子df来说明:

import pandas as pd
import string
df = pd.DataFrame(
    {'1st': [random.sample(["orange", "apple", "banana"], k=1)[0] for i in range(1000000)],\
     '2nd': [random.sample(list(string.ascii_lowercase), k=1)[0] for i in range(1000000)]}
    )

前 10 行 df.head(10) 是:

    1st     2nd
0   banana  t
1   orange  t
2   banana  m
3   banana  g
4   banana  g
5   orange  a
6   apple   x
7   orange  s
8   orange  d
9   apple   u

统计数据df['2nd'].value_counts()是:

s    39004
k    38726
n    38720
b    38699
t    38688
p    38646
u    38638
w    38611
y    38587
o    38576
q    38559
x    38558
r    38545
i    38497
h    38429
v    38385
m    38369
j    38278
f    38262
e    38241
a    38241
l    38236
g    38210
z    38202
c    38058
d    38035
第 1 步:定义阈值
threshold = 38500
第 2 步:关注您要对其进行 one-hot 编码的列,并将频率低于阈值的条目更改为 others
%timeit df.loc[df['2nd'].value_counts()[df['2nd']].values < threshold, '2nd'] = "others"

所用时间为 206 ms ± 346 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

第 3 步:照常应用 one-hot 编码
df = pd.get_dummies(df, columns = ['2nd'], prefix='', prefix_sep='')

前10行one-hot编码后df.head(10)变为

    1st     b   k   n   o   others  p   q   r   s   t   u   w   x   y
0   banana  0   0   0   0   0       0   0   0   0   1   0   0   0   0
1   orange  0   0   0   0   0       0   0   0   0   1   0   0   0   0
2   banana  0   0   0   0   1       0   0   0   0   0   0   0   0   0
3   banana  0   0   0   0   1       0   0   0   0   0   0   0   0   0
4   banana  0   0   0   0   1       0   0   0   0   0   0   0   0   0
5   orange  0   0   0   0   1       0   0   0   0   0   0   0   0   0
6   apple   0   0   0   0   0       0   0   0   0   0   0   0   1   0
7   orange  0   0   0   0   0       0   0   0   1   0   0   0   0   0
8   orange  0   0   0   0   1       0   0   0   0   0   0   0   0   0
9   apple   0   0   0   0   0       0   0   0   0   0   1   0   0   0
第 4 步(可选):如果您希望 others 成为 df 的最后一列,您可以尝试:
df = df[[col for col in df.columns if col != 'others'] + ['others']]

这会将 others 移动到最后一列。

    1st     b   k   n   o   p   q   r   s   t   u   w   x   y   others
0   banana  0   0   0   0   0   0   0   0   1   0   0   0   0   0
1   orange  0   0   0   0   0   0   0   0   1   0   0   0   0   0
2   banana  0   0   0   0   0   0   0   0   0   0   0   0   0   1
3   banana  0   0   0   0   0   0   0   0   0   0   0   0   0   1
4   banana  0   0   0   0   0   0   0   0   0   0   0   0   0   1
5   orange  0   0   0   0   0   0   0   0   0   0   0   0   0   1
6   apple   0   0   0   0   0   0   0   0   0   0   0   1   0   0
7   orange  0   0   0   0   0   0   0   1   0   0   0   0   0   0
8   orange  0   0   0   0   0   0   0   0   0   0   0   0   0   1
9   apple   0   0   0   0   0   0   0   0   0   1   0   0   0   0