web-dev-qa-db-fra.com

Méthode efficace pour calculer le vecteur de classement d'une liste en Python

Je recherche un moyen efficace de calculer le vecteur de classement d'une liste en Python, similaire à la fonction rank de R. Dans une liste simple sans liens entre les éléments, l'élément i du vecteur de rang d'une liste l doit être x si et seulement si l[i] est le x - ème élément de l'élément trié liste. C'est simple jusqu'à présent, l'extrait de code suivant fait l'affaire:

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

Les choses se compliquent toutefois si la liste d'origine est liée (c'est-à-dire plusieurs éléments de même valeur). Dans ce cas, tous les éléments ayant la même valeur doivent avoir le même rang, qui est la moyenne de leurs rangs obtenue selon la méthode naïve ci-dessus. Ainsi, par exemple, si j'ai [1, 2, 3, 3, 3, 4, 5], le classement naïf me donne [0, 1, 2, 3, 4, 5, 6], mais ce que j'aimerais avoir, c'est [0, 1, 3, 3, 3, 5, 6]. Lequel serait le moyen le plus efficace de faire cela en Python?


Note de bas de page: Je ne sais pas si NumPy a déjà une méthode pour y parvenir ou non; Si c'est le cas, merci de me le faire savoir, mais je serais de toute façon intéressé par une solution Python pure, car je développe un outil qui devrait également fonctionner sans NumPy.

24
Tamás

En utilisant scipy, la fonction que vous recherchez est 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.])

Les rangs commencent à 1 au lieu de 0 (comme dans votre exemple), mais là encore, c'est la façon dont la fonction R de rank fonctionne également.

Voici un équivalent pur en python de la fonction rankdata de 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]
51
unutbu

Cela ne donne pas le résultat exact que vous spécifiez, mais peut-être serait-il utile de toute façon. L'extrait suivant donne le premier index pour chaque élément, donnant un dernier vecteur de rang de [0, 1, 2, 2, 2, 5, 6]

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

Vos propres tests devraient prouver l'efficacité de ceci.

3
stw_dev

C'est l'une des fonctions que j'ai écrites pour calculer le rang. 

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]

contribution: 

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

sortie: 

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

Il existe un module vraiment sympa appelé Ranking http://pythonhosted.org/ranking/ avec une page d’instructions facile à suivre. Pour télécharger, utilisez simplement easy_install ranking

2
Kerry Kalweit

Voici une petite variation du code de unutbu, incluant un argument optionnel 'méthode' pour le type de valeur des rangs liés.

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
Sunthar
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])

timecomplexity est 46.2us

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

sorted(l) donnera la version triée index(x) donnera la index dans le tableau trié

par exemple :

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

Ces codes me donnent beaucoup d’inspiration, en particulier le code de unutbu. Cependant, mes besoins sont plus simples, j’ai donc légèrement modifié le code.

Espérant aider les gars ayant les mêmes besoins.

Voici la classe pour enregistrer les scores et les classements des joueurs.

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

Quelques données.

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

Voici le code pour le calcul:

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
0
Joe