如何使用Python/Numpy计算百分位数?

300
有没有一种方便的方法来计算序列或单维度的numpy数组的百分位数?
我正在寻找类似于Excel的百分位函数的方法。

一个关于从频率计算百分位数的相关问题:https://dev59.com/sILba4cB1Zd3GeqPZRLE - newtover
一个与pandas数据框相关的问题:python - 查找给定列的百分位统计信息 - undefined
12个回答

386

2
谢谢!原来它一直藏在那里。我知道scipy,但我想我认为像百分位数这样的简单事情应该内置在numpy中。 - Uri
20
到目前为止,numpy中存在一个百分位数函数:http://docs.scipy.org/doc/numpy/reference/generated/numpy.percentile.html - Anaphory
1
你也可以将它用作聚合函数,例如通过键计算值列每个组的第十个百分位数,使用 df.groupby('key')[['value']].agg(lambda g: np.percentile(g, 10)) - patricksurry
1
请注意,SciPy建议在NumPy 1.9及更高版本中使用np.percentile。 - Tim Diels

86

顺便提一下,如果不想依赖scipy,可以使用纯Python实现的百分位函数。以下是该函数的代码:

## {{{ http://code.activestate.com/recipes/511478/ (r1)
import math
import functools

def percentile(N, percent, key=lambda x:x):
    """
    Find the percentile of a list of values.

    @parameter N - is a list of values. Note N MUST BE already sorted.
    @parameter percent - a float value from 0.0 to 1.0.
    @parameter key - optional key function to compute value from each element of N.

    @return - the percentile of the values
    """
    if not N:
        return None
    k = (len(N)-1) * percent
    f = math.floor(k)
    c = math.ceil(k)
    if f == c:
        return key(N[int(k)])
    d0 = key(N[int(f)]) * (c-k)
    d1 = key(N[int(c)]) * (k-f)
    return d0+d1

# median is 50th percentile.
median = functools.partial(percentile, percent=0.5)
## end of http://code.activestate.com/recipes/511478/ }}}

68
我是上面食谱的作者。ASPN的一位评论者指出原始代码有一个错误。公式应该为d0 = key(N[int(f)]) * (c-k); d1 = key(N[int(c)]) * (k-f)。已在ASPN上进行了更正。 - Wai Yip Tung
2
“percentile” 函数如何知道要使用哪个值作为 “N”?函数调用中没有指定。 - Richard
22
对于那些甚至没有阅读代码的人,在使用之前,N 必须是已排序的。 - kevin
我对lambda表达式感到困惑。它是做什么的,以及它是如何做到的?我知道什么是lambda表达式,所以我不是在问lambda是什么。我是在问这个特定的lambda表达式是做什么的,以及它是如何一步一步地完成的?谢谢! - dsanchez
Lambda函数允许您在计算百分位数之前转换N中的数据。假设您实际上有一个元组列表N = [(1, 2), (3, 1), ..., (5, 1)],并且您想要获取元组的第一个元素的百分位数,那么您可以选择key=lambda x: x[0]。在计算百分位数之前,您还可以对列表元素应用一些(改变顺序的)转换。 - Elias Strehle
在这种情况下,可以使用lambda将传入的数据映射为可作为百分位数进行评估的值。理论上,您可以向函数传递一个已排序的单词字典,并使用lambda将条目映射为单词长度、ASCII值之和等。lambda接受每个条目x并将其映射到一个新值。此处传递的默认参数只是将每个条目映射为它本身(x:x)。 - mdhansen

37
import numpy as np
a = [154, 400, 1124, 82, 94, 108]
print np.percentile(a,95) # gives the 95th percentile

36

从Python 3.8开始,标准库中的quantiles函数成为了statistics模块的一部分:

from statistics import quantiles

quantiles([1, 2, 3, 4, 5], n=100)
# [0.06, 0.12, 0.18, 0.24, 0.3, 0.36, 0.42, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.08, 1.14, 1.2, 1.26, 1.32, 1.38, 1.44, 1.5, 1.56, 1.62, 1.68, 1.74, 1.8, 1.86, 1.92, 1.98, 2.04, 2.1, 2.16, 2.22, 2.28, 2.34, 2.4, 2.46, 2.52, 2.58, 2.64, 2.7, 2.76, 2.82, 2.88, 2.94, 3.0, 3.06, 3.12, 3.18, 3.24, 3.3, 3.36, 3.42, 3.48, 3.54, 3.6, 3.66, 3.72, 3.78, 3.84, 3.9, 3.96, 4.02, 4.08, 4.14, 4.2, 4.26, 4.32, 4.38, 4.44, 4.5, 4.56, 4.62, 4.68, 4.74, 4.8, 4.86, 4.92, 4.98, 5.04, 5.1, 5.16, 5.22, 5.28, 5.34, 5.4, 5.46, 5.52, 5.58, 5.64, 5.7, 5.76, 5.82, 5.88, 5.94]
quantiles([1, 2, 3, 4, 5], n=100)[49] # 50th percentile (e.g median)
# 3.0

