【问题标题】:Converting one int to multiple bool columns in pandas在熊猫中将一个 int 转换为多个 bool 列
【发布时间】:2017-09-30 00:22:47
【问题描述】:

背景

我得到了一个带有整数的数据框。这些整数表示该行存在或不存在的一系列特征。

我希望这些特征在我的数据框中命名为列。

问题

我当前的解决方案在内存中爆炸并且速度非常慢。如何提高这个的内存效率?

import pandas as pd
df = pd.DataFrame({'some_int':range(5)})
df['some_int'].astype(int).apply(bin).str[2:].str.zfill(4).apply(list).apply(pd.Series).rename(columns=dict(zip(range(4), ["f1", "f2", "f3", "f4"])))

  f1 f2 f3 f4
0  0  0  0  0
1  0  0  0  1
2  0  0  1  0
3  0  0  1  1
4  0  1  0  0

似乎是.apply(pd.Series) 正在减慢速度。在我添加这个之前,其他一切都很快。

我不能跳过它,因为简单的列表不会构成数据框。

【问题讨论】:

    标签: python performance pandas numpy


    【解决方案1】:

    我认为你需要:

    a = pd.DataFrame(df['some_int'].astype(int)
                                   .apply(bin)
                                   .str[2:]
                                   .str.zfill(4)
                                   .apply(list).values.tolist(), columns=["f1","f2","f3","f4"])
    print (a)
      f1 f2 f3 f4
    0  0  0  0  0
    1  0  0  0  1
    2  0  0  1  0
    3  0  0  1  1
    4  0  1  0  0
    

    另一种解决方案,谢谢Jon Clements and ayhan:

    a = pd.DataFrame(df['some_int'].apply(lambda x: list('{:04b}'.format(x))).values.tolist(), 
                     columns=['f1', 'f2', 'f3', 'f4'])
    print (a)
      f1 f2 f3 f4
    0  0  0  0  0
    1  0  0  0  1
    2  0  0  1  0
    3  0  0  1  1
    4  0  1  0  0
    

    有点变化:

    a = pd.DataFrame([list('{:04b}'.format(x)) for x in df['some_int'].values], 
                      columns=['f1', 'f2', 'f3', 'f4'])
    print (a)
      f1 f2 f3 f4
    0  0  0  0  0
    1  0  0  0  1
    2  0  0  1  0
    3  0  0  1  1
    4  0  1  0  0
    

    时间安排

    df = pd.DataFrame({'some_int':range(100000)})
    
    In [80]: %timeit pd.DataFrame(df['some_int'].astype(int).apply(bin).str[2:].str.zfill(20).apply(list).values.tolist())
    1 loop, best of 3: 231 ms per loop
    
    In [81]: %timeit pd.DataFrame([list('{:020b}'.format(x)) for x in df['some_int'].values])
    1 loop, best of 3: 232 ms per loop
    
    In [82]: %timeit pd.DataFrame(df['some_int'].apply(lambda x: list('{:020b}'.format(x))).values.tolist())
    1 loop, best of 3: 222 ms per loop
    
    In [83]: %timeit pd.DataFrame([list(np.binary_repr(x, width=20)) for x in df.some_int.values])
    1 loop, best of 3: 343 ms per loop
    
    In [84]: %timeit df.some_int.apply(lambda x: pd.Series(list(np.binary_repr(x, width=20))))
    1 loop, best of 3: 16.4 s per loop
    
    In [87]: %timeit pd.DataFrame( num2bin(df.some_int.values, 20))
    100 loops, best of 3: 11.4 ms per loop
    

    【讨论】:

    • 稍微“简单一点”:pd.DataFrame(df.some_int.apply('{:04b}'.format).apply(list).tolist(), columns=['f1', 'f2', 'f3', 'f4'])
    • 在我更大的数据集上试试这个
    • @JonClements 实际上是 .values 部分让它稍微快了一点。
    • @ayhan 啊,好吧...正在剪掉 astype、bin、slice、zfill 并在那里使用str.format...(如果这样做效率较低,请感到惊讶)
    • 好吧,看来我的例子太简单了,有一个隐藏的问题...OverflowError: Python int too large to convert to C long我有 22 个功能...
    【解决方案2】:

    你可以使用numpy.binary_repr方法:

    In [336]: df.some_int.apply(lambda x: pd.Series(list(np.binary_repr(x, width=4)))) \
                .add_prefix('f')
    Out[336]:
      f0 f1 f2 f3
    0  0  0  0  0
    1  0  0  0  1
    2  0  0  1  0
    3  0  0  1  1
    4  0  1  0  0
    

    In [346]: pd.DataFrame([list(np.binary_repr(x, width=4)) for x in df.some_int.values],
         ...:              columns=np.arange(1,5)) \
         ...:   .add_prefix('f')
         ...:
    Out[346]:
      f1 f2 f3 f4
    0  0  0  0  0
    1  0  0  0  1
    2  0  0  1  0
    3  0  0  1  1
    4  0  1  0  0
    

    【讨论】:

    • pd.Series 实例化是我的解决方案中的重中之重。您也在您的解决方案中执行此操作。有办法解决吗?此外,lambda 函数让我感到怀疑。不过我会试试的
    • 第二种解决方案有效。第一个仍在运行,但速度较慢
    • 第二种解决方案给出的是字符串,不是整数,也不是布尔值,而且内存很重
    • @firelynx,我相信你想使用 Divakar 的矢量化解决方案 ;-)
    • 我现在。但我正在等待给出正确的解决方案,直到明天。让每个人都有机会找出最佳解决方案
    【解决方案3】:

    这是一个矢量化的 NumPy 方法 -

    def num2bin(nums, width):
        return ((nums[:,None] & (1 << np.arange(width-1,-1,-1)))!=0).astype(int)
    

    示例运行 -

    In [70]: df
    Out[70]: 
       some_int
    0         1
    1         5
    2         3
    3         8
    4         4
    
    In [71]: pd.DataFrame( num2bin(df.some_int.values, 4), \
                        columns = [["f1", "f2", "f3", "f4"]])
    Out[71]: 
       f1  f2  f3  f4
    0   0   0   0   1
    1   0   1   0   1
    2   0   0   1   1
    3   1   0   0   0
    4   0   1   0   0
    

    说明

    1) 输入:

    In [98]: nums = np.array([1,5,3,8,4])
    
    In [99]: width = 4
    

    2) 获取 2 个动力范围号:

    In [100]: (1 << np.arange(width-1,-1,-1))
    Out[100]: array([8, 4, 2, 1])
    

    3) 将 nums 转换为 2D 数组版本,因为我们稍后希望按照 broadcasting 的规则以矢量化方式在它和 2 次幂数字之间进行元素位与运算:

    In [101]: nums[:,None]
    Out[101]: 
    array([[1],
           [5],
           [3],
           [8],
           [4]])
    
    In [102]: nums[:,None] & (1 << np.arange(width-1,-1,-1))
    Out[102]: 
    array([[0, 0, 0, 1],
         [0, 4, 0, 1],
         [0, 0, 2, 1],
         [8, 0, 0, 0],
         [0, 4, 0, 0]])
    

    为了理解位与运算,让我们考虑来自 nums 的数字 5 及其与所有 2 次幂数字 [8,4,2,1] 的位与运算:

    In [103]: 5 & 8    # 0101 & 1000
    Out[103]: 0
    
    In [104]: 5 & 4    # 0101 & 0100
    Out[104]: 4
    
    In [105]: 5 & 2    # 0101 & 0010
    Out[105]: 0
    
    In [106]: 5 & 1    # 0101 & 0001
    Out[106]: 1
    

    因此,我们看到与[8,2] 没有交集,而对于其他人,我们有非零。

    4) 在最后阶段,查找匹配项(非零),然后通过与 0 进行比较,将它们简单地转换为 1 并其余为 0,从而生成一个布尔数组,然后转换为 int dtype:

    In [107]: matches = nums[:,None] & (1 << np.arange(width-1,-1,-1))
    
    In [108]: matches!=0
    Out[108]: 
    array([[False, False, False,  True],
           [False,  True, False,  True],
           [False, False,  True,  True],
           [ True, False, False, False],
           [False,  True, False, False]], dtype=bool)
    
    In [109]: (matches!=0).astype(int)
    Out[109]: 
    array([[0, 0, 0, 1],
           [0, 1, 0, 1],
           [0, 0, 1, 1],
           [1, 0, 0, 0],
           [0, 1, 0, 0]])
    

    运行时测试

    In [58]: df = pd.DataFrame({'some_int':range(100000)})
    
    # @jezrael's soln-1
    In [59]: %timeit pd.DataFrame(df['some_int'].astype(int).apply(bin).str[2:].str.zfill(4).apply(list).values.tolist())
    1 loops, best of 3: 198 ms per loop
    
    # @jezrael's soln-2
    In [60]: %timeit pd.DataFrame([list('{:20b}'.format(x)) for x in df['some_int'].values])
    10 loops, best of 3: 154 ms per loop
    
    # @jezrael's soln-3
    In [61]: %timeit pd.DataFrame(df['some_int'].apply(lambda x: list('{:20b}'.format(x))).values.tolist())
    10 loops, best of 3: 132 ms per loop
    
    # @MaxU's soln-1
    In [62]: %timeit pd.DataFrame([list(np.binary_repr(x, width=20)) for x in df.some_int.values])
    1 loops, best of 3: 193 ms per loop
    
    # @MaxU's soln-2
    In [64]: %timeit df.some_int.apply(lambda x: pd.Series(list(np.binary_repr(x, width=20))))
    1 loops, best of 3: 11.8 s per loop
    
    # Proposed in this post
    In [65]: %timeit pd.DataFrame( num2bin(df.some_int.values, 20))
    100 loops, best of 3: 5.64 ms per loop
    

    【讨论】:

    • 神圣的通心粉,这个很快
    • ... 然后是 Divakar,所有人都张大了嘴巴,不明白 numpy 有多快 ;)
    • 这个解决方案很棒,但有点神奇。我对函数 num2bin 内部的作用只有一个模糊的概念。我希望它更清楚。 (我是一个干净的代码狂热者)
    • @firelynx 添加了一些解释。看看吧!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 2019-07-29
    • 2013-10-21
    相关资源
    最近更新 更多