将每个列表值映射到其对应的百分位数

Jub*_*les 23 python numpy scipy median percentile

我想创建一个函数,它将(有序)列表作为其参数,并输出一个包含每个元素的相应百分位数的列表.

例如,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
Run Code Online (Sandbox Code Playgroud)

目前,如果我提交percentile([1,2,3,4,17]),[0.0, 0.0, 0.5, 0.0, 1.0]则返回列表.

Bre*_*arn 38

我认为您的示例输入/输出与计算百分位数的典型方法不对应.如果将百分位数计算为"严格小于此值的数据点的比例",则最高值应为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].您的定义似乎是"数据点的数量严格小于此值,被视为不等于此值的数据点数量的比例",但根据我的经验,这不是一个常见的定义(例如参见维基百科) .

对于典型的百分位定义,数据点的百分位数等于其等级除以数据点的数量.(例如,请参阅有关Stats SE的这个问题,询问如何在R中执行相同的操作.)如何计算百分位数与计算排名的差异(例如,如何对并列值进行排名)的差异.该scipy.stats.percentileofscore函数提供了四种计算百分位数的方法:

>>> 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]
Run Code Online (Sandbox Code Playgroud)

(我使用了包含关系的数据集来说明在这种情况下会发生什么.)

"等级"方法为绑定组分配等于他们将覆盖的等级的平均值的等级(即,第二位的三向平行获得等级3,因为它"占据"等级2,3和4)."弱"方法基于小于或等于给定点的数据点的比例来分配百分位数; "严格"是相同的,但计算严格小于给定点的点的比例."均值"方法是后两者的平均值.

正如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])
Run Code Online (Sandbox Code Playgroud)

在最后一种情况下,将等级向下调整为1使其从0开始而不是1.(我省略了"均值",但可以通过平均后两种方法的结果来轻松获得.)

我做了一些时间安排.对于像你的例子中那样的小数据,使用rankdata比Kevin H. Lin的解决方案要慢一些(可能是由于开销将scipy转换为引擎盖下的numpy数组),但比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
Run Code Online (Sandbox Code Playgroud)

然而,对于大型数据集,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
Run Code Online (Sandbox Code Playgroud)

这种优势只会在越来越大的数据集上变得更加明显.


rep*_*cus 16

我想你想要scipy.stats.percentileofscore

例:

percentileofscore([1, 2, 3, 4], 3)
75.0
percentiles = [percentileofscore(data, i) for i in data]
Run Code Online (Sandbox Code Playgroud)

  • 我认为这个解决方案是O(n ^ 2),这不是最优的. (2认同)

All*_*leo 12

纯粹numpy版本的凯文解决方案

正如Kevin所说,最佳解决方案在O(n log(n))时间内工作.这是他的代码的快速版本,numpy几乎同时工作stats.rankdata:

percentiles = numpy.argsort(numpy.argsort(array)) * 100. / (len(array) - 1)
Run Code Online (Sandbox Code Playgroud)

PS.这是我最喜欢的技巧之一numpy.


Kev*_*Lin 11

就复杂性而言,我认为reptilicus的答案并非最佳.它需要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
Run Code Online (Sandbox Code Playgroud)

我不确定,但我认为这是您可以获得的最佳时间复杂度.我认为它是最优的粗略原因是因为所有百分位数的信息基本上等于排序列表的信息,并且你不能比O(n log n)更好地进行排序.

编辑:根据您对"百分位数"的定义,这可能并不总能给出正确的结果.有关更多解释和使用scipy/numpy的更好解决方案,请参阅BrenBarn的答案.

  • 在我发布这个答案后,有人决定连续向我的所有SO帖子投票.不酷... (3认同)
  • 对于Python3,在“zip”周围添加“list”,并从“xrange”中删除“x” (2认同)