快速的python算法(在numpy或pandas中?)查找与另一个数组中的元素匹配的数组元素的索引



我正在寻找一种快速的方法来确定两个数组的交叉匹配索引,定义如下。

我有两个非常大的(>1e7 元素)结构化数组,一个称为成员,另一个称为。这两个数组都有一个组 ID 列。组数组的 ID 条目是唯一的,成员数组的组 ID 条目不是。

数组有一个名为 mass 的列。成员数组有一个名为 groupmass 的列(当前为空)。我想将正确的组质量分配给具有与其中一个组匹配的组 ID的成员元素。这将通过以下方式实现:

members['groupmass'][idx_matched_members] = groups['mass'][idx_matched_groups]

所以我需要一个快速的例程来计算两个索引数组idx_matched_membersidx_matched_groups。这种任务似乎很常见,以至于像numpy或pandas这样的软件包很可能有一个优化的解决方案。有谁知道专业开发、自制或其他解决方案?

可以通过pandas使用map使用一列的数据映射另一列的数据来完成。 下面是一个包含示例数据的示例:

members = pandas.DataFrame({
    'id': np.arange(10),
    'groupID': np.arange(10) % 3,
    'groupmass': np.zeros(10)
})
groups = pandas.DataFrame({
    'groupID': np.arange(3),
    'mass': np.random.randint(1, 10, 3)
})

这将为您提供以下数据:

>>> members
   groupID  groupmass  id
0        0          0   0
1        1          0   1
2        2          0   2
3        0          0   3
4        1          0   4
5        2          0   5
6        0          0   6
7        1          0   7
8        2          0   8
9        0          0   9
>>> groups
   groupID  mass
0        0     3
1        1     7
2        2     4

然后:

>>> members['groupmass'] = members.groupID.map(groups.set_index('groupID').mass)
>>> members
   groupID  groupmass  id
0        0          3   0
1        1          7   1
2        2          4   2
3        0          3   3
4        1          7   4
5        2          4   5
6        0          3   6
7        1          7   7
8        2          4   8
9        0          3   9

如果您经常想使用 groupID 作为 groups 的索引,您可以永久设置它,这样您就不必每次执行此操作时都使用 set_index

下面是仅使用 numpy 设置mass的示例。 它确实使用迭代,因此对于大型数组,它不会很快。

对于仅 10 行,这比pandas等效的要快得多。 但随着数据集变大(例如。M=10000),pandas要好得多。 pandas的设置时间更大,但每行迭代时间要短得多。

生成测试数组:

dt_members = np.dtype({'names':['groupID','groupmass'], 'formats': [int, float]})
dt_groups =  np.dtype({'names':['groupID', 'mass'], 'formats': [int, float]})
N, M = 5, 10
members = np.zeros((M,), dtype=dt_members)    
groups = np.zeros((N,), dtype=dt_groups)
members['groupID'] = np.random.randint(101, 101+N, M)
groups['groupID'] = np.arange(101, 101+N)
groups['mass']  = np.arange(1,N+1)
def getgroup(id):
    idx = id==groups['groupID']
    return groups[idx]
members['groupmass'][:] = [getgroup(id)['mass'] for id in members['groupID']]

python2迭代可以使用map

members['groupmass'] =  map(lambda x: getgroup(x)['mass'], members['groupID'])

我可以通过最小化重复的下标将速度提高约 2 倍,例如。

def setmass(members, groups):
    gmass = groups['mass']
    gid = groups['groupID']
    mass = [gmass[id==gid] for id in members['groupID']]
    members['groupmass'][:] = mass

但是如果groups['groupID']可以映射到arange(N),那么我们可以在速度上获得很大的飞跃。 通过将相同的映射应用于members['groupID'],它变成了一个简单的数组索引问题。

在我的示例数组中,groups['groupID']只是arange(N)+101。 因此,映射只是减去该最小值。

def setmass1(members, groups):
    members['groupmass'][:] = groups['mass'][members['groupID']-groups['groupID'].min()]

这比我以前的代码快 300 倍,比 pandas 解决方案(对于 10000,500 个数组)好 8 倍。

我怀疑pandas做了这样的事情。 pgroups.set_index('groupID').massmass系列,增加了.index属性。 (我可以用更通用的数组来测试这一点)

在更一般的情况下,它可能有助于对groups进行排序,并在必要时填补一些索引空白。


这是一个"矢量化"解决方案 - 没有迭代。 但是它必须计算一个非常大的矩阵(组的长度按成员的长度),所以不会获得太多的速度(np.where是最慢的步骤)。

def setmass2(members, groups):
    idx = np.where(members['groupID'] == groups['groupID'][:,None])
    members['groupmass'][idx[1]] = groups['mass'][idx[0]]

相关内容

最新更新