如何加速LabelEncoder将分类变量重新编码为整数

时间:2016-09-13 16:48:30

标签: python pandas scikit-learn

我有一个大的csv,每行有两个字符串:

g,k
a,h
c,i
j,e
d,i
i,h
b,b
d,d
i,a
d,h

我读了前两列并将字符串重新编码为整数,如下所示:

import pandas as pd
df = pd.read_csv("test.csv", usecols=[0,1], prefix="ID_", header=None)
from sklearn.preprocessing import LabelEncoder

# Initialize the LabelEncoder.
le = LabelEncoder()
le.fit(df.values.flat)

# Convert to digits.
df = df.apply(le.transform)

此代码来自https://stackoverflow.com/a/39419342/2179021

代码效果很好但是当df很大时代码很慢。我为每一步计时,结果让我感到惊讶。

  • pd.read_csv大约需要40秒。
  • le.fit(df.values.flat)大约需要30秒
  • df = df.apply(le.transform)大约需要250秒。

有没有办法加快最后一步?感觉它应该是这一切中最快的一步!

在具有4GB内存的计算机上重新编码步骤的更多时间

maxymoo的答案很快,但没有给出正确的答案。以问题顶部的示例csv为例,将其转换为:

   0  1
0  4  6
1  0  4
2  2  5
3  6  3
4  3  5
5  5  4
6  1  1
7  3  2
8  5  0
9  3  4

请注意,'d'在第一列中映射为3,在第二列中映射为2。

我尝试了https://stackoverflow.com/a/39356398/2179021的解决方案并获得以下内容。

df = pd.DataFrame({'ID_0':np.random.randint(0,1000,1000000), 'ID_1':np.random.randint(0,1000,1000000)}).astype(str)
df.info()
memory usage: 7.6MB
%timeit x = (df.stack().astype('category').cat.rename_categories(np.arange(len(df.stack().unique()))).unstack())
1 loops, best of 3: 1.7 s per loop

然后我将数据帧大小增加了10倍。

df = pd.DataFrame({'ID_0':np.random.randint(0,1000,10000000), 'ID_1':np.random.randint(0,1000,10000000)}).astype(str) 
df.info()
memory usage: 76.3+ MB
%timeit x = (df.stack().astype('category').cat.rename_categories(np.arange(len(df.stack().unique()))).unstack())
MemoryError                               Traceback (most recent call last)

这种方法似乎使用了大量的RAM来尝试翻译它崩溃的相对较小的数据帧。

我还将LabelEncoder定时为具有1000万行的较大数据集。它运行没有崩溃,但单独的拟合线需要50秒。 df.apply(le.transform)步骤大约需要80秒。

我怎么能:

  1. 大致了解maxymoo的答案速度和LabelEncoder的大致内存使用情况,但是当数据框有两列时,它给出了正确的答案。
  2. 存储映射,以便我可以将其重用于不同的数据(就像LabelEncoder允许我这样做的那样)?

3 个答案:

答案 0 :(得分:4)

使用pandas category数据类型看起来要快得多;在内部,它使用哈希表,而LabelEncoder使用排序搜索:

In [87]: df = pd.DataFrame({'ID_0':np.random.randint(0,1000,1000000), 
                            'ID_1':np.random.randint(0,1000,1000000)}).astype(str)

In [88]: le.fit(df.values.flat) 
         %time x = df.apply(le.transform)
CPU times: user 6.28 s, sys: 48.9 ms, total: 6.33 s
Wall time: 6.37 s

In [89]: %time x = df.apply(lambda x: x.astype('category').cat.codes)
CPU times: user 301 ms, sys: 28.6 ms, total: 330 ms
Wall time: 331 ms

编辑:这是一个你可以使用的自定义变换器类(你可能在官方的scikit-learn版本中看不到这个,因为维护人员不想这样做将熊猫作为依赖者)

import pandas as pd
from pandas.core.nanops import unique1d
from sklearn.base import BaseEstimator, TransformerMixin

class PandasLabelEncoder(BaseEstimator, TransformerMixin):
    def fit(self, y):
        self.classes_ = unique1d(y)
        return self

    def transform(self, y):
        s = pd.Series(y).astype('category', categories=self.classes_)
        return s.cat.codes

答案 1 :(得分:3)

我尝试使用DataFrame:

 <location path="elmah.axd">
    <system.web>
      <authorization>
        <allow roles="Admin,Role1, etc..." />
        <deny users="*" />
      </authorization>
    </system.web>
  </location>

看起来像这样:

In [xxx]: import string
In [xxx]: letters = np.array([c for c in string.ascii_lowercase])
In [249]: df = pd.DataFrame({'ID_0': np.random.choice(letters, 10000000), 'ID_1':np.random.choice(letters, 10000000)})

所以,1000万行。在当地,我的时间是:

In [261]: df.head()
Out[261]: 
  ID_0 ID_1
0    v    z
1    i    i
2    d    n
3    z    r
4    x    x

In [262]: df.shape
Out[262]: (10000000, 2)

然后我制作了一个将字母映射到数字的dict并使用了pandas.Series.map:

In [257]: % timeit le.fit(df.values.flat)
1 loops, best of 3: 17.2 s per loop

In [258]: % timeit df2 = df.apply(le.transform)
1 loops, best of 3: 30.2 s per loop

所以这可能是一个选择。 dict只需要拥有数据中出现的所有值。

编辑:OP询问我对第二个选项的时间,包括类别。这就是我得到的:

In [248]: letters = np.array([l for l in string.ascii_lowercase])
In [263]: d = dict(zip(letters, range(26)))

In [273]: %timeit for c in df.columns: df[c] = df[c].map(d)
1 loops, best of 3: 1.12 s per loop

In [274]: df.head()
Out[274]: 
   ID_0  ID_1
0    21    25
1     8     8
2     3    13
3    25    17
4    23    23

编辑:根据第2条评论:

In [40]: %timeit   x=df.stack().astype('category').cat.rename_categories(np.arange(len(df.stack().unique()))).unstack()
1 loops, best of 3: 13.5 s per loop

答案 2 :(得分:0)

我想指出一个替代解决方案,该解决方案应该很好地服务于许多读者。尽管我更喜欢拥有一组已知的ID,但如果严格地是单向重新映射,则不一定总是需要。

代替

df[c] = df[c].apply(le.transform)

dict_table = {val: i for i, val in enumerate(uniques)}
df[c] = df[c].map(dict_table)

或(sklearn source code中的检出_encode()和_encode_python(),我认为它平均比提到的其他方法要快)

df[c] = np.array([dict_table[v] for v in df[c].values])

您可以代替

df[c] = df[c].apply(hash)

优点:速度更快,所需内存更少,无需训练,哈希可以减少为较小的表示形式(通过转换dtype产生更多的冲突)。

缺点:提供时髦的数字,可能会发生冲突(不保证是唯一的),不能保证函数不会随着新版本的python改变

请注意,安全哈希函数将减少冲突,但会降低速度。

使用时间示例:您的字符串有些长,大多数情况下都是唯一的,数据集非常庞大。最重要的是,即使它可能是模型预测中的噪声源,您也不必担心罕有的哈希冲突。

我已经尝试了上述所有方法,我的工作量大约需要90分钟才能从训练中学习编码(1M行和600个功能),并将其重新应用于多个测试集,同时还要处理新值。哈希方法将其减少到几分钟,而且我不需要保存任何模型。