(ソートされた)リストを引数として取り、各要素に対応するパーセンタイルを含むリストを出力する関数を作成したいと思います。
たとえば、fn([1,2,3,4,17])
は_[0.0, 0.25, 0.50, 0.75, 1.00]
_を返します。
誰でもどちらかを喜ばせることができます:
私の現在のコード:
_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]
_が返されます。
あなたの入力/出力の例は、パーセンタイルを計算する一般的な方法に対応していないと思います。パーセンタイルを「この値よりも厳密に小さいデータポイントの割合」として計算する場合、上位の値は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]
になります。あなたの定義は「この値より厳密に少ないデータポイントの数であり、この値と等しくないデータポイントの数の比率と見なされる」ようですが、私の経験では、これは一般的な定義ではありません(たとえば- wikipedia )。
典型的なパーセンタイル定義では、データポイントのパーセンタイルは、そのランクをデータポイントの数で割ったものと等しくなります。 (たとえば、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
の使用はKevin H. Linのソリューションよりも多少遅くなります(おそらく、フードの下でnumpy配列に物事を変換する際にオーバーヘッドscipyが原因で発生します)、ループでpercentileofscore
を呼び出すよりは高速です回答:
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のlist_to_percentiles
よりも10倍高速になります。
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
この利点は、より大きなデータセットでのみ顕著になります。
私はあなたが望むと思います scipy.stats.percentileofscore
例:
percentileofscore([1, 2, 3, 4], 3)
75.0
percentiles = [percentileofscore(data, i) for i in data]
ケビンが言ったように、最適解はO(n log(n))時間で機能します。これはnumpy
にある彼のコードの高速バージョンで、stats.rankdata
とほぼ同時に機能します。
percentiles = numpy.argsort(numpy.argsort(array)) * 100. / (len(array) - 1)
PS。これは、私のお気に入りのトリックであるnumpy
の1つです。
複雑さの点では、レプティリカスの答えは最適ではないと思います。 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の回答を参照してください。
これは単純すぎるように見えるかもしれませんが、これについてはどうでしょう:
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]
私があなたを正しく理解しているなら、あなたがしたいことは、この要素が配列で表すパーセンタイル、つまりその要素の前の配列の量を定義することです。 [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]
私にとって最良の解決策は、sklearn.preprocessing
でQuantileTransformer
を使用することです。
from sklearn.preprocessing import QuantileTransformer
fn = lambda input_list : QuantileTransformer(100).fit_transform(np.array(input_list).reshape([-1,1])).ravel().tolist()
input_raw = [1, 2, 3, 4, 17]
output_perc = fn( input_raw )
print "Input=", input_raw
print "Output=", np.round(output_perc,2)
これが出力です
Input= [1, 2, 3, 4, 17]
Output= [ 0. 0.25 0.5 0.75 1. ]
注:この関数には2つの顕著な特徴があります。
pure python functionがpercentile特定のアイテムのスコアを、人口分布(スコアのリスト)と比較して、これをscipy
ソースコードからプルし、numpyへのすべての参照を削除しました。
def percentileofscore(a, score, kind='rank'):
n = len(a)
if n == 0:
return 100.0
left = len([item for item in a if item < score])
right = len([item for item in a if item <= score])
if kind == 'rank':
pct = (right + left + (1 if right > left else 0)) * 50.0/n
return pct
Elif kind == 'strict':
return left / n * 100
Elif kind == 'weak':
return right / n * 100
Elif kind == 'mean':
pct = (left + right) / n * 50
return pct
else:
raise ValueError("kind can only be 'rank', 'strict', 'weak' or 'mean'")
ソース: https://github.com/scipy/scipy/blob/v1.2.1/scipy/stats/stats.py#L1744-L1835
パーセンタイルの計算は思ったより難しいですが、完全なscipy/numpy/scikitパッケージほど複雑ではないので、これは軽量のデプロイメントに最適です。元のコードはゼロ以外の値のみをフィルタリングしますが、それ以外は数学は同じです。オプションのパラメーターは、他の2つの値の間にある値を処理する方法を制御します。
この使用例では、map()関数を使用して、リスト内の各アイテムに対してこの関数を呼び出すことができます。
このバージョンでは、ランキングに使用される正確なパーセンタイル値を渡すこともできます。
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の範囲にヒットします