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

时间:2012-09-13 20:10:37

标签: python numpy scipy median percentile

我想创建一个以(排序)列表为参数的函数,并输出一个包含每个元素相应百分位数的列表。

例如,fn([1,2,3,4,17])会返回[0.0, 0.25, 0.50, 0.75, 1.00]

任何人都可以请:

  1. 帮我修改下面的代码? OR
  2. 提供比我的代码更好的替代方案,用于将列表中的值映射到相应的百分位数?
  3. 我目前的代码:

    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]

8 个答案:

答案 0 :(得分:30)

我认为您的示例输入/输出与计算百分位数的典型方法不对应。如果您将百分位数计算为&#34;数据点的比例严格小于此值&#34;,则最高值应为0.8(因为5个值中的4个小于最大值)。如果将其计算为&#34;数据点的百分比小于或等于此值&#34;,则底部值应为0.2(因为5个值中的1个等于最小值)。因此百分位数为[0, 0.2, 0.4, 0.6, 0.8][0.2, 0.4, 0.6, 0.8, 1]。您的定义似乎是&#34;数据点的数量严格小于此值,被视为不等于此值的数据点数的比例&#34;,但根据我的经验,这不是一个常见的定义(例如,见wikipedia)。

对于典型的百分位数定义,数据点的百分位数等于其等级除以数据点的数量。 (例如,请参阅Stats SE上的this question,询问如何在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]

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

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

正如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.(我省略了#34;意思是&#34;,但可以通过平均结果轻松获得后两种方法。)

我做了一些时间安排。对于像你的例子中那样的小数据,使用rankdata比使用Kevin H. Lin的解决方案要慢一点(可能是因为开销时将scipy转换为numpy数组),但速度要快于如同在reptilicus的回答中一样,在循环中调用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

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

答案 1 :(得分:14)

我想你想要scipy.stats.percentileofscore

示例:

percentileofscore([1, 2, 3, 4], 3)
75.0
percentiles = [percentileofscore(data, i) for i in data]

答案 2 :(得分:10)

就复杂性而言,我认为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

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

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

答案 3 :(得分: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]

答案 4 :(得分:2)

我尝试了 Scipy 的百分位分数,但结果证明我的一项任务非常慢。所以,简单地以这种方式实现它。如果需要弱排名,可以修改。


def assign_pct(X):
    mp = {}
    X_tmp = np.sort(X)
    pct = []
    cnt = 0
    for v in X_tmp:
        if v in mp:
            continue
        else:
            mp[v] = cnt
            cnt+=1
    for v in X:
        pct.append(mp[v]/cnt)
    return pct        

调用函数

assign_pct([23,4,1,43,1,6])

函数输出

[0.75, 0.25, 0.0, 1.0, 0.0, 0.5]

答案 5 :(得分: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]

答案 6 :(得分: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范围

答案 7 :(得分:0)

对于纯python函数,以计算给定项目的百分位数得分,与总体分布(得分列表)相比,我从{ {1}}源代码,并删除了所有对numpy的引用:

scipy

来源:https://github.com/scipy/scipy/blob/v1.2.1/scipy/stats/stats.py#L1744-L1835

鉴于百分位数的计算比人们想象的要难,但比完整的scipy / numpy / scikit包复杂得多,这是轻量级部署的最佳选择。原始代码仅对非零值进行了更好的过滤,但是在其他方面,数学是相同的。可选参数控制它如何处理两个其他值之间的值。

对于此用例,可以使用map()函数对列表中的每个项目调用此函数。