尽可能高效地执行一些熊猫数据帧行的成对比较

时间:2020-02-04 20:02:18

标签: python-3.x pandas performance numpy pairwise

对于给定的熊猫数据框df,我想将每个样本(行)彼此进行比较。

对于更大的数据集,这将导致过多的比较(n**2)。因此,有必要仅对较小的组(即,对所有共享相同id的组)进行比较,并且要尽可能地高效。

我想构造一个数据帧(df_pairs),该数据帧的每一行都包含一对。另外,我想获取所有对索引(最好是Python集合)。

首先,我构建一个示例数据框:

import numpy as np
import pandas as pd
from functools import reduce
from itertools import product, combinations

n_samples = 10_000
suffixes = ["_1", "_2"]  # for df_pairs
id_str = "id"

df = pd.DataFrame({id_str: np.random.randint(0, 10, n_samples),
                   "A": np.random.randint(0, 100, n_samples),
                   "B": np.random.randint(0, 100, n_samples),
                   "C": np.random.randint(0, 100, n_samples)}, index=range(0, n_samples))

columns_df_pairs = ([elem + suffixes[0] for elem in df.columns] + 
                    [elem + suffixes[1] for elem in df.columns])

在下面,我将比较4个不同的选项和相应的性能指标:

选项1

groups = df.groupby(id_str).groups  # get the groups
pairs_per_group = [set(product(elem.tolist(), repeat=2)) for _, elem in groups.items()]  # determine pairs per group
set_of_pairs = reduce(set.union, pairs_per_group)  # convert all groups into one set
idcs1, idcs2 = zip(*[(e1, e2) for e1, e2 in set_of_pairs])
df_pairs = pd.DataFrame(np.hstack([df.values[idcs1, :], df.values[idcs2, :]]), # construct the dataframe of pairs
                        columns=columns_df_pairs, 
                        index=pd.MultiIndex.from_tuples(set_of_pairs, names=('index 1', 'index 2')))
df_pairs.drop([id_str + suffixes[0], id_str + suffixes[1]], inplace=True, axis=1)

选项1耗时34.2 s±1.28 s。

选项2

groups = df.groupby(id_str).groups  # get the groups
pairs_per_group = [np.array(np.meshgrid(elem.values, elem.values)).T.reshape(-1, 2) for _, elem in groups.items()]
idcs = np.unique(np.vstack(pairs_per_group), axis=0)
df_pairs2 = pd.DataFrame(np.hstack([df.values[idcs[:, 0], :], df.values[idcs[:, 1], :]]), # construct the dataframe of pairs
                        columns=columns_df_pairs, 
                        index=pd.MultiIndex.from_arrays([idcs[:, 0], idcs[:, 1]], names=('index 1', 'index 2')))
df_pairs2.drop([id_str + suffixes[0], id_str + suffixes[1]], inplace=True, axis=1)

选项2耗时13 s±1.34 s。

选项3

groups = df.groupby(id_str).groups  # get the groups
pairs_per_group = [np.array([np.tile(elem.values, len(elem.values)), np.repeat(elem.values, len(elem.values))]).T.reshape(-1, 2) for _, elem in groups.items()]
idcs = np.unique(np.vstack(pairs_per_group), axis=0)
df_pairs3 = pd.DataFrame(np.hstack([df.values[idcs[:, 0], :], df.values[idcs[:, 1], :]]), # construct the dataframe of pairs
                        columns=columns_df_pairs, 
                        index=pd.MultiIndex.from_arrays([idcs[:, 0], idcs[:, 1]], names=('index 1', 'index 2')))
df_pairs3.drop([id_str + suffixes[0], id_str + suffixes[1]], inplace=True, axis=1)

选项3耗时12.1秒±347毫秒。

选项4

df_pairs4 = pd.merge(left=df, right=df, how="inner", on=id_str, suffixes=suffixes)
# here, I do not know how to get the MultiIndex in
df_pairs4.drop([id_str], inplace=True, axis=1)

最快以1.41 s±239 ms计算选项4。但是,在这种情况下,我没有成对的索引。

通过使用comparisons而非itertools的product,我可以稍微提高性能。我还可以构建比较矩阵,仅使用上三角形,然后从那里构建数据框。但是,这似乎并不比执行笛卡尔积和删除自引用以及反向比较(a, b) = (b, a)更有效。

  • 您能告诉我一种更有效的方法来获取对进行比较(理想情况下是一个可以使用set操作的集合)吗?
  • 我可以使用merge或另一个pandas函数来用多索引构造所需的数据框吗?

1 个答案:

答案 0 :(得分:1)

内部merge将破坏索引,而使用新的Int64Index。如果索引很重要,请reset_index将其作为一列,然后将这些列设置回索引。

df_pairs4 = (pd.merge(left=df.reset_index(), right=df.reset_index(), 
                      how="inner", on=id_str, suffixes=suffixes)
               .set_index(['index_1', 'index_2']))

                 id  A_1  B_1  C_1  A_2  B_2  C_2
index_1 index_2                                  
0       0         4   92   79   10   92   79   10
        13        4   92   79   10   83   68   69
        24        4   92   79   10   67   73   90
        25        4   92   79   10   22   31   35
        36        4   92   79   10   64   44   20
...              ..  ...  ...  ...  ...  ...  ...
9993    9971      7   20   65   92   47   65   21
        9977      7   20   65   92   50   35   27
        9980      7   20   65   92   43   36   62
        9992      7   20   65   92   99    2   17
        9993      7   20   65   92   20   65   92