将二进制 (0|1) 的 numpy 数组转换为整数或二进制字符串?

20
有没有将二进制(0|1)的numpy数组转换为整数或二进制字符串的快捷方式? 例如。
b = np.array([0,0,0,0,0,1,0,1])   
  => b is 5

np.packbits(b)

这个方法仅适用于8位值。如果numpy有9个或更多元素,它将生成2个或更多个8位值。另一个选择是返回0|1的字符串...

我目前所做的是:

    ba = bitarray()
    ba.pack(b.astype(np.bool).tostring())
    #convert from bitarray 0|1 to integer
    result = int( ba.to01(), 2 )

太丑了!!!

5个回答

24
一种方法是使用 点积2 的次幂 范围数组 -
b.dot(2**np.arange(b.size)[::-1])

示例运行 -

In [95]: b = np.array([1,0,1,0,0,0,0,0,1,0,1])

In [96]: b.dot(2**np.arange(b.size)[::-1])
Out[96]: 1285

或者,我们可以使用按位左移运算符来创建范围数组,从而获得所需的输出,如下所示:

b.dot(1 << np.arange(b.size)[::-1])

如果您对时间感兴趣 -
In [148]: b = np.random.randint(0,2,(50))

In [149]: %timeit b.dot(2**np.arange(b.size)[::-1])
100000 loops, best of 3: 13.1 µs per loop

In [150]: %timeit b.dot(1 << np.arange(b.size)[::-1])
100000 loops, best of 3: 7.92 µs per loop

反向过程

要获取二进制数组,请使用np.binary_reprnp.fromstring函数 -

In [96]: b = np.array([1,0,1,0,0,0,0,0,1,0,1])

In [97]: num = b.dot(2**np.arange(b.size)[::-1]) # integer

In [98]: np.fromstring(np.binary_repr(num), dtype='S1').astype(int)
Out[98]: array([1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1])

有没有将数据转换回原始二进制数组格式的建议? - Jonathan
@Jonathan 很好的问题!应该早点加上。已发布。 - Divakar
@Divakar 太棒了,谢谢你!!我还添加了一个 'zfill' 来确保它保持相同的长度,所以:np.fromstring(np.binary_repr(num).zfill(b.size), dtype='S1').astype(int) - Jonathan
@Jonathan 这是一个很好的建议来保持长度。谢谢。 - Divakar
可能更快的方法是使用 b.dot(1 << np.arange(b.size, 0, -1)),因为它让 numpy 处理反转。不过这可能只是微不足道的差别。 - theo-brown
显示剩余2条评论

6

我对@Divikar的优秀点积解决方案进行了扩展,通过使用向量化矩阵乘法代码,使它在我的主机上运行速度提高了约180倍。原始代码是逐行运行的,对于我的pandas数据帧中的100K行18列大概需要3分钟的运行时间。那么,下周我需要将行数从100K增加到20M,因此约10小时的运行时间不够快。首先,新代码进行了向量化处理,这是Python代码的真正变化。其次,matmult通常在多核处理器上并行运行,取决于您的主机配置,特别是当OpenBLAS或其他BLAS用于像此matmult这样的矩阵代数运算时,numpy会自动使用多个处理器和核心。

新的代码非常简单,在我的主机上运行100K行x 18个二进制列大约需要1秒的时间,对我来说这已经“任务完成”:

'''
Fast way is vectorized matmult. Pass in all rows and cols in one shot.
'''
def BitsToIntAFast(bits):
  m,n = bits.shape # number of columns is needed, not bits.size
  a = 2**np.arange(n)[::-1]  # -1 reverses array of powers of 2 of same length as bits
  return bits @ a  # this matmult is the key line of code

'''I use it like this:'''
bits = d.iloc[:,4:(4+18)] # read bits from my pandas dataframe
gs = BitsToIntAFast(bits)
print(gs[:5])
gs.shape
...
d['genre'] = np.array(gs)  # add the newly computed column to pandas

希望这可以帮到你。

4

使用numpy进行转换会限制结果为64位有符号二进制数。如果您确实希望使用numpy并且64位限制适用于您,那么一个更快的numpy实现是:

import numpy as np
def bin2int(bits):
    return np.right_shift(np.packbits(bits, -1), bits.size).squeeze()

通常情况下,如果您使用numpy,则关心速度,那么对于大于64位的结果,最快的实现方式是:

import gmpy2
def bin2int(bits):
    return gmpy2.pack(list(bits[::-1]), 1)

如果你不想依赖于gmpy2,那么这个方法会稍微慢一些,但是没有任何依赖,并且支持大于64位的结果:

def bin2int(bits):
    total = 0
    for shift, j in enumerate(bits[::-1]):
        if j:
            total += 1 << shift
    return total

注意细心的读者会发现,上一个版本与其他回答此问题的版本有些相似,主要区别在于使用了<<运算符而不是**。在我的测试中,这样做可以显著提高速度。

numpy支持无符号64位数据类型,所以这会起作用吗? - qwr
这真的更快吗?我的计时显示使用gmpy比普通方法慢了约4倍。此外,gmpy在打包方面存在奇怪的错误 https://github.com/aleaxit/gmpy/issues/129 - qwr

4

我的 timeit 结果:

b.dot(2**np.arange(b.size)[::-1])
100000 loops, best of 3: 2.48 usec per loop

b.dot(1 << np.arange(b.size)[::-1])
100000 loops, best of 3: 2.24 usec per loop

# Precompute powers-of-2 array with a = 1 << np.arange(b.size)[::-1]
b.dot(a)
100000 loops, best of 3: 0.553 usec per loop

# using gmpy2 is slower
gmpy2.pack(list(map(int,b[::-1])), 1)
100000 loops, best of 3: 10.6 usec per loop

如果您事先知道大小,那么预先计算2的幂次方数组的速度会更快。但是如果可能的话,应该像Geoffrey Anderson的回答中所示,使用矩阵乘法同时进行所有计算。


-1
def binary_converter(arr):
    total = 0
    for index, val in enumerate(reversed(arr)):
        total += (val * 2**index)
    print total


In [14]: b = np.array([1,0,1,0,0,0,0,0,1,0,1])
In [15]: binary_converter(b)
1285
In [9]: b = np.array([0,0,0,0,0,1,0,1])
In [10]: binary_converter(b)
5

或者

b = np.array([1,0,1,0,0,0,0,0,1,0,1])
sum(val * 2**index for index, val in enumerate(reversed(b)))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接