【问题标题】:How to speed LabelEncoder up recoding a categorical variable into integers如何加快 LabelEncoder 将分类变量重新编码为整数的速度
【发布时间】:2017-01-21 08:37:42
【问题描述】:

我有一个大 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 RAM 的计算机上重新编码步骤的更多时间

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 来尝试转换这个相对较小的数据帧,以至于它崩溃了。

我还使用具有 1000 万行的较大数据集对 LabelEncoder 进行了计时。它运行时不会崩溃,但仅拟合线就需要 50 秒。 df.apply(le.transform) 步骤大约耗时 80 秒。

我该怎么做:

  1. 获得的结果大致与 maxymoo 的答案速度和 LabelEncoder 的内存使用量大致相同,但当数据帧有两列时,它会给出正确的答案。
  2. 存储映射,以便我可以将它用于不同的数据(就像 LabelEncoder 允许我做的那样)?

【问题讨论】:

  • 您的 csv - 行和列有多大?你有多少个 le.class?
  • @wwii 大约 1000 万行和 100 万个 le.classes。
  • groupby.... 完成了什么?
  • @wwii 它计算重复。
  • 您使用的是哪个版本的 Python?

标签: python pandas scikit-learn


【解决方案1】:

看起来使用 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 版本中看到它,因为维护者不希望将 pandas 作为依赖)

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

【讨论】:

  • 谢谢,尽管我确实需要在两列之间使用一致的编码,而且我不会在两列中都表示每个值。
  • 我需要能够将此映射应用到另一个数据框。有没有办法像 labelencoder 那样存储它?
【解决方案2】:

我用 DataFrame 试过这个:

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

看起来像这样:

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)

所以,1000 万行。在本地,我的时间安排是:

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

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

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

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

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

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

编辑:根据第二条评论:

In [45]: %timeit uniques = np.sort(pd.unique(df.values.ravel()))
1 loops, best of 3: 933 ms per loop

In [46]: %timeit  dfc = df.apply(lambda x: x.astype('category', categories=uniques))
1 loops, best of 3: 1.35 s per loop

【讨论】:

  • 这是一个很好的答案,我不知道你可以映射这样的字典,非常好的技巧。顺便说一句,更实用的语法是df.apply(lambda c: c.map(d))(尽管由于某种原因这会慢 0.5 秒)
  • 谢谢。请问uniques = np.sort(pd.unique(df.values.ravel())) df.apply(lambda x: x.astype('category', categories=uniques))也可以吗?
  • 感谢您安排时间我并不是说您运行的代码。我的意思是我在评论中粘贴的两行。您应该不需要其他任何东西(例如堆栈和取消堆栈)?
【解决方案3】:

我想指出一个可以很好地为许多读者服务的替代解决方案。虽然我更喜欢拥有一组已知的 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 分钟才能从训练中学习编码(100 万行和 600 个特征)并将其重新应用于多个测试集,同时还要处理新值。哈希方法将它缩短到几分钟,我不需要保存任何模型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-14
    • 1970-01-01
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多