Rのrank
関数と同様に、Pythonでリストのランクベクトルを計算する効率的な方法を探しています。要素間に関連付けがない単純なリストでは、リストのランクベクトルの要素il
は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がこれを実現する方法をすでに持っているかどうかはわかりません。ある場合はお知らせください。NumPyがなくても機能するツールを開発しているため、純粋なPythonソリューションに興味があります。
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.])
(例のように)ランクは0ではなく1から始まりますが、R
のrank
関数も同様に機能します。
scipy
のrankdata関数に相当する純粋なPythonは次のとおりです。
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]
これは、ランクを計算するために書いた関数の1つです。
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]
これは指定した正確な結果を提供しませんが、おそらくそれはとにかく役に立つでしょう。次のスニペットは、各要素の最初のインデックスを提供し、[0, 1, 2, 2, 2, 5, 6]
の最終ランクベクトルを生成します
def rank_index(vector):
return [vector.index(x) for x in sorted(range(n), key=vector.__getitem__)]
あなた自身のテストはこれの効率を証明しなければならないでしょう。
_[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]
_
ランキング http://pythonhosted.org/ranking/ と呼ばれる本当に素晴らしいモジュールがあり、わかりやすい説明ページがあります。ダウンロードするには、単にeasy_install ranking
を使用します
以下はunutbuのコードの小さなバリエーションであり、同順位の値のタイプのオプションの「メソッド」引数を含みます。
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
既存のすべてのソリューションが非常に複雑である理由が本当にわかりません。これは次のように行うことができます:
[index for element, index in sorted(Zip(sequence, range(len(sequence))))]
要素と実行中のインデックスを含むタプルを作成します。次に、全体を並べ替えます。タプルは最初の要素で並べ替え、結合中は2番目の要素で並べ替えます。このようにして、これらのタプルのソートされたリストがあり、後でそのインデックスを選択する必要があります。また、これにより、後でシーケンス内の要素を検索する必要がなくなります。これにより、O(N²)操作になる可能性がありますが、これはO(N log(N))になります。
ですから、これは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、これはこれまでに提案された他のアプローチよりも優れています:
.index()
を使用したすべての提案はひどいもので、実行時の複雑さはN ^ 2です。.index()
検索をわずかに改善しますが、各反復での検索および挿入操作では、これは時間(NlogN)とスペースの両方で依然として非常に非効率的であり、次の値も必要です。ハッシュ可能であること。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.2us
これらのコード、特に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