比较两个pandas数据框的行?

4
这是我的问题的续篇。比较两个pandas数据框行的最快方法? 我有两个数据框 A 和 B:
A 是1000行x 500列,填充了二进制值,指示存在或不存在。
举个简单的例子:
    A   B   C   D   E  
0   0   0   0   1   0  
1   1   1   1   1   0  
2   1   0   0   1   1  
3   0   1   1   1   0  

B 是一个1024行x10列的矩阵,是从0到1023的二进制形式的完整迭代。

示例:

     0   1   2  
0    0   0   0  
1    0   0   1   
2    0   1   0  
3    0   1   1  
4    1   0   0
5    1   0   1
6    1   1   0 
7    1   1   1

我正在尝试找出A中特定10列的哪些行与B中的每一行对应。

可以保证A [My_Columns_List]的每一行都会在B中的某个地方,但并非B的每一行都能与A [My_Columns_List]中的一行匹配。

例如,我想展示A的列[B,D,E]

A的行[1,3]B的行[6]对应,

A的行[0]B的行[2]对应,

A的行[2]B的行[3]对应。

我已经尝试使用:

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

这个方法是可行的,但我希望它能更快:

S = 2**np.arange(10)
A_ID = np.dot(A[My_Columns_List],S)
B_ID = np.dot(B,S)
out_row_idx = np.where(np.in1d(A_ID,B_ID))[0]

但是当我这样做时,out_row_idx返回一个包含所有A索引的数组,这并没有告诉我任何信息。我认为这种方法会更快,但我不知道为什么它返回从0到999的数组。欢迎任何意见!同时,感谢@jezrael和@Divakar提供的这些方法。

请发布示例数据框,并说明您希望从这些示例中实现什么结果。 - Alex
为了将来的清晰度,请不要将数据框命名为 AB,当数据框 A 也包含列 ABC...时,请将数据框命名为 df1df2dfadfb。此外,在 Python 中,我们使用 PEP-8 命名约定来命名变量,因此不要使用 A_ID,而是使用 a_id 或者更好的 id_a - smci
1个回答

6

我会坚持我的初始答案,但也许会更好地解释一下。

您要求比较两个Pandas数据框。因此,我将构建数据框。我可能会使用NumPy,但我的输入和输出将是数据框。

设置

您说我们有一个1000 x 500的由1和0组成的数组。让我们构建它。

A_init = pd.DataFrame(np.random.binomial(1, .5, (1000, 500)))
A_init.columns = pd.MultiIndex.from_product([range(A_init.shape[1]/10), range(10)])
A = A_init

此外,我给A添加了一个MultiIndex,以便轻松地按列分组为10个一组。

解决方案

这与@Divakar的答案非常相似,但有一个细微的差别需要指出。

对于10个1和0的一组,我们可以将其视为长度为8的位数组。然后,我们可以通过将其与2的幂的数组进行点积来计算它的整数值。

twos = 2 ** np.arange(10)

我可以像这样一次性为每组10个1和0执行此操作。
AtB = A.stack(0).dot(twos).unstack()

我使用堆叠操作将50组10个元素的行转换成列,以更加优雅地进行点积运算,然后使用取消堆叠操作将其还原。
现在我有一个1000 x 50的数据帧,其中包含从0到1023的数字。
假设B是一个数据帧,每行都是1024个唯一的0和1组合之一。B应该按B = B.sort_values().reset_index(drop=True)的方式进行排序。
这是我上次认为我未能解释清楚的部分。请看
AtB.loc[:2, :2]

enter image description here

那个在(0,0)位置上的值951表示第一行中前10个1和0组成的第一组与索引为951B行匹配。这就是你想要的!!!有趣的是,我从来没有看过B。你知道为什么吗?因为B是无关紧要的!它只是一种愚蠢的表示数字0到1023的方式。这就是我的答案不同之处,我忽略了B。忽略这个无用的步骤可以节省时间。
这些都是接受两个数据框AB并返回一个指数数据框的函数,其中AB匹配。剧透警告,我将完全忽略B
def FindAinB(A, B):
    assert A.shape[1] % 10 == 0, 'Number of columns in A is not a multiple of 10'
    rng = np.arange(A.shape[1])
    A.columns = pd.MultiIndex.from_product([range(A.shape[1]/10), range(10)])

    twos = 2 ** np.arange(10)

    return A.stack(0).dot(twos).unstack()

def FindAinB2(A, B):
    assert A.shape[1] % 10 == 0, 'Number of columns in A is not a multiple of 10'
    rng = np.arange(A.shape[1])
    A.columns = pd.MultiIndex.from_product([range(A.shape[1]/10), range(10)])
    # use clever bit shifting instead of dot product with powers
    # questionable improvement
    return (A.stack(0) << np.arange(10)).sum(1).unstack()

我正在发挥自己内在的@Divakar(也就是说,这些都是我从Divakar那里学到的东西)

def FindAinB3(A, B):
    assert A.shape[1] % 10 == 0, 'Number of columns in A is not a multiple of 10'
    a = A.values.reshape(-1, 10)
    a = np.einsum('ij->i', a << np.arange(10))
    return pd.DataFrame(a.reshape(A.shape[0], -1), A.index)

极简单行代码

f = lambda A: pd.DataFrame(np.einsum('ij->i', A.values.reshape(-1, 10) << np.arange(10)).reshape(A.shape[0], -1), A.index)

像这样使用

f(A)

计时

FindAinB3 的速度快了一个数量级

enter image description here


嗨@piRSquared,我真的很喜欢你的答案的效率; 然而,我一直遇到一个错误:TypeError: object of type 'zip' has no len(),不能找出问题所在。我担心这是一个Python 3与2的问题,但我会再次检查看看能否找出问题。非常感谢! - Garrett Miller
我应该更具体地说明,我如何编辑函数以指定我要查看A的哪些列?而不是取其中的一组。 - Garrett Miller
1
@GarrettMiller,你应该将你想要的列作为参数A传递给函数。 - piRSquared
1
@Divakar,你觉得我的np.einsum怎么样?这都是你的功劳。 - piRSquared
1
喜欢那部分,这是NumPy最好的东西之一!不过没有什么能打败点积。 - Divakar
显示剩余3条评论

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