处理包含重复值的numpy数组排名
我有一个包含浮点数或整数的numpy数组,想把它的元素转换成它们的排名。
如果数组里没有重复的数字,可以用下面的代码来解决这个问题:
In [49]: a1
Out[49]: array([ 0.1, 5.1, 2.1, 3.1, 4.1, 1.1, 6.1, 8.1, 7.1, 9.1])
In [50]: a1.argsort().argsort()
Out[50]: array([0, 5, 2, 3, 4, 1, 6, 8, 7, 9])
现在我想把这个方法扩展到可能有重复数字的数组,这样重复的数字就能映射到同一个值。比如,我想把数组 a:
a2 = np.array([0.1, 1.1, 2.1, 3.1, 4.1, 1.1, 6.1, 7.1, 7.1, 1.1])
映射成下面的任意一种:
0 1 4 5 6 1 7 8 8 1
或者:
0 3 4 5 6 3 7 9 9 3
或者:
0 2 4 5 6 2 7 8.5 8.5 2
在第一种和第二种情况下,我们把重复的数字映射到它们中的最小排名或最大排名,如果我们直接用 a2.argsort().argsort()。第三种情况就是前两种的平均值。
有什么建议吗?
编辑(效率要求)
在最开始的描述中,我忘了提到时间要求。我希望能找到一个使用numpy或scipy函数的解决方案,这样可以避免“纯Python的开销”。为了让大家更清楚,考虑一下Richard提出的解决方案,虽然解决了问题,但速度比较慢:
def argsortdup(a1):
sorted = np.sort(a1)
ranked = []
for item in a1:
ranked.append(sorted.searchsorted(item))
return np.array(ranked)
In [86]: a2 = np.array([ 0.1, 1.1, 2.1, 3.1, 4.1, 1.1, 6.1, 7.1, 7.1, 1.1])
In [87]: %timeit a2.argsort().argsort()
1000000 loops, best of 3: 1.55 us per loop
In [88]: %timeit argsortdup(a2)
10000 loops, best of 3: 25.6 us per loop
In [89]: a = np.arange(0.1, 1000.1)
In [90]: %timeit a.argsort().argsort()
10000 loops, best of 3: 24.5 us per loop
In [91]: %timeit argsortdup(a)
1000 loops, best of 3: 1.14 ms per loop
In [92]: a = np.arange(0.1, 10000.1)
In [93]: %timeit a.argsort().argsort()
1000 loops, best of 3: 303 us per loop
In [94]: %timeit argsortdup(a)
100 loops, best of 3: 11.9 ms per loop
从上面的分析可以看出,argsortdup的速度比a.argsort().argsort()慢30到50倍。主要原因是使用了Python的循环和列表。
3 个回答
这里有一个函数,可以返回你想要的结果(在第一种情况下)
def argsortdup(a1):
sorted = sort(a1)
ranked = []
for item in a1:
ranked.append(sorted.searchsorted(item))
return array(ranked)
基本上,你先对数据进行排序,然后再查找这个项目的位置。假设有重复项,它会返回第一个出现的位置。我用你的a2示例测试了一下,做了类似这样的操作
a3 = argsortdup(a2)
结果是
array([0, 1, 4, 5, 6, 1, 7, 8, 8, 1])
"用a2测试":
>>> a2
array([ 0.1, 1.1, 2.1, 3.1, 4.1, 1.1, 6.1, 7.1, 7.1, 1.1])
>>> def argsortdup(a1):
... sorted = sort(a1)
... ranked = []
... for item in a1:
... ranked.append(sorted.searchsorted(item))
... return array(ranked)
...
>>> a3 = argsortdup(a2)
>>> a2
array([ 0.1, 1.1, 2.1, 3.1, 4.1, 1.1, 6.1, 7.1, 7.1, 1.1])
>>> a3
array([0, 1, 4, 5, 6, 1, 7, 8, 8, 1])
>>>
你可以通过使用 unique
和 bincount
来达到不错的效果:
>>> u, v = np.unique(a2, return_inverse=True)
>>> (np.cumsum(np.bincount(v)) - 1)[v]
array([0, 3, 4, 5, 6, 3, 7, 9, 9, 3])
或者,如果你想要最小的排名:
>>> (np.cumsum(np.concatenate(([0], np.bincount(v)))))[v]
array([0, 1, 4, 5, 6, 1, 7, 8, 8, 1])
通过给 bincount
提供箱子的数量,可以稍微提高速度:
(np.cumsum(np.bincount(v, minlength=u.size)) - 1)[v]
在按照@WarrenWeckesser的建议升级到最新版本的scipy
后,发现scipy.stats.rankdata
的速度比scipy.stats.mstats.rankdata
和np.searchsorted
都快,尤其是在处理较大的数组时,scipy.stats.rankdata
是最快的选择。
In [1]: import numpy as np
In [2]: from scipy.stats import rankdata as rd
...: from scipy.stats.mstats import rankdata as rd2
...:
In [3]: array = np.arange(0.1, 1000000.1)
In [4]: %timeit np.searchsorted(np.sort(array), array)
1 loops, best of 3: 385 ms per loop
In [5]: %timeit rd(array)
10 loops, best of 3: 109 ms per loop
In [6]: %timeit rd2(array)
1 loops, best of 3: 205 ms per loop