如何在NumPy矩阵中高效地汇总和复制数值

4
在我的工作中,我经常需要聚合和扩展各种量的矩阵,并且我正在寻找最有效的方法来执行这些操作。例如,我将有一个 NxN 的矩阵,我想将其从 NxN 聚合到 PxP,其中 P < N。这是使用较大维度和较小维度之间的对应关系完成的。通常,P 将会在100左右。
例如,我会有一个类似下面这样的假设的 4x4 矩阵(但实际上,我的矩阵会大得多,约为 1000x1000
m=np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])

>>> m
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12],
       [13, 14, 15, 16]])

以此类似的对应关系(示意图):

0 -> 0
1 -> 1
2 -> 0
3 -> 1

我通常将数据存储在字典中。这意味着行和列的索引0和2都被分配给新索引0,而行和列的索引1和3则被分配给新索引1。矩阵可以是任何形式,但当我想要压缩时,对应关系始终是多对一的。

如果输入矩阵为A,输出矩阵为B,那么单元格B[0, 0]将是A[0, 0] + A[0, 2] + A[2, 0] + A[2, 2]的总和,因为新索引0由原始索引0和2组成。

这里的聚合过程将导致:

array([[ 1+3+9+11,  2+4+10+12 ],
       [ 5+7+13+15, 6+8+14+16 ]])
= array([[ 24, 28 ],
         [ 40, 44 ]])

我可以通过创建一个正确大小的空矩阵并循环遍历初始矩阵的所有4x4=16个单元格,并在嵌套循环中累积,但这似乎是低效的,numpy的向量化特性总是被人们强调。我还使用np.ix_创建索引集并使用m[row_indices, col_indices].sum()来完成,但我想知道最高效的numpy方式是什么。

相反,使用对应关系扩展矩阵的明智且高效的方法是什么?例如,使用相同的对应关系但以相反的方式展开时,我将从以下情况开始:

array([[ 1, 2 ],
       [ 3, 4 ]])

to

array([[ 1, 2, 1, 2 ],
       [ 3, 4, 3, 4 ],
       [ 1, 2, 1, 2 ],
       [ 3, 4, 3, 4 ]])

这里的值只是简单地复制到新单元格中。

在尝试聚合方面,我已经使用了一些带有Pandas方法的方法,在索引和列上使用groupby,然后使用例如df.values提取最终矩阵。但是,我不知道如何等效地扩展矩阵,而不使用许多类似unstackjoin等内容。而且,我看到很多人经常说使用Pandas不太时间有效。

编辑1:在评论中有人问我应该如何进行聚合。如果我使用嵌套循环和原始维度与新维度之间的字典查找,则会按以下方式执行:

>>> m=np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])  
>>> mnew=np.zeros((2,2))  
>>> big2small={0:0, 1:1, 2:0, 3:1}  
>>> for i in range(4):
...     inew = big2small[i]
...     for j in range(4):
...         jnew = big2small[j]
...         mnew[inew, jnew] += m[i, j]
...
>>> mnew
array([[24., 28.],
       [40., 44.]])

编辑2: 另一位评论要求在开始时更明确地阐述聚合示例,因此我已经这样做了。


不太清楚您要找什么。您能解释一下聚合是如何发生的吗?对于您的第二个问题,您可以使用 np.tile(arr, [2,2]) 来解决,但我不理解您的要求,因此可能不正确。 - Code Different
你好。因为评论似乎不允许多行代码,或者我不知道怎么做,所以我在问题结尾处添加了一个示例,展示了我如何“长方式”完成它。 - JohnFrum
1
你好。这是一个有趣的问题,但很难准确理解。在你的例子中,能否添加一个中间步骤?也就是说,将 array([[ 24, 28 ], [ 40, 44 ]]) 重写为 array([[ 1+3+9+11, etc ], [ etc,etc ]]) - Stef
我已按要求完成。 - JohnFrum
1个回答

3

假设你的索引没有规则结构,我建议尝试使用稀疏矩阵。

import scipy.sparse as ss
import numpy as np
# your current array of indices
g=np.array([[0,0],[1,1],[2,0],[3,1]])
# a sparse matrix of (data=ones, (row_ind=g[:,0], col_ind=g[:,1]))
# it is one for every pair (g[i,0], g[i,1]), zero elsewhere
u=ss.csr_matrix((np.ones(len(g)), (g[:,0], g[:,1])))

聚合

m=np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
u.T @ m @ u

扩展

m2 = np.array([[1,2],[3,4]])
u @ m2 @ u.T

这非常棒。你说得对,我的聚合指数通常不会有一个漂亮的规则结构。 - JohnFrum
我应该在我的问题中指出4x4只是一个例子,实际上往往会是1000x1000到100x100。在这里进行矩阵乘法是否与其他可能存在的方法相比效率低下?我假设我的暴力嵌套循环方法将是最差的方法之一。 - JohnFrum
2
这应该是高效的,考虑到一对多的映射,它将受到O(len(m)**2)的限制,它与矩阵的副本一样具有可扩展性。 - Bob
非常感谢这个。我会抽出一些时间,使用我的实际矩阵和对应关系来测试这种方法,并查看它在实践中的速度有多快。 - JohnFrum
1
我使用一个1%填充的矩阵(1000,1000)->(100,100)对密集和稀疏解决方案进行了基准测试。[【Colab笔记本】(https://colab.research.google.com/drive/12jQu8SDU3Yl07DS7Bx-IMUZsvZRdtU60?usp=sharing)] - Michael Szczesny
1
我已经使用我的实际代码进行了一些测试,这个方法非常出色。我从未想过使用稀疏矩阵来同时进行扩展和聚合,但它完美地满足了我的需求(就我所知)。 - JohnFrum

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