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

Posted

技术标签:

【中文标题】将每个列表值映射到其相应的百分位数【英文标题】:Map each list value to its corresponding percentile 【发布时间】:2012-09-07 00:00:27 【问题描述】:

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

例如,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]

【问题讨论】:

我在你的代码中没有看到任何 numpy 或 scipy 的使用,为什么要使用这些标签? 当你说each elements corresponding percentile时,你的意思是quintile吗? @Martijin Pieters:我将 Numpy 和 SciPy 作为标签包括在内,因为我预计有人可能会将我引导到这些库。 @Gerrat:五分位数是分位数的一种特殊情况(即,数据被分成五个“桶”的情况,每个桶代表 20% 的数据)。通过分位数,我打算知道哪个百分比的数据低于某个观察值(请注意,观察数据的多个实例可能对应于相同的值;考虑 [1,2,3,4,4,4,4,17, 17,21])。 ***.com/questions/2374640/…的可能重复 【参考方案1】:

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

对于典型的百分位数定义,数据点的百分位数等于其排名除以数据点的数量。 (例如,请参阅 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]

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

“排名”方法为并列组分配的排名等于他们将覆盖的排名的平均值(即,获得第二名的三路并列获得排名 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])

在最后一种情况下,将排名向下调整 1 以使它们从 0 而不是 1 开始。(我省略了“均值”,但可以通过对后两种方法的结果进行平均来轻松获得。)

我做了一些计时。对于您的示例中的小数据,使用 rankdata 比 Kevin H. Lin 的解决方案要慢一些(可能是由于 scipy 将事物转换为引擎盖下的 numpy 数组时产生的开销),但比在 a 中调用 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 的 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.rankdata (github.com/scipy/scipy/blob/v0.16.0/scipy/stats/…) 的实现,您会发现它使用了argsort()。他们的算法本质上和我的一样,差异完全是由 Python 列表和 numpy 数组之间的差异来解释的。【参考方案2】:

我想你想要scipy.stats.percentileofscore

例子:

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

【讨论】:

具体来说,[percentileofscore(score) for score in original_list] @user1443118 和@Karl Knechtel:就是这样。具体到我的喜好,[percentileofscore(data, i, 'weak') for i in data] 是我正在寻找的。也非常Pythonic。 我认为这个解决方案不是最优的 O(n^2)。【参考方案3】:

就复杂性而言,我认为 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) 更好的排序。

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

【讨论】:

在我发布了这个答案之后,有人决定连续否决我所有的 SO 帖子。不酷... 谢谢!你说得对,使用scipy.stats.percentileofscore 的列表理解的答案是“不是最佳的”。我用timeit 为这两种方法计时,你的功能很棒。 对于 Python3,在 zip 周围添加 list 并从 xrange 中删除 x【参考方案4】:

Kevin 解决方案的纯 numpy 版本

正如 Kevin 所说,最优解在 O(n log(n)) 时间内有效。这是numpy 中他的代码的快速版本,它的工作时间几乎与stats.rankdata 相同:

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

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

【讨论】:

这不是最优的,因为排序的结果是重复值的排名不同。【参考方案5】:

这可能看起来过于简单,但是这个呢:

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]

【讨论】:

关闭,但这和上面阿拉丁的第一次尝试有同样的问题。【参考方案6】:

我尝试了 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]

【讨论】:

你能取消缩进代码的def assign_pct(x) 部分吗? 更正了缩进。【参考方案7】:

如果我理解正确,那么您要做的就是定义该元素在数组中表示的百分位数,即该元素之前的数组有多少。如 [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]

【讨论】:

关闭,但不完全。如果我尝试percentileList([1,2,3,4,4,5,5]),则返回列表[0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 0.99],我希望返回[0.0, 0.17, 0.33, 0.50, 0.50, 1.00, 1.00] 好吧,我想知道更多,关于你想做什么,重复的数字应该有相同的百分位,但它们的百分位仍然受到重复数字的影响?! 是的,虽然不同值的多个观测值都应具有相同的百分位数,但每个观测值仍会增加严格小于具有较大值的观测值的观测值计数。百分位数并不像某些人最初想象的那么简单。 @Jubbles,确实不是。我承认对你上面给出的例子有点困惑。最小值为0.0 最大值为100.0 似乎不一致。 感谢@Aladdin,我喜欢这个解决方案来解决我的问题。请注意,最好将其推广到空列表和具有一个元素的列表(这会导致 ZeroDivisionError 异常)。【参考方案8】:

对我来说,最好的解决方案是在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.  ]

注意:此功能有两个显着特点:

    输入的原始数据不一定是排序的。 输入的原始数据不一定是单列。

【讨论】:

【参考方案9】:

此版本还允许传递用于排名的精确百分位数:

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 范围

【讨论】:

【参考方案10】:

对于一个纯python函数来计算给定项目的百分位分数,与总体分布(分数列表)相比,我从@中提取了这个987654322@源代码并删除了所有对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 包要简单得多,因此这是轻量级部署的最佳选择。原始代码只过滤非零值更好,但除此之外,数学是相同的。可选参数控制它如何处理介于其他两个值之间的值。

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

【讨论】:

以上是关于将每个列表值映射到其相应的百分位数的主要内容,如果未能解决你的问题,请参考以下文章

python使用pandas中的groupby函数和agg函数计算每个分组数据的两个分位数(例如百分之10分位数和百分之90分位数)

如何计算列的每个值所在的百分位数? (Spark SQL)[重复]

深入浅出统计学02

使用matplotlib绘制百分位数

Pandas .. 分位数函数是不是需要排序数据来计算百分位数?

大熊猫是否表现出错误的百分位数?