Эффективный метод для вычисления вектора ранга списка в Python - PullRequest
26 голосов
/ 18 июня 2010

Я ищу эффективный способ вычисления вектора ранга списка в 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.

Ответы [ 9 ]

55 голосов
/ 18 июня 2010

Используя 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 (как в вашем примере), но с другой стороны, именно так работает rank функция *1005*.

Вот чистый Python-эквивалент функции 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]
4 голосов
/ 04 декабря 2016

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

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]
3 голосов
/ 18 июня 2010

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

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

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

2 голосов
/ 07 декабря 2018
[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]
2 голосов
/ 12 июня 2015

Вот небольшой вариант кода 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
2 голосов
/ 05 сентября 2013

Существует действительно хороший модуль под названием Ranking http://pythonhosted.org/ranking/ с простой в использовании страницей с инструкциями.Чтобы скачать, просто используйте easy_install ranking

0 голосов
/ 13 июля 2019

Итак ... это 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 по существу похож, но (я бы сказал,) слишком сложен для того, что попросил ОП;*
  • Все предложения, использующие .index(), ужасны, со сложностью времени выполнения N ^ 2;
  • @ Yuvraj Singh немного улучшается при поиске .index() с использованием словаря, однако с операциями поиска и вставки вна каждой итерации это все еще крайне неэффективно как во времени (NlogN), так и в пространстве, а также требует, чтобы значения были хэшируемыми.
0 голосов
/ 04 октября 2016
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 у

0 голосов
/ 06 января 2016

Эти коды вдохновляют меня, особенно код 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...