是否可以按降序使用 argsort ?
- 2025-02-12 10:04:00
- admin 原创
- 52
问题描述:
考虑以下代码:
avgDists = np.array([1, 8, 6, 9, 4])
ids = avgDists.argsort()[:n]
这给出了n
最小元素的索引。是否可以按降序使用相同的索引来获取最高元素argsort
的索引?n
解决方案 1:
如果对数组取反,则最低元素将成为最高元素,反之亦然。因此,n
最高元素的索引为:
(-avgDists).argsort()[:n]
正如评论中提到的,另一种推理方法是观察大元素在 argsort 中排在最后n
。因此,您可以从 argsort 的尾部读取以找到最高元素:
avgDists.argsort()[::-1][:n]
这两种方法的时间复杂度都是O(n log n)argsort
,因为调用是这里的主要项。但第二种方法有一个很好的优势:它用O(1)切片代替了数组的O(n)否定。如果您在循环内处理小数组,那么避免该否定可能会获得一些性能提升,如果您处理大型数组,那么您可以节省内存使用量,因为否定会创建整个数组的副本。
请注意,这些方法并不总是给出等效的结果:如果请求稳定的排序实现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 的评论是不正确的。在反转之前或之后截断对效率没有影响,因为这两个操作只是以不同的方式跨过数组的视图,而不是实际复制数据。
解决方案 2:
就像 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
视图方法明显更快(并且只使用一半的内存...)
解决方案 3:
如果您只需要最低/最高 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)
解决方案 4:
正如@Kammani 所暗示的,更容易解释的实现可能是使用numpy.flip
,如下所示:
import numpy as np
avgDists = np.array([1, 8, 6, 9, 4])
ids = np.flip(np.argsort(avgDists))
print(ids)
通过使用访问者模式而不是成员函数,可以更容易地读取操作顺序。
解决方案 5:
您可以使用翻转命令numpy.flipud()
,或者numpy.fliplr()
在使用命令排序后按降序获取索引argsort
。这就是我通常所做的。
解决方案 6:
您可以创建数组的副本,然后将每个元素乘以 -1。
结果,之前最大的元素将变为最小的元素。
副本中 n 个最小元素的索引是原始数组中 n 个最大的元素。
解决方案 7:
一种优雅的方式可能是这样的 -
ids = np.flip(np.argsort(avgDists))
这将为您提供按降序排序的元素索引。现在您可以使用常规切片...
top_n = ids[:n]
解决方案 8:
以你的例子来说:
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])
解决方案 9:
考虑相等元素的顺序
如果运行排序程序并且 2 个元素相等,则顺序通常不会改变。但是,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)
出于兼容性原因,我更喜欢负数组方法的 argsortarr
。当表示更复杂元素的一些数字表示时,这尤其重要。
例子:
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])
解决方案 10:
另一种方法是在 argsort 的参数中仅使用 '-',例如:“df[np.argsort(-df[:, 0])]”,前提是 df 是数据框,并且您希望按第一列(由列号“0”表示)对其进行排序。根据需要更改列名。当然,列必须是数字。