38

(ソートされた) リストを引数として取り、各要素の対応するパーセンタイルを含むリストを出力する関数を作成したいと思います。

たとえば、 をfn([1,2,3,4,17])返します[0.0, 0.25, 0.50, 0.75, 1.00]

誰でも次のいずれかをお願いできますか:

  1. 以下のコードを修正してください。また
  2. リスト内の値を対応するパーセンタイルにマッピングするためのコードよりも優れた代替手段を提供しますか?

私の現在のコード:

def median(mylist):
    length = len(mylist)
    if not length % 2:
        return (mylist[length / 2] + mylist[length / 2 - 1]) / 2.0
    return mylist[length / 2]

###############################################################################
# PERCENTILE FUNCTION
###############################################################################

def percentile(x):
    """
    Find the correspoding percentile of each value relative to a list of values.
    where x is the list of values
    Input list should already be sorted!
    """

    # sort the input list
    # list_sorted = x.sort()

    # count the number of elements in the list
    list_elementCount = len(x)

    #obtain set of values from list

    listFromSetFromList = list(set(x))

    # count the number of unique elements in the list
    list_uniqueElementCount = len(set(x))

    # define extreme quantiles
    percentileZero    = min(x)
    percentileHundred = max(x)

    # define median quantile
    mdn = median(x) 

    # create empty list to hold percentiles
    x_percentile = [0.00] * list_elementCount 

    # initialize unique count
    uCount = 0

    for i in range(list_elementCount):
        if x[i] == percentileZero:
            x_percentile[i] = 0.00
        elif x[i] == percentileHundred:
            x_percentile[i] = 1.00
        elif x[i] == mdn:
            x_percentile[i] = 0.50 
        else:
            subList_elementCount = 0
            for j in range(i):
                if x[j] < x[i]:
                    subList_elementCount = subList_elementCount + 1 
            x_percentile[i] = float(subList_elementCount / list_elementCount)
            #x_percentile[i] = float(len(x[x > listFromSetFromList[uCount]]) / list_elementCount)
            if i == 0:
                continue
            else:
                if x[i] == x[i-1]:
                    continue
                else:
                    uCount = uCount + 1
    return x_percentile

現在、送信するpercentile([1,2,3,4,17])と、リスト[0.0, 0.0, 0.5, 0.0, 1.0]が返されます。

4

10 に答える 10

54

あなたの入力/出力の例は、パーセンタイルを計算する典型的な方法に対応していないと思います。パーセンタイルを「この値よりも厳密に小さいデータ ポイントの割合」として計算すると、最大値は 0.8 になります (5 つの値のうち 4 つが最大値より小さいため)。「この値以下のデータ ポイントの割合」として計算すると、最小値は 0.2 になります (5 つの値のうち 1 つが最小値に等しいため)。したがって、パーセンタイルは[0, 0.2, 0.4, 0.6, 0.8]またはになり[0.2, 0.4, 0.6, 0.8, 1]ます。あなたの定義は、「この値と等しくないデータポイントの数の割合と見なされる、この値よりも厳密に少ないデータポイントの数」のようですが、私の経験では、これは一般的な定義ではありません(たとえば、ウィキペディアを参照) .

一般的なパーセンタイルの定義では、データ ポイントのパーセンタイルは、データ ポイントの数で割ったランクに等しくなります。(たとえば、R で同じことを行う方法を尋ねる Stats SE に関するこの質問を参照してください。) パーセンタイルの計算方法の違いは、ランクの計算方法の違いになります (たとえば、同点の値をランク付けする方法)。このscipy.stats.percentileofscore関数は、パーセンタイルを計算する 4 つの方法を提供します。

>>> x = [1, 1, 2, 2, 17]
>>> [stats.percentileofscore(x, a, 'rank') for a in x]
[30.0, 30.0, 70.0, 70.0, 100.0]
>>> [stats.percentileofscore(x, a, 'weak') for a in x]
[40.0, 40.0, 80.0, 80.0, 100.0]
>>> [stats.percentileofscore(x, a, 'strict') for a in x]
[0.0, 0.0, 40.0, 40.0, 80.0]
>>> [stats.percentileofscore(x, a, 'mean') for a in x]
[20.0, 20.0, 60.0, 60.0, 90.0]

(このような場合に何が起こるかを説明するために、タイを含むデータセットを使用しました。)

「ランク」方式は、同点のグループに、それらがカバーするランクの平均に等しいランクを割り当てます (つまり、2 位の 3 者同点は、ランク 2、3、および 4 を「占める」ため、ランク 3 になります)。「弱い」方法では、特定のポイント以下のデータ ポイントの割合に基づいてパーセンタイルが割り当てられます。"strict" は同じですが、指定されたポイントより厳密に小さいポイントの割合をカウントします。「平均」法は、後者の 2 つの平均です。

Kevin H. Lin が指摘したようpercentileofscoreに、パスごとにランクを再計算する必要があるため、ループ内での呼び出しは非効率的です。ただし、これらのパーセンタイルの計算は、 が提供するさまざまなランク付け方法を使用して簡単に複製できscipy.stats.rankdata、すべてのパーセンタイルを一度に計算できます。

