在Python中计算列表的秩向量的有效方法

时间:2010-06-18 16:27:08

标签: python list sorting ranking

我正在寻找一种有效的方法来计算Python中列表的等级向量,类似于R的rank函数。在元素之间没有联系的简单列表中,列表l的等级向量的元素 i 应该是 x 当且仅当{{1} }是排序列表中的 x -th元素。到目前为止,这很简单,以下代码片段可以解决这个问题:

l[i]
然而,如果原始列表具有联系(即具有相同值的多个元素),则事情变得复杂。在这种情况下,具有相同值的所有元素应该具有相同的等级,这是使用上述朴素方法获得的等级的平均值。所以,例如,如果我有def rank_simple(vector): return sorted(range(len(vector)), key=vector.__getitem__) ,那么天真的排名会给我[1, 2, 3, 3, 3, 4, 5],但我想要的是[0, 1, 2, 3, 4, 5, 6]。在Python中哪一个是最有效的方法?


脚注:我不知道NumPy是否已经有了实现这一目标的方法;如果确实如此,请告诉我,但无论如何我都会对纯Python解决方案感兴趣,因为我正在开发一个无NumPy的工具。

12 个答案:

答案 0 :(得分:55)

使用scipy,你正在寻找的功能是scipy.stats.rankdata:

In [13]: import scipy.stats as ss
In [19]: ss.rankdata([3, 1, 4, 15, 92])
Out[19]: array([ 2.,  1.,  3.,  4.,  5.])

In [20]: ss.rankdata([1, 2, 3, 3, 3, 4, 5])
Out[20]: array([ 1.,  2.,  4.,  4.,  4.,  6.,  7.])

排名从1开始,而不是0(如您的示例中所示),但是再次,这就是R的{​​{1}}函数的工作方式。

这是一个纯粹的python等价的rank的rankdata函数:

scipy

答案 1 :(得分:4)

这是我为计算排名所写的功能之一。

def calculate_rank(vector):
  a={}
  rank=1
  for num in sorted(vector):
    if num not in a:
      a[num]=rank
      rank=rank+1
  return[a[i] for i in vector]

输入:

calculate_rank([1,3,4,8,7,5,4,6])

输出:

[1, 2, 3, 7, 6, 4, 3, 5]

答案 2 :(得分:3)

这不会给出您指定的确切结果,但也许它会有用。以下代码段为每个元素提供第一个索引,产生最终的等级向量[0, 1, 2, 2, 2, 5, 6]

def rank_index(vector):
    return [vector.index(x) for x in sorted(range(n), key=vector.__getitem__)]

您自己的测试必须证明这一点的效率。

答案 3 :(得分:2)

有一个名为Ranking http://pythonhosted.org/ranking/的非常好的模块,其中包含一个易于遵循的说明页面。要下载,只需使用easy_install ranking

即可

答案 4 :(得分:2)

以下是unutbu代码的一小部分变体,包括绑定排名值的可选“方法”参数。

def rank_simple(vector):
    return sorted(range(len(vector)), key=vector.__getitem__)

def rankdata(a, method='average'):
    n = len(a)
    ivec=rank_simple(a)
    svec=[a[rank] for rank in ivec]
    sumranks = 0
    dupcount = 0
    newarray = [0]*n
    for i in xrange(n):
        sumranks += i
        dupcount += 1
        if i==n-1 or svec[i] != svec[i+1]:
            for j in xrange(i-dupcount+1,i+1):
                if method=='average':
                    averank = sumranks / float(dupcount) + 1
                    newarray[ivec[j]] = averank
                elif method=='max':
                    newarray[ivec[j]] = i+1
                elif method=='min':
                    newarray[ivec[j]] = i+1 -dupcount+1
                else:
                    raise NameError('Unsupported method')

            sumranks = 0
            dupcount = 0


    return newarray

答案 5 :(得分:2)

[sorted(l).index(x) for x in l]

sorted(l)将给出排序的版本 index(x)将在排序后的数组中给出index

例如:

l = [-1, 3, 2, 0,0]
>>> [sorted(l).index(x) for x in l]
[0, 4, 3, 1, 1]

答案 6 :(得分:1)

      DECLARE
     @StartDate  DATE ='20160101' ,
  @EndDate  DATE = '20160331', 

  @Box1  DECIMAL,
  @Box2  DECIMAL,
  @Box3  DECIMAL,
  @Box4  DECIMAL,
  @Box5  DECIMAL,
  @Box6  DECIMAL,
  @Box7  DECIMAL,
  @Box8  DECIMAL,
  @Box9  DECIMAL