quantiles 函数接收一个分布 dist,返回一个长度为 n-1 的列表,表示将分布 dist 分成 n 个连续区间(每个区间的概率相等)所需的 n-1 个切点:

statistics.quantiles(dist, *, n=4, method='exclusive')

在我们的情况下 (percentiles),n 等于 100


2
只是一点提醒。使用 method="exclusive" 时,p99 可能会大于原始列表中的最大值。如果这不是您想要的,即您希望 p100 = max,则请使用 method="inclusive"。 - Amaimersion

27

以下是无需使用numpy,只使用Python计算百分位数的方法。

import math

def percentile(data, perc: int):
    size = len(data)
    return sorted(data)[int(math.ceil((size * perc) / 100)) - 1]

percentile([10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 90)
# 9.0
percentile([142, 232, 290, 120, 274, 123, 146, 113, 272, 119, 124, 277, 207], 50)
# 146

2
是的,在执行以下代码前,你必须对列表进行排序: mylist=sorted(...) - Ashkan

13

我通常看到的百分位数定义期望结果是从提供的列表中找到P%的值下面的值...这意味着结果必须来自集合,而不是集合元素之间的插值。 为了达到这个目的,您可以使用一个更简单的函数。

def percentile(N, P):
    """
    Find the percentile of a list of values

    @parameter N - A list of values.  N must be sorted.
    @parameter P - A float value from 0.0 to 1.0

    @return - The percentile of the values.
    """
    n = int(round(P * len(N) + 0.5))
    return N[n-1]

# A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
# B = (15, 20, 35, 40, 50)
#
# print percentile(A, P=0.3)
# 4
# print percentile(A, P=0.8)
# 9
# print percentile(B, P=0.3)
# 20
# print percentile(B, P=0.8)
# 50

如果你希望获取所提供列表中 P% 的值及以下的值,则可以使用这个简单的修改:

def percentile(N, P):
    n = int(round(P * len(N) + 0.5))
    if n > 1:
        return N[n-2]
    else:
        return N[0]

或者按照@ijustlovemath建议的简化方式:

def percentile(N, P):
    n = max(int(round(P * len(N) + 0.5)), 2)
    return N[n-2]

谢谢,我也希望百分位数/中位数能够从数据集中得出实际值,而不是插值。 - hansaplast
1
嗨@mpounsett。感谢您提供的代码。为什么您的百分位总是返回整数值?百分位函数应该返回值列表的第N个百分位数,这也可以是浮点数。 例如,Excel的PERCENTILE函数对您的上述示例返回以下百分位数:3.7 = percentile(A, P=0.3),0.82 = percentile(A, P=0.8), 20 = percentile(B, P=0.3), 42 = percentile(B, P=0.8) - marco
1
在第一句中已经解释了。百分位数的更常见定义是,在一个系列中,低于P百分比值的数字。由于它是列表中项目的索引号,因此它不能是浮点数。 - mpounsett
1
这对于第0个百分位不起作用。它返回最大值。一个快速修复方法是将n = int(...)包装在max(int(...),1)函数中。 - ijustlovemath
请澄清一下,您是指第二个例子吗?我得到的是0而不是最大值。实际上错误在else语句中...我打印的是索引号而不是我想要的值。将“n”的赋值包装在max()调用中也可以解决问题,但您希望第二个值为2而不是1。然后,您可以消除整个if / else结构并仅打印N [n-2]的结果。第一个示例中的第0百分位数正常工作,分别返回'1'和'15'。 - mpounsett
抱歉给你点了踩。是不小心点错了,当时用手机没注意到。现在已经锁定了! - keepAlive

6

检查是否安装了scipy.stats模块:

 scipy.stats.scoreatpercentile

3

使用numpy.percentile <https://docs.scipy.org/doc/numpy/reference/generated/numpy.percentile.html>是计算一维 numpy 序列或矩阵百分位数的方便方式。示例:

import numpy as np

a = np.array([0,1,2,3,4,5,6,7,8,9,10])
p50 = np.percentile(a, 50) # return 50th percentile, e.g median.
p90 = np.percentile(a, 90) # return 90th percentile.
print('median = ',p50,' and p90 = ',p90) # median =  5.0  and p90 =  9.0

然而,如果您的数据中存在任何NaN值,则上述函数将无法使用。在这种情况下推荐使用的函数是numpy.nanpercentile <https://docs.scipy.org/doc/numpy/reference/generated/numpy.nanpercentile.html>。

import numpy as np

a_NaN = np.array([0.,1.,2.,3.,4.,5.,6.,7.,8.,9.,10.])
a_NaN[0] = np.nan
print('a_NaN',a_NaN)
p50 = np.nanpercentile(a_NaN, 50) # return 50th percentile, e.g median.
p90 = np.nanpercentile(a_NaN, 90) # return 90th percentile.
print('median = ',p50,' and p90 = ',p90) # median =  5.5  and p90 =  9.1

在上述两个选项中,您仍然可以选择插值模式。请参考以下示例以更容易理解。
import numpy as np

b = np.array([1,2,3,4,5,6,7,8,9,10])
print('percentiles using default interpolation')
p10 = np.percentile(b, 10) # return 10th percentile.
p50 = np.percentile(b, 50) # return 50th percentile, e.g median.
p90 = np.percentile(b, 90) # return 90th percentile.
print('p10 = ',p10,', median = ',p50,' and p90 = ',p90)
#p10 =  1.9 , median =  5.5  and p90 =  9.1

print('percentiles using interpolation = ', "linear")
p10 = np.percentile(b, 10,interpolation='linear') # return 10th percentile.
p50 = np.percentile(b, 50,interpolation='linear') # return 50th percentile, e.g median.
p90 = np.percentile(b, 90,interpolation='linear') # return 90th percentile.
print('p10 = ',p10,', median = ',p50,' and p90 = ',p90)
#p10 =  1.9 , median =  5.5  and p90 =  9.1

print('percentiles using interpolation = ', "lower")
p10 = np.percentile(b, 10,interpolation='lower') # return 10th percentile.
p50 = np.percentile(b, 50,interpolation='lower') # return 50th percentile, e.g median.
p90 = np.percentile(b, 90,interpolation='lower') # return 90th percentile.
print('p10 = ',p10,', median = ',p50,' and p90 = ',p90)
#p10 =  1 , median =  5  and p90 =  9

print('percentiles using interpolation = ', "higher")
p10 = np.percentile(b, 10,interpolation='higher') # return 10th percentile.
p50 = np.percentile(b, 50,interpolation='higher') # return 50th percentile, e.g median.
p90 = np.percentile(b, 90,interpolation='higher') # return 90th percentile.
print('p10 = ',p10,', median = ',p50,' and p90 = ',p90)
#p10 =  2 , median =  6  and p90 =  10

print('percentiles using interpolation = ', "midpoint")
p10 = np.percentile(b, 10,interpolation='midpoint') # return 10th percentile.
p50 = np.percentile(b, 50,interpolation='midpoint') # return 50th percentile, e.g median.
p90 = np.percentile(b, 90,interpolation='midpoint') # return 90th percentile.
print('p10 = ',p10,', median = ',p50,' and p90 = ',p90)
#p10 =  1.5 , median =  5.5  and p90 =  9.5

print('percentiles using interpolation = ', "nearest")
p10 = np.percentile(b, 10,interpolation='nearest') # return 10th percentile.
p50 = np.percentile(b, 50,interpolation='nearest') # return 50th percentile, e.g median.
p90 = np.percentile(b, 90,interpolation='nearest') # return 90th percentile.
print('p10 = ',p10,', median = ',p50,' and p90 = ',p90)
#p10 =  2 , median =  5  and p90 =  9

如果您的输入数组只包含整数值,则可能对整数值的百分位回答感兴趣。如果是这样,请选择插值模式,如“lower”、“higher”或“nearest”。


2
感谢您提到“插值”选项,因为没有它,输出结果会误导。 - Cypher

2
为了计算一系列数据的百分位数,请运行以下命令:
from scipy.stats import rankdata
import numpy as np

def calc_percentile(a, method='min'):
    if isinstance(a, list):
        a = np.asarray(a)
    return rankdata(a, method=method) / float(len(a))

例如:

a = range(20)
print {val: round(percentile, 3) for val, percentile in zip(a, calc_percentile(a))}
>>> {0: 0.05, 1: 0.1, 2: 0.15, 3: 0.2, 4: 0.25, 5: 0.3, 6: 0.35, 7: 0.4, 8: 0.45, 9: 0.5, 10: 0.55, 11: 0.6, 12: 0.65, 13: 0.7, 14: 0.75, 15: 0.8, 16: 0.85, 17: 0.9, 18: 0.95, 19: 1.0}

1

如果您需要答案是输入numpy数组的成员:

需要补充的是,numpy中的百分位函数默认将输出计算为输入向量中两个相邻条目的线性加权平均值。 在某些情况下,人们可能希望返回的百分位数是向量的实际元素,在这种情况下,从v1.9.0开始,您可以使用“interpolation”选项,其中包括“lower”,“higher”或“nearest”。

import numpy as np
x=np.random.uniform(10,size=(1000))-5.0

np.percentile(x,70) # 70th percentile

2.075966046220879

np.percentile(x,70,interpolation="nearest")

2.0729677997904314

后者是向量中的一个实际条目,而前者是两个边界百分位数之间的两个向量条目的线性插值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接