两个pandas数据框比较行的最快方法是什么?

3

我有两个pandas数据框,A和B。

A是1000行x500列,填充了二进制值,指示存在或不存在。

B是1024行x10列,并且是0和1的完整迭代,因此有1024行。

我试图找出在A的特定10列中,哪些行与B中给定的行相对应。我需要整行匹配,而不是逐个元素匹配。

例如,我想要

A[(A.ix[:,(1,2,3,4,5,6,7,8,9,10)==(1,0,1,0,1,0,0,1,0,0)).all(axis=1)]

将A中的行 (3,5,8,11,15) 与B中相应列 (1,2,3,4,5,6,7,8,9,10) 上的行 (1,0,1,0,1,0,0,1,0,0) 进行匹配。

我希望对B的每一行都进行这个操作,我想到最好的方法是:

import numpy as np
for i in B:
    B_array = np.array(i)
    Matching_Rows = A[(A.ix[:,(1,2,3,4,5,6,7,8,9,10)] == B_array).all(axis=1)]
    Matching_Rows_Index = Matching_Rows.index

这个例子看起来不是太糟糕,但我在一个 while 循环中使用了它,而该循环大约运行了 20,000 次;因此,它会使程序变慢。

我一直在尝试使用 DataFrame.apply,但无法取得成功。可能 map 函数能更好地发挥作用?

我只是希望有人能看到更有效率的方法,因为我对 Python 还比较新。

谢谢,祝好!


1
A中的单行可能有多个B匹配项。所以,您想在特定行中至少找到一个匹配项吗?还是根据发布的代码仅在A的col1-col10之间寻找匹配项? - Divakar
1
你打算如何处理匹配的行?在我看来,你可以避免使用B,而是在A的10个感兴趣的列上使用groupby - Victor Chubukov
我将根据匹配的行计算熵值。 - Garrett Miller
也许你可以直接使用A.groupby(column_list).apply(my_entropy_function)。请参考http://pandas.pydata.org/pandas-docs/stable/groupby.html。 - Victor Chubukov
4个回答

4
我们可以利用两个数据框都具有二进制值0或1的事实,将来自A的相关列和所有来自B的列折叠成1D数组。当将每行视为可转换为十进制数等价物的二进制数序列时,这应该会显着减少问题集,从而有助于性能。现在,在获取这些1D数组之后,我们可以使用np.in1d在A中查找与B匹配的项,并最终使用np.where在其上获取匹配索引。
因此,我们会有以下实现-
# Setup 1D arrays corresponding to selected cols from A and entire B
S = 2**np.arange(10)
A_ID = np.dot(A[range(1,11)],S)
B_ID = np.dot(B,S)

# Look for matches that exist from B_ID in A_ID, whose indices 
# would be desired row indices that have matched from B
out_row_idx = np.where(np.in1d(A_ID,B_ID))[0]

示例运行 -

In [157]: # Setup dataframes A and B with rows 0, 4 in A having matches from B
     ...: A_arr = np.random.randint(0,2,(10,14))
     ...: B_arr = np.random.randint(0,2,(7,10))
     ...: 
     ...: B_arr[2] = A_arr[4,1:11]
     ...: B_arr[4] = A_arr[4,1:11]
     ...: B_arr[5] = A_arr[0,1:11]
     ...: 
     ...: A = pd.DataFrame(A_arr)
     ...: B = pd.DataFrame(B_arr)
     ...: 

In [158]: S = 2**np.arange(10)
     ...: A_ID = np.dot(A[range(1,11)],S)
     ...: B_ID = np.dot(B,S)
     ...: out_row_idx = np.where(np.in1d(A_ID,B_ID))[0]
     ...: 

In [159]: out_row_idx
Out[159]: array([0, 4])

所以我有一个关于这个的后续问题,我真的很喜欢这种方法的效率,但是当我使用我的数据框运行它时,out_row_idx只返回一个大小为1000的数组,其中只包含A的所有索引,你有任何想法吗? - Garrett Miller
S = 2**np.arange(10); A_ID = np.dot(A[X],S); B_ID = np.dot(B,S); out_row_idx = np.where(np.in1d(A_ID,B_ID))[0] - Garrett Miller

3
你可以使用 mergereset_index - 输出是在自定义列中,与 A 中相等的 B 的索引:
A = pd.DataFrame({'A':[1,0,1,1],
                  'B':[0,0,1,1],
                  'C':[1,0,1,1],
                  'D':[1,1,1,0],
                  'E':[1,1,0,1]})

print (A)
   A  B  C  D  E
0  1  0  1  1  1
1  0  0  0  1  1
2  1  1  1  1  0
3  1  1  1  0  1

B = pd.DataFrame({'0':[1,0,1],
                  '1':[1,0,1],
                  '2':[1,0,0]})

print (B)
   0  1  2
0  1  1  1
1  0  0  0
2  1  1  0

print (pd.merge(B.reset_index(), 
                A.reset_index(), 
                left_on=B.columns.tolist(), 
                right_on=A.columns[[0,1,2]].tolist(),
                suffixes=('_B','_A')))

   index_B  0  1  2  index_A  A  B  C  D  E
0        0  1  1  1        2  1  1  1  1  0
1        0  1  1  1        3  1  1  1  0  1
2        1  0  0  0        1  0  0  0  1  1    

print (pd.merge(B.reset_index(), 
                A.reset_index(), 
                left_on=B.columns.tolist(), 
                right_on=A.columns[[0,1,2]].tolist(),
                suffixes=('_B','_A'))[['index_B','index_A']])    

   index_B  index_A
0        0        2
1        0        3
2        1        1   

谢谢!这个和我代码的其他更改一起,把我的运行时间从10小时降到了10分钟。 - Garrett Miller
@jezrael。可能我没有正确理解问题,但是根据上面的解决方案,您将如何比较A中超出解决方案指定范围的行(例如,假设A中的列22:32与B中的第7行对应)?据我所知,您已经硬编码了A中要进行比较的查找列表(即列0,1,2)。 - Siraj S.

1
你可以在pandas中使用loc或ix,并告诉它找到所有十列都相等的行。像这样:
A.loc[(A[1]==B[1]) & (A[2]==B[2]) & (A[3]==B[3]) & A[4]==B[4]) & (A[5]==B[5]) & (A[6]==B[6]) & (A[7]==B[7]) & (A[8]==B[8]) & (A[9]==B[9]) & (A[10]==B[10])]

在我看来,这很丑陋,但它能够起作用并且消除了循环,因此速度应该会显著提高。如果有人能想出更优雅的编码方式,我不会感到惊讶。


1
在这个特殊情况下,您的10个0和1的行可以解释为10位二进制数。如果B是有序的,则可以将其解释为从0到1023的范围。在这种情况下,我们只需要将A的行分成10列块,并计算它的二进制等价物。
我将首先定义一系列二的幂次方,以便我可以进行矩阵乘法。
twos = pd.Series(np.power(2, np.arange(10)))

接下来,我将把A的列重新标记为MultiIndex并使用stack来获取每组10个数据块。
A = pd.DataFrame(np.random.binomial(1, .5, (1000, 500)))
A.columns = pd.MultiIndex.from_tuples(zip((A.columns / 10).tolist(), (A.columns % 10).tolist()))
A_ = A.stack(0)

A_.head()

enter image description here

最后,我将使用twosA_乘以,以获得每行的整数表示,并unstack
A_.dot(twos).unstack()

enter image description here

现在这是一个1000 x 50的DataFrame,每个单元格表示我们为A的该特定行的该特定10列块匹配了B的哪些行。甚至不需要B。


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