Interpret columns of zeros and ones as binary and

2020-04-10 03:45发布

问题:

I have a dataframe of zeros and ones. I want to treat each column as if its values were a binary representation of an integer. What is easiest way to make this conversion?

I want this:

df = pd.DataFrame([[1, 0, 1], [1, 1, 0], [0, 1, 1], [0, 0, 1]])

print df

   0  1  2
0  1  0  1
1  1  1  0
2  0  1  1
3  0  0  1

converted to:

0    12
1     6
2    11
dtype: int64

As efficiently as possible.

回答1:

Similar solution, but more faster:

print (df.T.dot(1 << np.arange(df.shape[0] - 1, -1, -1)))
0    12
1     6
2    11
dtype: int64

Timings:

In [81]: %timeit df.apply(lambda col: int(''.join(str(v) for v in col), 2))
The slowest run took 5.66 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 264 µs per loop

In [82]: %timeit (df.T*(1 << np.arange(df.shape[0]-1, -1, -1))).sum(axis=1)
1000 loops, best of 3: 492 µs per loop

In [83]: %timeit (df.T.dot(1 << np.arange(df.shape[0] - 1, -1, -1)))
The slowest run took 6.14 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 204 µs per loop


回答2:

You can create a string from the column values and then use int(binary_string, base=2) to convert to integer:

df.apply(lambda col: int(''.join(str(v) for v in col), 2))
Out[6]: 
0    12
1     6
2    11
dtype: int64

Not sure about efficiency, multiplying by the relevant powers of 2 then summing probably takes better advantage of fast numpy operations, this is probably more convenient though.



回答3:

Similar in concept to @jezrael's solution that used dot-product, but with couple of improvements. We can avoid the transpose by bringing the 2-powered range array from the front for the dot-product. This would be beneficial for large arrays, as transposing them would have some overhead. Also, operating on NumPy arrays would be better for these number crunching cases, so we could operate on df.values instead. At the end, we need to convert to pandas series/dataframe for the final output.

Thus, combining these two improvements, the modified implementation would be -

pd.Series((2**np.arange(df.shape[0]-1,-1,-1)).dot(df.values))

Runtime test -

In [159]: df = pd.DataFrame(np.random.randint(0,2,(4,10000)))

In [160]: p1 = pd.Series((2**np.arange(df.shape[0]-1,-1,-1)).dot(df.values))

# @jezrael's solution
In [161]: p2 = (df.T.dot(1 << np.arange(df.shape[0] - 1, -1, -1)))

In [162]: np.allclose(p1.values, p2.values)
Out[162]: True

In [163]: %timeit pd.Series((2**np.arange(df.shape[0]-1,-1,-1)).dot(df.values))
1000 loops, best of 3: 268 µs per loop

# @jezrael's solution
In [164]: %timeit (df.T.dot(1 << np.arange(df.shape[0] - 1, -1, -1)))
1000 loops, best of 3: 554 µs per loop