如何高效地将一个Pandas数据框的每一列与另一个数据框的每一列相乘?

6

我正在尝试将两个pandas数据框相乘。具体来说,我想将另一个数据框的每一列与每一列相乘。

这些数据框是独热编码的,因此它们看起来像这样:

col_1, col_2, col_3, ...
 0      1      0 
 1      0      0 
 0      0      1 
 ...

我可以使用for循环迭代每一列,但在Python中这是计算成本高昂的,我希望有更简单的方法。

其中一个数据框有500列,另一个有100列。

这是我目前能够编写的最快版本:

interact_pd = pd.DataFrame(index=df_1.index)
df1_columns = [column for column in df_1]
for column in df_2:
    col_pd = df_1[df1_columns].multiply(df_2[column], axis="index")
    interact_pd = interact_pd.join(col_pd, lsuffix='_' + column)

我将在df_2中迭代每一列,并将df_1乘以该列的所有值,然后将结果附加到interact_pd。然而,我不想使用for循环方式,因为这会消耗大量计算资源。有没有更快的方法来实现?

编辑:示例

df_1:

1col_1, 1col_2, 1col_3
 0      1      0 
 1      0      0 
 0      0      1 

df_2:

2col_1, 2col_2
 0      1       
 1      0       
 0      0      

interact_pd:

1col_1_2col_1, 1col_2_2col_1,1col_3_2col_1, 1col_1_2col_2, 1col_2_2col_2,1col_3_2col_2

  0      0      0        0       1        0  
  1      0      0        0       0        0 
  0      0      0        0       0        0 

2
你需要提供一个可重现的例子,清晰地展示你想要的输出,并解释之前努力的不足之处。 - Paul H
交互是什么意思? - Kartik
你想如何将第一个数据框的每一列分别与第二个数据框的每一列相乘,如果它们的列数不同?你是否希望得到一个有50000列的结果? - Khris
只是一个表面上的事情,你可以在第2行中执行 df1_columns = df1.columns - Khris
@chris 看看我的解决方案,它在类似于你的数据集上比被接受的解决方案更快。 - jrjc
显示剩余5条评论
3个回答

7
# use numpy to get a pair of indices that map out every
# combination of columns from df_1 and columns of df_2
pidx = np.indices((df_1.shape[1], df_2.shape[1])).reshape(2, -1)

# use pandas MultiIndex to create a nice MultiIndex for
# the final output
lcol = pd.MultiIndex.from_product([df_1.columns, df_2.columns],
                                  names=[df_1.columns.name, df_2.columns.name])

# df_1.values[:, pidx[0]] slices df_1 values for every combination
# like wise with df_2.values[:, pidx[1]]
# finally, I marry up the product of arrays with the MultiIndex
pd.DataFrame(df_1.values[:, pidx[0]] * df_2.values[:, pidx[1]],
             columns=lcol)

enter image description here


Timing

code

from string import ascii_letters

df_1 = pd.DataFrame(np.random.randint(0, 2, (1000, 26)), columns=list(ascii_letters[:26]))
df_2 = pd.DataFrame(np.random.randint(0, 2, (1000, 52)), columns=list(ascii_letters))

def pir1(df_1, df_2):
    pidx = np.indices((df_1.shape[1], df_2.shape[1])).reshape(2, -1)

    lcol = pd.MultiIndex.from_product([df_1.columns, df_2.columns],
                                      names=[df_1.columns.name, df_2.columns.name])

    return pd.DataFrame(df_1.values[:, pidx[0]] * df_2.values[:, pidx[1]],
                        columns=lcol)

def Test2(DA,DB):
  MA = DA.as_matrix()
  MB = DB.as_matrix()
  MM = np.zeros((len(MA),len(MA[0])*len(MB[0])))
  Col = []
  for i in range(len(MB[0])):
    for j in range(len(MA[0])):
      MM[:,i*len(MA[0])+j] = MA[:,j]*MB[:,i]
      Col.append('1col_'+str(i+1)+'_2col_'+str(j+1))
  return pd.DataFrame(MM,dtype=int,columns=Col)

results

enter image description here


