我正在寻找一种快速的方法来确定两个数组的交叉匹配索引,定义如下。
我有两个非常大的(>1e7 元素)结构化数组,一个称为成员,另一个称为组。这两个数组都有一个组 ID 列。组数组的组 ID 条目是唯一的,成员数组的组 ID 条目不是。
组数组有一个名为 mass 的列。成员数组有一个名为 groupmass 的列(当前为空)。我想将正确的组质量分配给具有与其中一个组匹配的组 ID的成员元素。这将通过以下方式实现:
members['groupmass'][idx_matched_members] = groups['mass'][idx_matched_groups]
所以我需要一个快速的例程来计算两个索引数组idx_matched_members和idx_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').mass
是mass
系列,增加了.index
属性。 (我可以用更通用的数组来测试这一点)
在更一般的情况下,它可能有助于对groups
进行排序,并在必要时填补一些索引空白。
这是一个"矢量化"解决方案 - 没有迭代。 但是它必须计算一个非常大的矩阵(组的长度按成员的长度),所以不会获得太多的速度(np.where
是最慢的步骤)。
def setmass2(members, groups):
idx = np.where(members['groupID'] == groups['groupID'][:,None])
members['groupmass'][idx[1]] = groups['mass'][idx[0]]