numpy数组的元素级比较(Python)

cidc1ykv  于 2022-12-18  发布在  Python
关注(0)|答案(2)|浏览(150)

我想问一个关于下面numpy数组的问题。
我有一个数据集,其中包含50 rows and 15 columns,我创建了一个numpy数组,如下所示:

x=x.to_numpy()

我的目标是将每一行与其他行进行比较(元素级的,除了它自己),并找出是否有任何一行的所有值都小于该行。

样表:

a b c         
1 6 2
2 6 8
4 7 12
7 9 13

例如,对于行1和行2,不存在这样的行。但是行3、4存在行1和行2的所有值都小于所有值的行。因此,算法应返回计数2(指示行3和行4)。
应该实现哪段Python代码来获得这个特定的返回。
我已经尝试了一堆代码,但无法达成一个适当的解决方案。所以如果任何人有一个想法,我将不胜感激。

toe95027

toe950271#

只需使用两个循环并进行比较

import numpy as np

def f(x):
    count = 0

    for i in range(x.shape[0]):
        for j in range(x.shape[0]):
            if i == j:
                continue
            if np.all(x[i] > x[j]):
                count += 1
                break

    return count

x = np.array([[1, 6, 2], [2, 6, 8], [4, 7, 12], [7, 9, 13]])
print(f(x))
snvhrwxg

snvhrwxg2#

编辑:纯麻木溶液

(x.reshape(-1, 1, 3) > x.reshape(1, -1, 3)).all(axis=2).any(axis=1).sum()

解释

困难的部分是用三维来思考,所以我从二维开始,用简单的数字比较,假设你有x=np.array([1,2,3,4]),你想把x的所有元素和其他元素进行比较,形成一个4x4的布尔矩阵。
你要做的就是把x的形状改变为一边是一列值,另一边是一条线,所以两个二维数组:一个是4x1,另一个是1x4。
然后,当在这两个阵列之间执行操作时,广播将创建一个4x4阵列。
为了形象化,而不是比较,让我们这样做

x=np.array([1,2,3,4])
x.reshape(-1,1) #is
#[[1],
# [2],
# [3],
# [4]]
x.reshape(1,-1) #is
# [ [1,2,3,4] ]
x.reshape(-1,1)*10+x.reshape(1,-1) #is therefore
# [[11, 12, 13, 14],
#  [21, 22, 23, 24],
#  [31, 32, 33, 34],
#  [41, 42, 43, 44]]

# Likewise 
x.reshape(-1,1)<x.reshape(1,-1) # is
#array([[False,  True,  True,  True],
#       [False, False,  True,  True],
#       [False, False, False,  True],
#       [False, False, False, False]])

所以,我们所要做的是完全相同的事情,但是值是长度为3的一维数组而不是标量:
x.reshape(-1, 1, 3) > x.reshape(1, -1, 3)
广播将使其成为所有x[i]>x[j]的2d数组,就像前面的例子一样,除了x[i]x[j]x[i]>x[j]不是值,而是长度为3的1d数组,所以我们的结果是长度为3的1d数组的2d数组,也就是3d数组。
现在,我们只需要对所有的值求和,如果x[i]被认为是x[j],我们需要x[i]的所有值对x[j]的所有值都是>,因此all在轴2(长度为3的轴)上,现在我们有一个二维矩阵来告诉每个i,j是否是x[i]>x[j]
为了使x[j]有一个更小的对应项,也就是说x[j]至少大于一个x[i],我们需要在x[j]列上至少有一个True,因此any(axis=1)
最后,我们现在有一个一维的布尔数组,如果它至少有一个更小的值,我们只需要计算它们的个数,所以.sum()

复合迭代

单线性(具有一个循环。不理想,但优于2个循环)

sum((r>x).all(axis=1).any() for r in x)


r>x是将行r的每个元素与x的每个元素进行比较的布尔数组。因此,例如,当r是行x[2]时,则r>x

array([[ True,  True,  True],
       [ True,  True,  True],
       [False, False, False],
       [False, False, False]])

所以(r>x).all(axis=1)是一个布尔值的形状(4,)数组,它告诉我们是否每行都是布尔值(因为.all只迭代列,axis=1)是否为True。在前面的示例中,它将为[True, True, False, False](x[1]>x).all(axis=1)将为[False, False, False, False]x[1]>x的第一行包含2个True,但这对于.all是不够的)
因此(r>x).all(axis=1).any()给出了您想知道的内容:如果有任何行的所有列都是True。也就是说,如果前面的数组中有任何True。
((r>x).all(axis=1).any() for r in x)是x的所有行r的此计算的迭代器。如果将外部()替换为[],则会得到TrueFalse的列表(准确地说,如您所说:前两行为False,另外两行为True)。但是这里不需要构建列表,因为我们只想计数。复合迭代器只在调用者需要时才产生结果,这里的调用者是sum
sum((r>x).all(axis=1).any() for r in x)计算我们在前面的计算中得到True的次数。
(In在这个例子中,因为列表中只有4个元素,所以我使用复合迭代器而不是复合列表并不像是节省了很多内存。但是当我们并不需要在内存中构建一个包含所有中间结果的列表时,尝试使用复合迭代器是一个好习惯。)

计时

就你的例子而言,计算纯numpy需要19微秒,计算前一个答案需要48微秒,计算di.bezrukov的答案需要115微秒。
但是,当行数增加时,差异(和不存在差异)就会显现出来,对于10000×3的数据,我的两个答案的计算时间都是3.9秒,而di.bezrukov的方法需要353秒。
这2个事实背后的原因:

  • 事实上,di.bezrukov的区别越来越大,是因为我避免的内部for循环的数量越来越多,它们很重要
  • 事实上,我的两个版本之间的差异消失,是因为我的第二个版本(按时间顺序,这条消息的第一个版本,也就是我的纯麻木版本)只保留外循环。在行数不是很大的地方,这是不可忽略的。但是当行数很大的时候......那么外循环本身(不算它的内容,它是由内部循环优化的)在O(n²)的结果中是O(n),所以,如果n足够大,我们就不关心这个外部循环的效率有多高。
  • 更糟糕的是:记忆方面,这个纯粹 numpy 版本做了我在第一个版本中没有做过的事情:计算一个完整的结果列表。这算不了什么。它还计算一个完整的布尔3D矩阵。这只是中间结果。所以,对于足够大的n(比如100000,除非你有50GB的RAM),中间结果不适合内存。即使你有50GB的RAM,它也不会更快)

然而,如果我们把m称为列数,那么所有3个方法都是O(n²)·O(n²×m)偶数
所有的都有3个嵌套循环,di.bezrukov的有两个显式的python for循环,一个隐式的.all循环(仍然是for循环,即使它是在numpy的内部代码中完成的),我的复合版本有一个python compound for循环,两个隐式的.all.any循环。

我的纯numpy版本没有显式循环,但有3个隐式numpy的嵌套循环(在构建3d数组时)
同样的时间结构。只有numpy的循环更快。
我为我的纯numpy版本感到骄傲,因为我一开始并没有发现它。但是实际上,我的第一个版本(compound)更好。只有在无关紧要的时候(对于非常小的数组),它才更慢。它不消耗任何内存。而且它只会numpy外部循环,这在内部循环之前是可以忽略的。
TL;医生:

sum((r>x).all(axis=1).any() for r in x)

除非你真的只有4行,而μs很重要,或者你正在进行一场比赛,看谁能用最纯粹的 numpy 3D国际象棋思考:D,在这种情况下

(x.reshape(-1, 1, 3) > x.reshape(1, -1, 3)).all(axis=2).any(axis=1).sum()

相关问题