SET @Box1 = (SELECT ROUND(SUM (vt.Vat),2) FROM VatTransactions vt WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SET @Box2 = (SELECT ROUND(SUM(vt.VatDueOnECPurchases/vt.ConversionFactor),2) FROM VatTransactions vt WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SET @Box3 = (SELECT ROUND(SUM(@Box1 + @Box2),2))

SET @Box4 = (SELECT (ROUND(SUM(vt.VatInput),2) + @Box2) FROM VatTransactions vt WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SET @Box5 =(SELECT @Box3 - @Box4)

SET @Box8 = (SELECT ROUND(SUM(vt.SlAway/vt.ConversionFactor),2) FROM VatTransactions vt WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SET @Box9 = (SELECT ROUND(SUM(vt.PlAway/vt.ConversionFactor),2) FROM VatTransactions vt  WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SET @Box6 = (SELECT (ROUND(SUM(vt.SlHome),2) + @Box8) FROM VatTransactions vt WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SET @Box7 = (SELECT (ROUND(SUM(vt.PlHome),2) + @Box9) FROM VatTransactions vt WHERE vt.VatTransactionDate BETWEEN @StartDate AND @EndDate)

SELECT @Box1 AS BOX1, @Box2 AS Box2, @Box3 AS Box3, @Box4 AS Box4, @Box5 AS Box5, @Box6 AS Box6, @Box7 AS Box7, @Box8 AS Box8, @Box9 AS Box9

timecomplexity是46.2us

答案 7 :(得分:0)

这些代码给了我很多灵感,尤其是unutbu的代码。 但是我的需求更简单,所以我稍微改了一下代码。

希望帮助那些有相同需求的人。

这是记录球员得分和排名的课程。

class Player():
    def __init__(self, s, r):
        self.score = s
        self.rank = r

一些数据。

l = [Player(90,0),Player(95,0),Player(85,0), Player(90,0),Player(95,0)]

以下是计算代码:

l.sort(key=lambda x:x.score, reverse=True)    
l[0].rank = 1
dupcount = 0
prev = l[0]
for e in l[1:]:
    if e.score == prev.score:
        e.rank = prev.rank
        dupcount += 1
    else:
        e.rank = prev.rank + dupcount + 1
        dupcount = 0
        prev = e

答案 8 :(得分:0)

所以..这是2019年,我不知道为什么没人建议以下内容:

# Python-only
def rank_list( x, break_ties=False ):
    n = len(x)
    t = list(range(n))
    s = sorted( t, key=x.__getitem__ )

    if not break_ties:
        for k in range(n-1):
            t[k+1] = t[k] + (x[s[k+1]] != x[s[k]])

    r = s.copy()
    for i,k in enumerate(s):
        r[k] = t[i]

    return r

# Using Numpy, see also: np.argsort
def rank_vec( x, break_ties=False ):
    n = len(x)
    t = np.arange(n)
    s = sorted( t, key=x.__getitem__ )

    if not break_ties:
        t[1:] = np.cumsum(x[s[1:]] != x[s[:-1]])

    r = t.copy()
    np.put( r, s, t )
    return r

此方法在初始排序后具有线性运行时复杂性,它仅存储2个索引数组,并且不需要将值可哈希化(仅需要成对比较)。

AFAICT,这比到目前为止建议的其他方法要好:

  • @unutbu的方法本质上是相似的,但是(我会说)对于OP的要求来说太复杂了;
  • 所有使用.index()的建议都很糟糕,运行时复杂度为N ^ 2;
  • @Yuvraj Singh在使用字典进行.index()搜索时略有改进,但是在每次迭代中进行搜索和插入操作时,这在时间(NlogN)和空间方面仍然非常低效,并且还需要使用值可以散列。

答案 9 :(得分:0)

我真的不明白为什么所有现有解决方案都如此复杂。可以这样做:

[index for element, index in sorted(zip(sequence, range(len(sequence))))]

您将构建包含元素和运行索引的元组。然后,您对整个事物进行排序,元组按其第一个元素排序,并且在联系期间按其第二个元素排序。这样,就可以对这些元组进行排序,然后只需要从中选择索引即可。同样,这也消除了以后需要按顺序查找元素的麻烦,这很可能使其成为O(N²)操作,而这是O(N log(N))。

答案 10 :(得分:0)

这适用于spearman相关系数。

def get_rank(X, n):
    x_rank = dict((x, i+1) for i, x in enumerate(sorted(set(X))))
    return [x_rank[x] for x in X]

答案 11 :(得分:0)

可以使用以下方法在 O(n log n) 时间和 O(n) 额外空间内实现秩函数。

import bisect

def rank_list(lst: list[int]) -> list[int]:
    sorted_vals = sorted(set(lst))
    return [bisect.bisect_left(sorted_vals, val) for val in lst]

我在这里使用 bisect 库,但对于纯独立代码,它足以在排序数组上实现二进制搜索过程,其中具有唯一值,用于查询现有(在此数组中)值。