Эффективный метод вычисления вектора ранга списка в 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 ответов:
, используя составляющей, функции вы ищете составляющей.статистика.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
srank
.Вот чистый питонский эквивалент функции 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