web-dev-qa-db-ja.com

Pythonでリストのランクベクトルを計算する効率的な方法

Rのrank関数と同様に、Pythonでリストのランクベクトルを計算する効率的な方法を探しています。要素間に関連付けがない単純なリストでは、リストのランクベクトルの要素ilxである必要があります(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ソリューションに興味があります。

31
Tamás

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から始まりますが、Rrank関数も同様に機能します。

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]
56
unutbu

これは、ランクを計算するために書いた関数の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]
4
Yuvraj Singh

これは指定した正確な結果を提供しませんが、おそらくそれはとにかく役に立つでしょう。次のスニペットは、各要素の最初のインデックスを提供し、[0, 1, 2, 2, 2, 5, 6]の最終ランクベクトルを生成します

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

あなた自身のテストはこれの効率を証明しなければならないでしょう。

3
stw_dev
_[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
Jialiang Gu

ランキング http://pythonhosted.org/ranking/ と呼ばれる本当に素晴らしいモジュールがあり、わかりやすい説明ページがあります。ダウンロードするには、単にeasy_install rankingを使用します

2
Kerry Kalweit

以下は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
2
Sunthar

既存のすべてのソリューションが非常に複雑である理由が本当にわかりません。これは次のように行うことができます:

[index for element, index in sorted(Zip(sequence, range(len(sequence))))]

要素と実行中のインデックスを含むタプルを作成します。次に、全体を並べ替えます。タプルは最初の要素で並べ替え、結合中は2番目の要素で並べ替えます。このようにして、これらのタプルのソートされたリストがあり、後でそのインデックスを選択する必要があります。また、これにより、後でシーケンス内の要素を検索する必要がなくなります。これにより、O(N²)操作になる可能性がありますが、これはO(N log(N))になります。

0
Martin Ueding

ですから、これは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のアプローチは基本的に似ていますが、(私は議論するでしょう)OPが要求したものに対して複雑すぎます。
  • .index()を使用したすべての提案はひどいもので、実行時の複雑さはN ^ 2です。
  • @Yuvraj Singhは、辞書を使用して.index()検索をわずかに改善しますが、各反復での検索および挿入操作では、これは時間(NlogN)とスペースの両方で依然として非常に非効率的であり、次の値も必要です。ハッシュ可能であること。
0
Jonathan H
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

0
vamsi21

これらのコード、特に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
0
Joe