Python NumPy—按频率快速稳定的大型阵列arg排序
我有一个大的1D数组Python NumPy—按频率快速稳定的大型阵列arg排序,python,arrays,numpy,sorting,frequency,Python,Arrays,Numpy,Sorting,Frequency,我有一个大的1D数组a,它包含任何可比较的dtype,它的一些元素可能会重复 如何找到排序索引ix,它将按降序/升序的值频率对a进行稳定排序(稳定) 我想找到最快最简单的方法。也许现有的标准numpy函数可以做到这一点 还有一个相关的,但它特别要求删除重复的数组,即只输出唯一的排序值,我需要原始数组的所有值,包括重复的值 我已经编写了我的第一次尝试来完成这项任务,但它不是最快的(使用Python的循环),也可能不是最短/最简单的形式。如果相等元素的重复次数不高且数组很大,那么python循环可能
a
,它包含任何可比较的dtype
,它的一些元素可能会重复
如何找到排序索引ix
,它将按降序/升序的值频率对a
进行稳定排序(稳定)
我想找到最快最简单的方法。也许现有的标准numpy函数可以做到这一点
还有一个相关的,但它特别要求删除重复的数组,即只输出唯一的排序值,我需要原始数组的所有值,包括重复的值
我已经编写了我的第一次尝试来完成这项任务,但它不是最快的(使用Python的循环),也可能不是最短/最简单的形式。如果相等元素的重复次数不高且数组很大,那么python循环可能非常昂贵。如果在NumPy中有短函数来完成这一切也很好(例如,virtualnp.argsort\u by_freq()
)
产出:
rows: i_col(0) / original_a(1) / freqs(2) / sorted_a(3)
/ sorted_freqs(4) / sorting_ix(5)
[[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
[ 1 1 1 1 3 0 5 0 3 1 1 0 0 4 6 1 3 5 5 0 0 0 5 0]
[ 7 7 7 7 3 8 4 8 3 7 7 8 8 1 1 7 3 4 4 8 8 8 4 8]
[ 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 5 5 5 5 3 3 3 4 6]
[ 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 4 4 4 4 3 3 3 1 1]
[ 5 7 11 12 19 20 21 23 0 1 2 3 9 10 15 6 17 18 22 4 8 16 13 14]]
equal_order_by_val: False
i_col 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
original_a g g c f d d g a a a f f f g f c f a e b g d c b f
freqs 5 5 3 7 3 3 5 4 4 4 7 7 7 5 7 3 7 4 1 2 5 3 3 2 7
sorted_a f f f f f f f g g g g g a a a a c d d c d c b b e
sorted_freqs 7 7 7 7 7 7 7 5 5 5 5 5 4 4 4 4 3 3 3 3 3 3 2 2 1
sorting_ix 3 10 11 12 14 16 24 0 1 6 13 20 7 8 9 17 2 4 5 15 21 22 19 23 18
equal_order_by_val: True
i_col 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
original_a g g c f d d g a a a f f f g f c f a e b g d c b f
freqs 5 5 3 7 3 3 5 4 4 4 7 7 7 5 7 3 7 4 1 2 5 3 3 2 7
sorted_a f f f f f f f g g g g g a a a a c c c d d d b b e
sorted_freqs 7 7 7 7 7 7 7 5 5 5 5 5 4 4 4 4 3 3 3 3 3 3 2 2 1
sorting_ix 3 10 11 12 14 16 24 0 1 6 13 20 7 8 9 17 2 15 22 4 5 21 19 23 18
我可能遗漏了什么,但似乎使用
计数器
可以根据元素值的计数对每个元素的索引进行排序,然后使用元素值和索引来打破关系。例如:
from collections import Counter
a = [ 1, 1, 1, 1, 3, 0, 5, 0, 3, 1, 1, 0, 0, 4, 6, 1, 3, 5, 5, 0, 0, 0, 5, 0]
counts = Counter(a)
t = [(counts[v], v, i) for i, v in enumerate(a)]
t.sort()
print([v[2] for v in t])
t.sort(reverse=True)
print([v[2] for v in t])
输出:
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[23, 21, 20, 19, 12, 11, 7, 5, 15, 10, 9, 3, 2, 1, 0, 22, 18, 17, 6, 16, 8, 4, 14, 13]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 14, 13]
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 13, 14]
如果您希望使用计数相等的组来保持索引的升序,则可以使用lambda函数进行降序排序:
t.sort(key = lambda x:(-x[0],-x[1],x[2]))
print([v[2] for v in t])
输出:
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[23, 21, 20, 19, 12, 11, 7, 5, 15, 10, 9, 3, 2, 1, 0, 22, 18, 17, 6, 16, 8, 4, 14, 13]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 14, 13]
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 13, 14]
如果要在元素计数相同的情况下保持元素在数组中最初出现的顺序,则不按值排序,而是按元素在数组中第一次出现的索引排序:
a = [ 1, 1, 1, 1, 3, 0, 5, 0, 3, 1, 1, 0, 0, 4, 6, 1, 3, 5, 5, 0, 0, 0, 5, 0]
counts = Counter(a)
idxs = {}
t = []
for i, v in enumerate(a):
if not v in idxs:
idxs[v] = i
t.append((counts[v], idxs[v], i))
t.sort()
print([v[2] for v in t])
t.sort(key = lambda x:(-x[0],x[1],x[2]))
print([v[2] for v in t])
输出:
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[23, 21, 20, 19, 12, 11, 7, 5, 15, 10, 9, 3, 2, 1, 0, 22, 18, 17, 6, 16, 8, 4, 14, 13]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 14, 13]
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 13, 14]
要根据计数排序,然后在数组中定位,根本不需要值或第一个索引:
from collections import Counter
a = [ 1, 1, 1, 1, 3, 0, 5, 0, 3, 1, 1, 0, 0, 4, 6, 1, 3, 5, 5, 0, 0, 0, 5, 0]
counts = Counter(a)
t = [(counts[v], i) for i, v in enumerate(a)]
t.sort()
print([v[1] for v in t])
t.sort(key = lambda x:(-x[0],x[1]))
print([v[1] for v in t])
这将为您的字符串数组生成与前面的示例数据代码相同的输出:
a = ['g', 'g', 'c', 'f', 'd', 'd', 'g', 'a', 'a', 'a', 'f', 'f', 'f',
'g', 'f', 'c', 'f', 'a', 'e', 'b', 'g', 'd', 'c', 'b', 'f' ]
这将产生以下输出:
[18, 19, 23, 2, 4, 5, 15, 21, 22, 7, 8, 9, 17, 0, 1, 6, 13, 20, 3, 10, 11, 12, 14, 16, 24]
[3, 10, 11, 12, 14, 16, 24, 0, 1, 6, 13, 20, 7, 8, 9, 17, 2, 4, 5, 15, 21, 22, 19, 23, 18]
我可能遗漏了什么,但似乎使用
计数器
可以根据元素值的计数对每个元素的索引进行排序,然后使用元素值和索引来打破关系。例如:
from collections import Counter
a = [ 1, 1, 1, 1, 3, 0, 5, 0, 3, 1, 1, 0, 0, 4, 6, 1, 3, 5, 5, 0, 0, 0, 5, 0]
counts = Counter(a)
t = [(counts[v], v, i) for i, v in enumerate(a)]
t.sort()
print([v[2] for v in t])
t.sort(reverse=True)
print([v[2] for v in t])
输出:
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[23, 21, 20, 19, 12, 11, 7, 5, 15, 10, 9, 3, 2, 1, 0, 22, 18, 17, 6, 16, 8, 4, 14, 13]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 14, 13]
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 13, 14]
如果您希望使用计数相等的组来保持索引的升序,则可以使用lambda函数进行降序排序:
t.sort(key = lambda x:(-x[0],-x[1],x[2]))
print([v[2] for v in t])
输出:
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[23, 21, 20, 19, 12, 11, 7, 5, 15, 10, 9, 3, 2, 1, 0, 22, 18, 17, 6, 16, 8, 4, 14, 13]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 14, 13]
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 13, 14]
如果要在元素计数相同的情况下保持元素在数组中最初出现的顺序,则不按值排序,而是按元素在数组中第一次出现的索引排序:
a = [ 1, 1, 1, 1, 3, 0, 5, 0, 3, 1, 1, 0, 0, 4, 6, 1, 3, 5, 5, 0, 0, 0, 5, 0]
counts = Counter(a)
idxs = {}
t = []
for i, v in enumerate(a):
if not v in idxs:
idxs[v] = i
t.append((counts[v], idxs[v], i))
t.sort()
print([v[2] for v in t])
t.sort(key = lambda x:(-x[0],x[1],x[2]))
print([v[2] for v in t])
输出:
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[23, 21, 20, 19, 12, 11, 7, 5, 15, 10, 9, 3, 2, 1, 0, 22, 18, 17, 6, 16, 8, 4, 14, 13]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 14, 13]
[13, 14, 4, 8, 16, 6, 17, 18, 22, 0, 1, 2, 3, 9, 10, 15, 5, 7, 11, 12, 19, 20, 21, 23]
[5, 7, 11, 12, 19, 20, 21, 23, 0, 1, 2, 3, 9, 10, 15, 6, 17, 18, 22, 4, 8, 16, 13, 14]
要根据计数排序,然后在数组中定位,根本不需要值或第一个索引:
from collections import Counter
a = [ 1, 1, 1, 1, 3, 0, 5, 0, 3, 1, 1, 0, 0, 4, 6, 1, 3, 5, 5, 0, 0, 0, 5, 0]
counts = Counter(a)
t = [(counts[v], i) for i, v in enumerate(a)]
t.sort()
print([v[1] for v in t])
t.sort(key = lambda x:(-x[0],x[1]))
print([v[1] for v in t])
这将为您的字符串数组生成与前面的示例数据代码相同的输出:
a = ['g', 'g', 'c', 'f', 'd', 'd', 'g', 'a', 'a', 'a', 'f', 'f', 'f',
'g', 'f', 'c', 'f', 'a', 'e', 'b', 'g', 'd', 'c', 'b', 'f' ]
这将产生以下输出:
[18, 19, 23, 2, 4, 5, 15, 21, 22, 7, 8, 9, 17, 0, 1, 6, 13, 20, 3, 10, 11, 12, 14, 16, 24]
[3, 10, 11, 12, 14, 16, 24, 0, 1, 6, 13, 20, 7, 8, 9, 17, 2, 4, 5, 15, 21, 22, 19, 23, 18]
我只是想,对于任何数据类型,我自己可能都可以使用非常快速的解决方案,只使用numpy函数,而不使用python循环,它可以在
O(N log N)
时间内工作。使用的numpy函数:np.unique
、np.argsort
和数组索引
虽然在最初的问题中没有被问到,但我实现了额外的标志equal_order_by_val
,如果它为False,则具有相同频率的数组元素被排序为相等的稳定范围,这意味着可能会有cdc
输出,就像下面的输出转储一样,因为这是元素在原始数组中以相同频率移动的顺序。当flag为True时,这些元素将按原始数组的值进行额外排序,从而产生cdd
。换句话说,如果为False,我们只按键freq
进行稳定排序,如果为True,我们按(freq,value)
进行升序排序,按(-freq,value)
进行降序排序
我只是想,对于任何数据类型,我自己可能都可以使用非常快速的解决方案,只使用numpy函数,而不使用python循环,它可以在
O(N log N)
时间内工作。使用的numpy函数:np.unique
、np.argsort
和数组索引
虽然在最初的问题中没有被问到,但我实现了额外的标志equal_order_by_val
,如果它为False,则具有相同频率的数组元素被排序为相等的稳定范围,这意味着可能会有cdc
输出,就像下面的输出转储一样,因为这是元素在原始数组中以相同频率移动的顺序。当flag为True时,这些元素将按原始数组的值进行额外排序,从而产生cdd
。换句话说,如果为False,我们只按键freq
进行稳定排序,如果为True,我们按(freq,value)
进行升序排序,按(-freq,value)
进行降序排序
你当前的解决方案有什么问题?@Nick我在上面写下了原因:1)它不是最快的(使用纯python循环)2)可能不是最短的3)重要的是降序不稳定(),但是稳定性问题我刚刚在一分钟前解决了。那么你对这个问题的预期输出是什么?Nick在最后一个输出示例中,我需要在第3行的末尾
46
而不是64
,因为它们的频率相同,并且在原始数组中的顺序是46
。但稳定性不是问题,我已经解决了它,就像上面的评论一样,只是对否定值进行argsort排序。我会修正我的问题,不让人们担心稳定性。重要的是我想要最快的解决方案(没有Python循环)和尽可能最短的解决方案。@Nick如果数组很大,并且其中重复的元素很少,那么这个Python循环将花费很长时间。您当前的解决方案有什么问题吗?@Nick我在上面写了原因:1)它不是最快的(使用纯Python循环)2)对于降序,可能不是最短的3)和重要的不稳定(),但稳定性我一分钟前刚刚解决了。那么你的答案是什么