不错,但稍微解释一下会更好! - jrjc
使用我的示例,该函数的timeit每个循环产生910 µs - Khris
@Khris 在一个更大的数据框上运行这个。OP建议这是针对更大的数据集。 - piRSquared
使用两个更大的100x100数据集,每次循环需要16毫秒,与其他解决方案相比速度惊人地快。 - Khris
@Khris 不仅仅是计时,还能优雅地处理列名。 - piRSquared
@piRSquared 我的解决方案对于一个与 OP 数据集大小相似的数据集似乎更快。 - jrjc

6

您可以沿着索引轴将第一个df与第二个df的每一列相乘,这是处理大型数据集的最快方法(见下文):

df = pd.concat([df_1.mul(col[1], axis="index") for col in df_2.iteritems()], axis=1)
# Change the name of the columns
df.columns = ["_".join([i, j]) for j in df_2.columns for i in df_1.columns]
df
       1col_1_2col_1  1col_2_2col_1  1col_3_2col_1  1col_1_2col_2  \
0                  0              0              0              0   
1                  1              0              0              0   
2                  0              0              0              0   

   1col_2_2col_2  1col_3_2col_2  
0              1              0  
1              0              0  
2              0              0  

-->请参考基准测试结果,选择最适合你的数据集的答案。


基准测试

功能:

def Test2(DA,DB):
  MA = DA.as_matrix()
  MB = DB.as_matrix()
  MM = np.zeros((len(MA),len(MA[0])*len(MB[0])))
  Col = []
  for i in range(len(MB[0])):
    for j in range(len(MA[0])):
      MM[:,i*len(MA[0])+j] = MA[:,j]*MB[:,i]
      Col.append('1col_'+str(i+1)+'_2col_'+str(j+1))
  return pd.DataFrame(MM,dtype=int,columns=Col)

def Test3(df_1, df_2):
    df = pd.concat([df_1.mul(i[1], axis="index") for i in df_2.iteritems()], axis=1)
    df.columns = ["_".join([i,j]) for j in df_2.columns for i in df_1.columns]
    return df

def Test4(df_1,df_2):
    pidx = np.indices((df_1.shape[1], df_2.shape[1])).reshape(2, -1)
    lcol = pd.MultiIndex.from_product([df_1.columns, df_2.columns],
                                      names=[df_1.columns.name, df_2.columns.name])
    return pd.DataFrame(df_1.values[:, pidx[0]] * df_2.values[:, pidx[1]],
                 columns=lcol)

def jeanrjc_imp(df_1, df_2):
    df = pd.concat([df_1.mul(‌​i[1], axis="index") for i in df_2.iteritems()], axis=1, keys=df_2.columns) 
    return df

代码:

抱歉,这段丑陋的代码,最后的情节很重要:

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

df_1 = pd.DataFrame(np.random.randint(0, 2, (1000, 600)))
df_2 = pd.DataFrame(np.random.randint(0, 2, (1000, 600)))
df_1.columns = ["1col_"+str(i) for i in range(len(df_1.columns))]
df_2.columns = ["2col_"+str(i) for i in range(len(df_2.columns))]
resa = {}
resb = {}
resc = {}
for f, r in zip([Test2, Test3, Test4, jeanrjc_imp], ["T2", "T3", "T4", "T3bis"]):
        resa[r] = []
        resb[r] = []
        resc[r] = []
        for i in [5, 10, 30, 50, 150, 200]:
             a = %timeit -o f(df_1.iloc[:,:i], df_2.iloc[:, :10])
             b = %timeit -o f(df_1.iloc[:,:i], df_2.iloc[:, :50])
             c = %timeit -o f(df_1.iloc[:,:i], df_2.iloc[:, :200])
             resa[r].append(a.best)
             resb[r].append(b.best)
             resc[r].append(c.best)

X = [5, 10, 30, 50, 150, 200]
fig, ax = plt.subplots(1, 3, figsize=[16,5])
for j, (a, r) in enumerate(zip(ax, [resa, resb, resc])):
    for i in r:
        a.plot(X, r[i], label=i)
        a.set_xlabel("df_1 columns #") 
        a.set_title("df_2 columns # = {}".format(["10", "50", "200"][j]))
ax[0].set_ylabel("time(s)")
plt.legend(loc=0)
plt.tight_layout()

