Pandas系列位到十进制整数的转换

3
我有一个形状为(m,n)的pandas数据框,里面填充了01。如果将数据框的每一行看作是一个二进制数,则我希望生成一个pandas系列,其中包含由该行表示的十进制数。
给定以下维度为(m,n)、填充了01的矩阵:
m = int(1e6)
n = 5
df = pd.DataFrame(np.random.rand(m,n)).round().astype(int)

我目前使用的方法是这样的:
df_asstr = df.astype(str)
bin_series = df_asstr.sum(axis=1).astype(int).astype(str)

def bin_to_int(strnum):
    return int(strnum, 2)

decimal_series = bin_series.astype(str).apply(bin_to_int)

我的问题在于时间。如果数据框的长度大约为m=1e3,那么整个过程只需要不到一秒钟。然而,当我使用m=1e6时,需要大约22秒,而且我需要运行许多这样的过程,因此我真的希望加快速度。
我知道减慢过程的步骤是涉及将DataFrame转换为str的步骤,即这些行:
df_asstr = df.astype(str)
bin_series = df_asstr.sum(axis=1).astype(int).astype(str)
decimal_series = bin_series.astype(str).apply(bin_to_int)

有没有人知道更有效的方法来创建十进制整数序列?非常感谢!

3个回答

2
你可以使用按位左移运算符与点积dot乘积一起使用:dot
a = df.values
b = a.dot(1 << np.arange(a.shape[-1] - 1, -1, -1))

In [157]: %%timeit 
     ...: a = df.values
     ...: b = pd.Series(a.dot(1 << np.arange(a.shape[-1] - 1, -1, -1)), index=df.index)
     ...: 
16.8 ms ± 281 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [158]: %%timeit
     ...: (2 ** (np.arange(start = len(df.columns), stop = 0, step = -1)-1) * df).sum(axis =1)
     ...: 
81.5 ms ± 432 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
不错,这里的.dot()在乘法和加法上的运用是关键的加速方式,但<<也是一个不错的点睛之笔。 - jeremycg
1
该死,你们真聪明。非常感谢,它完美地运行了。 - Jacobo Lansac

1
我认为这个做了你想要的事情:

(2 ** (np.arange(start = len(df.columns), stop = 0, step = -1)-1) * df).sum(axis =1)

0          1
1         27
2          4
3         11
4         29
5         27
6          3
7         29

解释:

我们希望将数据框的每一列乘以2 ** x,其中x是距离右侧的索引:

2 ** (np.arange(start = len(df.columns), stop = 0, step = -1)-1) 

array([16,  8,  4,  2,  1], dtype=int32)

一旦我们获取了这个,就将数据框乘以它,并在轴=1上求和得到系列数据。
时间:
您的答案:
%%timeit
df_asstr = df.astype(str)
bin_series = df_asstr.sum(axis=1).astype(int).astype(str)

def bin_to_int(strnum):
    return int(strnum, 2)

decimal_series = bin_series.astype(str).apply(bin_to_int)

1 loop, best of 3: 20.2 s per loop

这个是什么?
%%timeit
(2 ** (np.arange(start = len(df.columns), stop = 0, step = -1)-1) * df).sum(axis =1)

10 loops, best of 3: 117 ms per loop

编辑:如下面@jezrael的回答,一个mul和sum是一个点乘积:
df.values.dot((2 ** (np.arange(start = len(df.columns), stop = 0, step = -1)-1)))

10 loops, best of 3: 23.4 ms per loop

1

您正确地识别了字符串转换是瓶颈。这些可以通过将二进制转换为十进制的教科书方法来避免。将每列与相应的值相乘,并按行求和。在过时的安装中,这将产生约380倍的加速。下面的片段在Jupyter笔记本中计时两种方法。 df 的设置与您第一个代码部分中的设置相同。

m = int(1e6)
n = 5
df = pd.DataFrame(np.random.rand(m,n)).round().astype(int)

def StatusQuo(df):
    df_asstr = df.astype(str)
    bin_series = df_asstr.sum(axis=1).astype(int).astype(str)

    def bin_to_int(strnum):
        return int(strnum, 2)

    decimal_series = bin_series.astype(str).apply(bin_to_int)
    return decimal_series
%time StatusQuo(df)
# CPU times: user 12.1 s, sys: 103 ms, total: 12.2 s
# Wall time: 12.2 s


def Naive(df):
    n = len(df.columns)
    powers = np.array([2**i for i in range(n-1,-1,-1)])
    df_values = df.mul(powers).sum(axis=1)
return df_values
%time Naive(df)
# CPU times: user 31 ms, sys: 52 ms, total: 83 ms
# Wall time: 32.1 ms

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