it-swarm.com.de

Effiziente Methode zur Berechnung des Rangvektors einer Liste in Python

Ich suche nach einer effizienten Methode, um den Rangvektor einer Liste in Python zu berechnen, ähnlich wie bei der rank-Funktion von R. In einer einfachen Liste ohne Bindungen zwischen den Elementen sollte element i des Rangvektors einer Liste l x sein und nur dann, wenn l[i] das x - te Element im sortierten Element ist Liste. Das ist bisher einfach, der folgende Code-Snippet macht den Trick:

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

Die Dinge werden jedoch komplizierter, wenn die ursprüngliche Liste Verbindungen enthält (d. H. Mehrere Elemente mit demselben Wert). In diesem Fall sollten alle Elemente mit demselben Wert den gleichen Rang haben. Dies ist der Durchschnitt ihrer Ränge, die mit der oben genannten naiven Methode erhalten wurden. Wenn ich zum Beispiel [1, 2, 3, 3, 3, 4, 5] habe, gibt mir das naive Ranking [0, 1, 2, 3, 4, 5, 6], aber ich möchte [0, 1, 3, 3, 3, 5, 6]. Welches wäre der effizienteste Weg, dies in Python zu tun?


Fußnote: Ich weiß nicht, ob NumPy bereits eine Methode hat, um dies zu erreichen oder nicht. Wenn ja, lass es mich wissen, aber ich wäre trotzdem an einer reinen Python-Lösung interessiert, da ich ein Tool entwickle, das auch ohne NumPy funktionieren sollte.

24
Tamás

Mit scipy suchen Sie die Funktion 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.])

Die Ränge beginnen bei 1 und nicht bei 0 (wie in Ihrem Beispiel), aber auf diese Weise funktioniert auch die R-Funktion von rank.

Hier ist ein reines Python-Äquivalent der Rankdata-Funktion von 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

Dies gibt nicht das genaue Ergebnis, das Sie angeben, aber es wäre auf jeden Fall nützlich. Das folgende Snippet gibt den ersten Index für jedes Element an und ergibt einen endgültigen Rangvektor von [0, 1, 2, 2, 2, 5, 6].

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

Ihre eigenen Tests müssten die Effizienz beweisen.

3
stw_dev

Dies ist eine der Funktionen, die ich geschrieben habe, um den Rang zu berechnen. 

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]

eingang: 

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

ausgabe: 

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

Es gibt ein wirklich schönes Modul namens Ranking http://pythonhosted.org/ranking/ mit einer leicht verständlichen Anweisungsseite. Verwenden Sie zum Herunterladen einfach easy_install ranking

2
Kerry Kalweit

Hier ist eine kleine Variation von unutbu-Code, einschließlich eines optionalen 'method'-Arguments für den Wertetyp der gebundenen Ränge.

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

zeitkomplexität ist 46.2us

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

sorted(l) gibt die sortierte Version index(x) gibt die index im sortierten Array ab

zum Beispiel :

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

Das ist also 2019, und ich habe keine Ahnung, warum niemand Folgendes vorgeschlagen hat:

# 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

Dieser Ansatz hat eine lineare Laufzeitkomplexität nach der anfänglichen Sortierung, speichert nur 2 Arrays von Indizes und erfordert keine Hashbarkeit von Werten (nur paarweiser Vergleich erforderlich).

AFAICT, dies ist besser als andere bisher vorgeschlagene Ansätze:

  • Der Ansatz von @ unutbu ist im Wesentlichen ähnlich, aber (ich würde argumentieren) zu kompliziert für das, was das OP gefordert hat.
  • Alle Vorschläge, die .index() verwenden, sind schrecklich, mit einer Laufzeitkomplexität von N ^ 2;
  • @Yuvraj Singh verbessert die .index() - Suche mithilfe eines Wörterbuchs geringfügig. Mit Such- und Einfügeoperationen bei jeder Iteration ist dies jedoch sowohl in Bezug auf Zeit (NlogN) als auch auf den Speicherplatz ineffizient haschbar sein.
0
Sheljohn

Diese Codes geben mir eine Menge Inspiration, insbesondere den Code von Unutbu. Allerdings sind meine Bedürfnisse einfacher, daher habe ich den Code ein wenig geändert.

Ich hoffe, den Jungs mit den gleichen Bedürfnissen zu helfen.

Hier ist die Klasse, in der die Punkte und Ränge der Spieler aufgezeichnet werden.

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

Daten.

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

Hier ist der Code für die Berechnung:

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