这是我问题的延续。 Fastest way to compare rows of two pandas dataframes?
我有两个数据帧 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 行 x 10 列,是从 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 列中找到 A
中的哪些行与 B
的每一行相对应。A[My_Columns_List]
的每一行都保证在 B
中的某个位置,但并非 B
的每一行都与 A[My_Columns_List]
中的一行匹配例如,我想表明对于
[B,D,E]
的 A
列,A 的行
[1,3]
与 [6]
的行 B
匹配,A 的行
[0]
与 [2]
的行 B
匹配,A 的行
[2]
与 [3]
的行 B
匹配。我试过使用:
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。
最佳答案
我会坚持我最初的回答,但也许解释得更好。
您要求比较 2 个 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()
我
stack
将一行 50 组的 10 排成列,以便更优雅地进行点积。然后我用 unstack
把它带回来了。我现在有一个 1000 x 50 的数据框,范围为 0-1023。
假设
B
是一个数据帧,每一行都有 1024 个唯一的 1 和 0 组合之一。 B
应该像 B = B.sort_values().reset_index(drop=True)
一样排序。这是我认为我上次未能解释的部分。看着
AtB.loc[:2, :2]
(0, 0)
位置 951
中的那个值意味着 A
第一行中的第一组 10 个 1 和 0 与 B
中具有索引 951
的行匹配。那就是你想要的!!!有趣的是,我从来没有看过 B。你知道为什么,B 无关紧要!!!这只是表示 0 到 1023 数字的一种愚蠢方式。这与我的答案不同,我忽略了 B
。忽略这个无用的步骤应该可以节省时间。这些都是采用两个数据帧
A
和 B
并返回索引数据帧的函数,其中 A
匹配 B
。剧透警报,我将完全忽略 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 快了一个数量级