223

考虑以下代码:

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

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

4

9 回答 9

295

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

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

正如评论中提到的,另一种推理方式是观察大元素在 argsort中排在最后。因此,您可以从 argsort 的尾部读取以找到n最高元素:

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

这两种方法的时间复杂度都是O(n log n),因为argsort调用是这里的主要术语。但是第二种方法有一个很好的优势:它将数组的O(n)否定替换为O(1)切片。如果您在循环内使用小数组,那么您可能会从避免这种否定中获得一些性能提升,如果您使用的是大型数组,那么您可以节省内存使用量,因为否定会创建整个数组的副本。

请注意,这些方法并不总是给出相同的结果:如果请求稳定的排序实现argsort,例如通过传递关键字参数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 的评论不正确。是否在反转之前或之后截断对效率没有影响,因为这两个操作都只是以不同的方式跨越数组视图,而不是实际复制数据。

于 2013-05-10T16:00:38.727 回答
87

就像 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])

并为此创建一个副本:

>>> (-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 的内存......)

于 2013-05-10T16:00:21.000 回答
8

如果您只需要最低/最高 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)
于 2017-01-19T02:40:25.620 回答
7

您可以使用翻转命令numpy.flipud()numpy.fliplr()使用命令排序后按降序获取索引argsort。这就是我通常做的。

于 2017-02-24T01:25:34.780 回答
6

正如@Kanmani 暗示的那样,可以使用更易于解释的实现numpy.flip,如下所示:

import numpy as np

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

通过使用访问者模式而不是成员函数,更容易阅读操作顺序。

于 2019-06-26T19:01:49.693 回答
4

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

于 2013-05-10T16:01:38.460 回答
2

用你的例子:

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])
于 2017-08-21T16:39:26.380 回答
2

一种优雅的方式可能如下 -

ids = np.flip(np.argsort(avgDists))

这将为您提供按降序排序的元素索引。现在您可以使用常规切片...

top_n = ids[:n]
于 2020-08-25T14:46:50.780 回答
-1

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

于 2017-08-06T14:00:25.413 回答