Эффективный метод вычисления вектора ранга списка в Python


Я ищу эффективный способ вычисления вектора ранга списка в Python, подобный функции R rank. В простом списке без связей между элементами элемент i вектора ранга списка l должен быть x тогда и только тогда, когда l[i] является x-м элементом в отсортированном списке. Пока это просто, следующий фрагмент кода делает трюк:

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], но то, что я хотел бы иметь, - это [0, 1, 3, 3, 3, 5, 6]. Какой из них был бы наиболее эффективным способом сделать это в Python?


Сноска: я не знаю, есть ли у NumPy уже метод для достижения этого или нет; если это так, пожалуйста, дайте мне знать, но я буду меня интересует чистое решение Python, так как я разрабатываю инструмент, который должен работать и без NumPy.

7 24

7 ответов:

, используя составляющей, функции вы ищете составляющей.статистика.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 s rank.

Вот чистый питонский эквивалент функции rankdata scipy:

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

def rankdata(a):
    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]:
            averank = sumranks / float(dupcount) + 1
            for j in xrange(i-dupcount+1,i+1):
                newarray[ivec[j]] = averank
            sumranks = 0
            dupcount = 0
    return newarray

print(rankdata([3, 1, 4, 15, 92]))
# [2.0, 1.0, 3.0, 4.0, 5.0]
print(rankdata([1, 2, 3, 3, 3, 4, 5]))
# [1.0, 2.0, 4.0, 4.0, 4.0, 6.0, 7.0]

Это не дает точного результата, который вы указываете, но, возможно, это было бы полезно в любом случае. Следующий фрагмент кода дает первый индекс для каждого элемента, получая конечный вектор ранга [0, 1, 2, 2, 2, 5, 6]

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

Ваше собственное тестирование должно было бы доказать эффективность этого.

Есть очень хороший модуль под названием ранжирование http://pythonhosted.org/ranking/ с простой в использовании страницей инструкций. Для загрузки просто используйте easy_install ranking

Вот небольшая вариация кода unutbu, включая необязательный аргумент "method" для типа значения связанных рангов.

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

Это одна из функций, которую я написал для вычисления ранга.

   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]

import numpy as np

def rankVec(arg):
    p = np.unique(arg) #take unique value
    k = (-p).argsort().argsort() #sort based on arguments in ascending order
    dd = defaultdict(int)
    for i in xrange(np.shape(p)[0]):
        dd[p[i]] = k[i]
    return np.array([dd[x] for x in arg])

Временная сложность составляет 46,2 us

Эти коды дают мне много вдохновения, особенно код унутбу. Однако мои потребности проще, поэтому я немного изменил код.

Надеясь помочь ребятам с теми же потребностями.

Вот класс для записи результатов и рангов игроков.

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