问题描述
让我们假设如果形状为A
的{{1}}和形状为(100,)
的{{1}},则我们有一个数组B
。两者都包含[0,1]中的值。
如何使(10,)
中的元素数大于A
中的每个值?我期望形状为B
,其中第一个元素是“ (10,)
中有多少大于A
”,第二个元素是“ B[0]
中有多少大于A
”,等等...
不使用循环。
我尝试了以下操作,但没有成功:
B[1]
Python不会将我的函数用作import numpy as np
import numpy.random as rdm
A = rdm.rand(100)
B = np.linspace(0,1,10)
def occ(z: float) ->float:
return np.count_nonzero(A > z)
occ(B)
上的标量函数,这就是为什么我得到:
B
我也尝试过使用operands Could not be broadcast together with shapes (10,) (100,)
,但是我遇到了同样的问题...
解决方法
缓慢但简单
如果您不理解该错误消息,则它是神秘的,但它告诉您该怎么做。数组维是broadcast在一起,方法是从右边缘开始将它们对齐。如果将操作分为两部分,这将特别有用:
-
创建一个
(100,10)
遮罩,以显示A
的哪些元素大于B
的哪些元素:mask = A[:,None] > B
-
沿与
A
对应的轴上一次操作的结果求和:result = np.count_nonzero(mask,axis=0)
OR
result = np.sum(mask,axis=0)
这可以写成单线:
(A[:,None] > B).sum(0)
OR
np.count_nonzero(A[:,None] > B,axis=0)
您可以切换尺寸并将B
放置在第一个轴上以获得相同的结果:
(A > B[:,None]).sum(1)
快速而优雅
采用完全不同(但可能效率更高)的方法,您可以使用np.searchsorted
:
A.sort()
result = A.size - np.searchsorted(A,B)
默认情况下,searchsorted
返回将B
的每个元素插入到A
处的左索引。这几乎可以立即告诉您A
有多少个元素大于该数量。
基准
这里,这些算法的标签如下:
-
B0
:(A[:,None] > B).sum(0)
-
B1
:(A > B[:,None]).sum(1)
-
HH
:np.cumsum(np.histogram(A,bins=B)[0][::-1])[::-1]
-
SS
:A.sort(); A.size - np.searchsorted(A,B)
+--------+--------+----------------------------------------+
| A.size | B.size | Time (B0 / B1 / HH / SS) |
+--------+--------+----------------------------------------+
| 100 | 10 | 20.9 µs / 15.7 µs / 68.3 µs / 8.87 µs |
+--------+--------+----------------------------------------+
| 1000 | 10 | 118 µs / 57.2 µs / 139 µs / 17.8 µs |
+--------+--------+----------------------------------------+
| 10000 | 10 | 987 µs / 288 µs / 1.23 ms / 131 µs |
+--------+--------+----------------------------------------+
| 100000 | 10 | 9.48 ms / 2.77 ms / 13.4 ms / 1.42 ms |
+--------+--------+----------------------------------------+
| 100 | 100 | 70.7 µs / 63.8 µs / 71 µs / 11.4 µs |
+--------+--------+----------------------------------------+
| 1000 | 100 | 518 µs / 388 µs / 148 µs / 21.6 µs |
+--------+--------+----------------------------------------+
| 10000 | 100 | 4.91 ms / 2.67 ms / 1.22 ms / 137 µs |
+--------+--------+----------------------------------------+
| 100000 | 100 | 57.4 ms / 35.6 ms / 13.5 ms / 1.42 ms |
+--------+--------+----------------------------------------+
内存布局很重要。 B1
总是比B0
快。发生这种情况是因为求和连续的(缓存的)元素(沿C顺序的最后一个轴)总是比跳过所有行来获取下一个元素要快。对于B
的较小值,广播效果很好。请记住,B0
和B1
的时间和空间复杂度均为O(A.size * B.size)
。两种直方图解决方案的复杂度应该约为O(A.size * log(A.size))
,但是SS
的实现效率要比HH
高得多,因为它可以承担关于数据的更多事情。
我认为您可以使用np.histogram来完成这项工作
A = rdm.rand(100)
B = np.linspace(0,1,10)
np.histogram(A,bins=B)[0]
提供输出
array([10,9,8,11,14,10,12,17])
B[9]
将始终为空,因为没有值> 1。
然后向后计算总和
np.cumsum(np.histogram(A,bins=B)[0][::-1])[::-1]
输出
array([100,90,81,73,62,53,39,29,17])
,
np.sum(A>B.reshape((-1,1)),axis=1)
说明
需要了解broadcasting并为此重塑。通过将B重塑为形状(len(B),1),可以将其与A一起广播,以生成具有形状(len(B),len(A))的数组,其中包含所有比较。然后,您沿轴1(沿A)求和。
换句话说,A < B
不起作用,因为A
有100个条目,而B有10个条目。如果您阅读broadcasting rules,则会看到numpy将以最后一个开始尺寸,如果尺寸相同,则可以一对一比较。如果两者之一是1
,则该维度将被拉伸或“复制”以匹配另一个维度。如果它们不相等并且没有一个等于1
,则失败。
举一个简短的例子:
A = np.array([0.5112744,0.21696187,0.14710105,0.98581087,0.50053359,0.54954654,0.81217522,0.50009166,0.42990167,0.56078499])
B = array([0.25,0.5,0.75])
(A>B.reshape((-1,1)))
的转置(出于可读性)
np.array([[ True,True,False],[False,False,[ True,True],False]])
和np.sum(A>B.reshape((-1,axis=1)
是
array([8,7,2])