Pandas列乘法

使用 T3b <=> jeanrjc_imp。它比 Test3 更快一些。

结论:

根据您的数据集大小,选择正确的函数,Test4 和 Test3(b)之间。根据 OP 的数据集,Test3 或者 jeanrjc_imp 应该是最快的,也是最短的!

希望对你有帮助。


@Khris 数据集越大,你的解决方案就会越慢。 - jrjc
你的解决方案已经从迭代列名变为使用 df_2.iteritems()。后者比前者更快。在某些情况下,它比我的解决方案更快(如 500 x 100)。但是,它的速度不对称。如果列的不平衡性反过来,它会变慢。 - piRSquared
此外,这个解决方案在基于整数的列上会出现问题。使用“keys”可以解决这个问题。话虽如此,这是一个很好的解决方案!+1 - piRSquared
@piRSquared 谢谢,我不知道 pd.concatkeys 参数,你能详细解释一下为什么它更快吗?还是因为它消除了枚举列名的双重循环? - jrjc
你只有一个循环,那就是在 df_2 的列上。如果 df_2 中的列数比 df_1 中的列数多得多,那么速度会变慢,因为你要循环更多的列。所以 500 列乘以 100 列比 100 列乘以 500 列要快得多。无论如何,这种方法似乎在更大范围内的有意义的可能性中更快。 - piRSquared
显示剩余3条评论

3
你可以使用numpy。
考虑以下示例代码,我修改了变量名,但 Test1() 本质上是你的代码。 我没有在该函数中创建正确的列名称:
import pandas as pd
import numpy as np

A = [[1,0,1,1],[0,1,1,0],[0,1,0,1]]
B = [[0,0,1,0],[1,0,1,0],[1,1,0,0],[1,0,0,1],[1,0,0,0]]

DA = pd.DataFrame(A).T
DB = pd.DataFrame(B).T

def Test1(DA,DB):
  E = pd.DataFrame(index=DA.index)
  DAC = [column for column in DA]
  for column in DB:
    C = DA[DAC].multiply(DB[column], axis="index")
    E = E.join(C, lsuffix='_' + str(column))
  return E

def Test2(DA,DB):
  MA = DA.as_matrix()
  MB = DB.as_matrix()
  MM = np.zeros((len(MA),len(MA[0])*len(MB[0])))
  Col = []
  for i in range(len(MB[0])):
    for j in range(len(MA[0])):
      MM[:,i*len(MA[0])+j] = MA[:,j]*MB[:,i]
      Col.append('1col_'+str(i+1)+'_2col_'+str(j+1))
  return pd.DataFrame(MM,dtype=int,columns=Col)

print Test1(DA,DB)
print Test2(DA,DB)

输出:

   0_1  1_1  2_1  0  1  2  0_3  1_3  2_3  0  1  2  0  1  2
0    0    0    0  1  0  0    1    0    0  1  0  0  1  0  0
1    0    0    0  0  0  0    0    1    1  0  0  0  0  0  0
2    1    1    0  1  1  0    0    0    0  0  0  0  0  0  0
3    0    0    0  0  0  0    0    0    0  1  0  1  0  0  0
   1col_1_2col_1  1col_1_2col_2  1col_1_2col_3  1col_2_2col_1  1col_2_2col_2  \
0              0              0              0              1              0   
1              0              0              0              0              0   
2              1              1              0              1              1   
3              0              0              0              0              0   

   1col_2_2col_3  1col_3_2col_1  1col_3_2col_2  1col_3_2col_3  1col_4_2col_1  \
0              0              1              0              0              1   
1              0              0              1              1              0   
2              0              0              0              0              0   
3              0              0              0              0              1   

   1col_4_2col_2  1col_4_2col_3  1col_5_2col_1  1col_5_2col_2  1col_5_2col_3  
0              0              0              1              0              0  
1              0              0              0              0              0  
2              0              0              0              0              0  
3              0              1              0              0              0  

您的函数的性能表现:

%timeit(Test1(DA,DB))
100 loops, best of 3: 11.1 ms per loop

我的函数的性能:

%timeit(Test2(DA,DB))
1000 loops, best of 3: 464 µs per loop

它不美观,但效率高。


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