>>> from scipy import stats
>>> stats.rankdata(x, "average")/len(x)
array([ 0.3,  0.3,  0.7,  0.7,  1. ])
>>> stats.rankdata(x, 'max')/len(x)
array([ 0.4,  0.4,  0.8,  0.8,  1. ])
>>> (stats.rankdata(x, 'min')-1)/len(x)
array([ 0. ,  0. ,  0.4,  0.4,  0.8])

最後のケースでは、ランクは 1 ではなく 0 から始まるように 1 ずつ調整されます (「平均」は省略しましたが、後者の 2 つの方法の結果を平均することで簡単に取得できます)。

私はいくつかのタイミングを行いました。あなたの例のような小さなデータでは、使用rankdataはケビンH.リンのソリューションよりもやや遅くなります(おそらく、フードの下で物事をnumpy配列に変換する際に発生するオーバーヘッドscipyが原因です)がpercentileofscore、reptilicusの回答のようにループで呼び出すよりも高速です:

In [11]: %timeit [stats.percentileofscore(x, i) for i in x]
1000 loops, best of 3: 414 µs per loop

In [12]: %timeit list_to_percentiles(x)
100000 loops, best of 3: 11.1 µs per loop

In [13]: %timeit stats.rankdata(x, "average")/len(x)
10000 loops, best of 3: 39.3 µs per loop

ただし、大規模なデータセットでは、numpy のパフォーマンス上の利点が有効にrankdataなり、Kevin の 10 倍の速度で使用できlist_to_percentilesます。

In [18]: x = np.random.randint(0, 10000, 1000)

In [19]: %timeit [stats.percentileofscore(x, i) for i in x]
1 loops, best of 3: 437 ms per loop

In [20]: %timeit list_to_percentiles(x)
100 loops, best of 3: 1.08 ms per loop

In [21]: %timeit stats.rankdata(x, "average")/len(x)
10000 loops, best of 3: 102 µs per loop

この利点は、データセットが大きくなるほど顕著になります。

于 2015-02-18T06:08:44.607 に答える
22

scipy.stats.percentileofscoreが必要だと思います

例:

percentileofscore([1, 2, 3, 4], 3)
75.0
percentiles = [percentileofscore(data, i) for i in data]
于 2012-09-13T20:42:07.030 に答える
14

複雑さに関しては、レプティリカスの答えは最適ではないと思います。O(n^2)時間かかります。

これは、O(n log n) 時間かかるソリューションです。

def list_to_percentiles(numbers):
    pairs = zip(numbers, range(len(numbers)))
    pairs.sort(key=lambda p: p[0])
    result = [0 for i in range(len(numbers))]
    for rank in xrange(len(numbers)):
        original_index = pairs[rank][1]
        result[original_index] = rank * 100.0 / (len(numbers)-1)
    return result

よくわかりませんが、これが最適な時間の複雑さだと思います。私が最適だと思う大まかな理由は、すべてのパーセンタイルの情報がソートされたリストの情報と本質的に同等であり、ソートのために O(n log n) よりも良くなることはできないためです。

編集:「パーセンタイル」の定義によっては、常に正しい結果が得られるとは限りません。詳細な説明と、scipy/numpy を利用するより良い解決策については、BrenBarn の回答を参照してください。

于 2014-12-16T04:39:14.687 に答える
13

ケビンのソリューションの純粋な派手なバージョン

ケビンが言ったように、最適解は O(n log(n)) 時間で機能します。これは の彼のコードの高速バージョンでnumpy、 とほぼ同時に動作しstats.rankdataます。

percentiles = numpy.argsort(numpy.argsort(array)) * 100. / (len(array) - 1)

PS。これは、私のお気に入りのトリックの 1 つですnumpy

于 2015-05-01T15:35:10.123 に答える
2

これは単純化されすぎているように見えるかもしれませんが、これはどうでしょうか。

def percentile(x):
    pc = float(1)/(len(x)-1)
    return ["%.2f"%(n*pc) for n, i in enumerate(x)]

編集:

def percentile(x):
    unique = set(x)
    mapping = {}
    pc = float(1)/(len(unique)-1)
    for n, i in enumerate(unique):
        mapping[i] = "%.2f"%(n*pc)
    return [mapping.get(el) for el in x]
于 2012-09-13T20:45:19.730 に答える
1

私があなたを正しく理解しているなら、あなたがしたいのは、この要素が配列で表すパーセンタイル、つまりその要素の前にある配列の量を定義することだけです。[1、2、3、4、5]のように、[0.0、0.25、0.5、0.75、1.0]である必要があります

私はそのようなコードで十分だと信じています:

def percentileListEdited(List):
    uniqueList = list(set(List))
    increase = 1.0/(len(uniqueList)-1)
    newList = {}
    for index, value in enumerate(uniqueList):
        newList[index] = 0.0 + increase * index
    return [newList[val] for val in List]
于 2012-09-13T20:31:40.447 に答える
0

このバージョンでは、ランキングに使用される正確なパーセンタイル値を渡すこともできます:

def what_pctl_number_of(x, a, pctls=np.arange(1, 101)):
    return np.argmax(np.sign(np.append(np.percentile(x, pctls), np.inf) - a))

したがって、提供されたパーセンタイルのパーセンタイル数値が何になるかを調べることができます。

_x = np.random.randn(100, 1)
what_pctl_number_of(_x, 1.6, [25, 50, 75, 100])

出力:

3

75~100の範囲にヒットするので

于 2018-05-07T20:39:05.200 に答える