可以按降序使用argsort吗?


180

考虑以下代码:

avgDists = np.array([1, 8, 6, 9, 4])
ids = avgDists.argsort()[:n]

这给了我n最小元素的索引。是否可以argsort按降序使用它来获得n最高元素的索引?


3
是不是很简单ids = np.array(avgDists).argsort()[-n:]
海梅(Jaime)

2
@Jaime:不,那是行不通的。“正确答案”是[3, 1, 2]。您的代码行产生了[2, 1, 3](如果以n == 3为例)
dawg

2
@drewk好,那就来吧ids = np.array(avgDists).argsort()[-n:][::-1]。这样做的目的是避免制作整个列表的副本,这是在列表-前面添加时得到的。与OP的小示例无关,可能适用于较大的案例。
海梅(Jaime)

1
@Jaime:你是对的。看到我更新的答案。语法tho与您对结尾片段的评论相反:np.array(avgDists).argsort()[::-1][:n]将执行此操作。另外,如果您要使用numpy,请保留在numpy中。首先将列表转换为数组:avgDist=np.array(avgDists)然后变为avgDist.argsort()[::-1][:n}
dawg

Answers:


228

如果对数组求反,则最低的元素变为最高的元素,反之亦然。因此,n最高元素的索引为:

(-avgDists).argsort()[:n]

评论中所述,对此进行推理的另一种方法是观察大元素在argsort 中排在最后。因此,您可以从argsort的末尾读取以找到n最高的元素:

avgDists.argsort()[::-1][:n]

两种方法的时间复杂度均为O(n log n),因为在此argsort调用是主要项。但是第二种方法有一个很好的优点:它将数组的O(n)取反替换为O(1)切片。如果在循环中使用小型数组,则避免这种求反可能会获得一些性能提升;如果使用大型数组,则可以节省内存使用量,因为这种求反会创建整个数组的副本。

请注意,这些方法并不总是给出相等的结果:如果要求稳定的排序实现(argsort例如,通过传递关键字parameter)kind='mergesort',则第一个策略将保留排序稳定性,但是第二个策略将破坏稳定性(即,位置相等)项目将被撤消)。

时间示例:

使用100个浮点和30个尾巴的小阵列,查看方法快大约15%

>>> avgDists = np.random.rand(100)
>>> n = 30
>>> timeit (-avgDists).argsort()[:n]
1.93 µs ± 6.68 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> timeit avgDists.argsort()[::-1][:n]
1.64 µs ± 3.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> timeit avgDists.argsort()[-n:][::-1]
1.64 µs ± 3.66 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

对于较大的阵列,argsort占主导地位,并且没有明显的时序差异

>>> avgDists = np.random.rand(1000)
>>> n = 300
>>> timeit (-avgDists).argsort()[:n]
21.9 µs ± 51.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> timeit avgDists.argsort()[::-1][:n]
21.7 µs ± 33.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> timeit avgDists.argsort()[-n:][::-1]
21.9 µs ± 37.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

请注意,以下来自nedim的评论不正确。在反转之前还是之后进行截断在效率上没有区别,因为这两个操作都只是以不同的方式遍历数组的视图,而实际上并未复制数据。


14
倒车之前切片的效率更高,即np.array(avgDists).argsort()[:-n][::-1]
nedim

3
如果原始数组包含nans,则这些答案并不相同。在这种情况下,第一个解决方案似乎在结尾处而不是开头处给出了更自然的结果。
feilchenfeldt

1
当需要稳定排序时,这些比较如何?推测切片策略会颠倒相等项吗?
艾瑞克(Eric)

1
@ user3666197我觉得这与答案无关。否定是否创建副本(确实如此)在这里并不重要,相关信息是,计算否定的复杂度为O(n),而取另一个值为O(1)
wim

1
@ user3666197是的,这很不错-如果阵列占用了50%的可用内存,我们当然希望避免复制它并引起交换。我将再次编辑以提及在其中创建了一个副本。
wim

70

就像Python一样,它[::-1]反转了返回的数组argsort()[:n]给出最后n个元素:

>>> avgDists=np.array([1, 8, 6, 9, 4])
>>> n=3
>>> ids = avgDists.argsort()[::-1][:n]
>>> ids
array([3, 1, 2])

这种方法的优点ids是可以看到 avgDists:

>>> ids.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

(“ OWNDATA”为False表示这是一个视图,而不是副本)

另一种方法是这样的:

(-avgDists).argsort()[:n]

问题在于,这种工作方式是为数组中的每个元素创建负数:

>>> (-avgDists)
array([-1, -8, -6, -9, -4])

ANd为此创建了一个副本:

>>> (-avgDists_n).flags['OWNDATA']
True

因此,如果您每次使用非常小的数据集计时:

>>> import timeit
>>> timeit.timeit('(-avgDists).argsort()[:3]', setup="from __main__ import avgDists")
4.2879798610229045
>>> timeit.timeit('avgDists.argsort()[::-1][:3]', setup="from __main__ import avgDists")
2.8372560259886086

查看方法实质上更快(并使用1/2的内存...)


4
这个答案很好,但是我觉得您的措辞歪曲了真实的性能特征:“即使使用非常小的数据集,视图方法也明显更快”。实际上,否定是O(n),而argsort是O(n log n)。这意味着对于较大的数据集,时间差异将减小-O(n log n)项占主导地位,但是您的建议是对O(n)部分进行优化。因此,复杂度保持不变,尤其是对于这个小的数据集,我们看到任何明显的不同。
2015年

2
渐近等效的复杂度仍然可以表示一种算法的渐近速度是另一种算法的两倍。抛弃这些区别可能会产生后果。例如,即使时间差异(以百分比为单位)确实接近0,我还是愿意打赌,取反的算法仍会使用两倍的内存。
错误

@bug可以,但是在这种情况下不可以。我在回答中添加了一些时间安排。数字表明,对于较大的数组,这些方法具有相似的时序,这支持argsort是主导的假设。对于否定,我想您对内存使用情况是正确的,但是如果用户关心nan的位置和/或需要稳定的排序,则用户可能仍会更喜欢。
维姆


5

如果您只需要最低/最高n个元素的索引,则np.argsort可以使用np.argpartition- 来代替使用。

这不需要对整个数组进行排序,而只需要排序所需的部分,但请注意,“分区内的顺序”是未定义的,因此尽管它提供了正确的索引,但它们可能未正确排序:

>>> avgDists = [1, 8, 6, 9, 4]
>>> np.array(avgDists).argpartition(2)[:2]  # indices of lowest 2 items
array([0, 4], dtype=int64)

>>> np.array(avgDists).argpartition(-2)[-2:]  # indices of highest 2 items
array([1, 3], dtype=int64)

或者,如果同时使用argsort和argpartition,则必须对argpartition操作执行该操作。
demongolem

3

您可以创建数组的副本,然后将每个元素乘以-1。
结果,之前最大的元素将变成最小的元素。
副本中n个最小元素的索引是原件中的n个最大元素。


如其他答案所述,这很容易取反数组:-array
onofricamila

2

就像@Kanmani暗示的那样,可以使用来简化解释numpy.flip,如下所示:

import numpy as np

avgDists = np.array([1, 8, 6, 9, 4])
ids = np.flip(np.argsort(avgDists))
print(ids)

通过使用访问者模式而不是成员函数,可以更轻松地读取操作顺序。


1

以您的示例为例:

avgDists = np.array([1, 8, 6, 9, 4])

获得n个最大值的索引:

ids = np.argpartition(avgDists, -n)[-n:]

按降序对它们进行排序:

ids = ids[np.argsort(avgDists[ids])[::-1]]

获得结果(n = 4):

>>> avgDists[ids]
array([9, 8, 6, 4])

-1

另一种方法是在argsort的参数中仅使用“-”,例如:“ df [np.argsort(-df [:, 0])]”,前提是df是数据帧,并且您想按第一个对它进行排序列(由列号“ 0”表示)。适当更改列名。当然,该列必须是数字列。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.