考虑下面的代码:
avgDists = np.array([1, 8, 6, 9, 4]) ids = avgDists.argsort()[:n]
这给出了n最小元素的索引。是否可以使用相同的argsort降序来获得n最高元素的下标?
n
argsort
就像Python一样,[::-1]反转了argsort()返回的数组,而[:n]给出了最后n个元素:
[::-1]
argsort()
[:n]
>>> avgDists=np.array([1, 8, 6, 9, 4]) >>> n=3 >>> ids = avgDists.argsort()[::-1][:n] >>> ids array([3, 1, 2])
这个方法的优点是ids是avgDists的视图:
ids
>>> 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
view方法基本上更快(并且使用1/2的内存…)
如果对数组求反,最低的元素就变成最高的元素,反之亦然。因此,n最高元素的索引为:
另一种推理方法,正如评论中提到的,是观察到argsort中的大元素来自最后的。因此,你可以从argsort的尾部读取,找到n最高的元素:
avgDists.argsort()[::-1][:n]
这两个方法在时间复杂度上都是O(n log n),因为argsort调用是这里的主要术语。但是第二种方法有一个很好的优势:它将数组的O (n)否定替换为O (1)切片。如果在循环中使用的是小型数组,那么避免这种否定可能会获得一些性能收益;如果使用的是大型数组,那么可以节省内存使用,因为否定会创建整个数组的副本。
注意,这些方法并不总是给出等价的结果:如果请求argsort实现一个稳定的排序,例如通过传递关键字参数kind='mergesort',那么第一种策略将保持排序稳定性,但第二种策略将破坏稳定性(即相等项的位置将颠倒)。
kind='mergesort'
< em >示例计时:< / em >
使用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的评论是不正确的。在反转之前还是反转之后截断对效率没有影响,因为这两种操作只是对数组的视图进行不同的跨步操作,而不是实际复制数据。
如果你只需要最低/最高n个元素的索引,你可以使用np.argpartition代替np.argsort。
np.argpartition
np.argsort
这并不需要对整个数组进行排序,只需要对你需要的部分进行排序,但请注意“分区内的顺序”是未定义的,所以虽然它给出了正确的索引,但它们的顺序可能并不正确:
>>> 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)
可以使用翻转命令numpy.flipud()或numpy.fliplr()在使用argsort命令排序后按降序获取索引。那是我通常做的事。
numpy.flipud()
numpy.fliplr()
另一种方法是在argsort的参数中只使用一个'-',例如:"df[np。Argsort (-df[:, 0])]",如果df是数据帧,你想要按第一列排序(由列号'0'表示)。适当地更改列名。当然,列必须是数字。
用你的例子:
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])
正如@Kanmani所暗示的,更容易解释的实现可以使用numpy.flip,如下所示:
numpy.flip
import numpy as np avgDists = np.array([1, 8, 6, 9, 4]) ids = np.flip(np.argsort(avgDists)) print(ids)
通过使用访问者模式而不是成员函数,可以更容易地读取操作的顺序。
一种优雅的方式可以如下-
ids = np.flip(np.argsort(avgDists))
top_n = ids[:n]
如果你运行一个排序程序并且两个元素相等,那么顺序通常不会改变。然而,flip/[::-1]方法改变相等元素的顺序。
>>> arr = np.array([3, 5, 4, 7, 3]) >>> >>> np.argsort(arr)[::-1] array([3, 1, 2, 4, 0]) # equal elements reorderd >>> np.argsort(-arr) array([3, 1, 2, 0, 4]) # equal elements not reorderd (compatible to other sorting)
由于兼容性的原因,我因此更喜欢对负数组进行Argsort的方法。当arr表示更复杂元素的数字表示时,这一点尤其相关。
arr
例子:
obj = ['street', 'house', 'bridge', 'station', 'rails'] arr = np.array([3, 5, 4, 7, 3]) # cost of obj in coins
声明:一个更常见的方法是用sorted(list_of_tuples_obj_cost, key=lambda x: x[1])来解决上面的例子
sorted(list_of_tuples_obj_cost, key=lambda x: x[1])