Подтвердить что ты не робот

Сопоставьте каждое значение списка с соответствующим процентили

Я хотел бы создать функцию, которая принимает (отсортированный) список в качестве своего аргумента и выводит список, содержащий каждый элемент, соответствующий процентилю.

Например, 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].

4b9b3361

Ответ 1

Я думаю, что ваш пример ввода/вывода не соответствует типичным способам вычисления процентиля. Если вы вычисляете процентиль как "пропорцию точек данных, строго меньших этого значения", то верхнее значение должно быть 0,8 (поскольку 4 из 5 значений меньше самого большого). Если вы подсчитаете его как "процент точек данных, меньших или равных этому значению", то нижнее значение должно быть 0,2 (поскольку 1 из 5 значений равно наименьшему). Таким образом, процентили будут [0, 0.2, 0.4, 0.6, 0.8] или [0.2, 0.4, 0.6, 0.8, 1]. Ваше определение, по-видимому, представляет собой "количество точек данных, строго меньших этого значения, которое рассматривается как доля от числа точек данных, не равных этому значению", но, по моему опыту, это не общее определение (см., Например, wikipedia).

При типичных определениях процентилей процентиль точки данных равен ее рангу, деленному на количество точек данных. (См. Например этот вопрос по статистике 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]

(Я использовал набор данных, содержащий ссылки, чтобы проиллюстрировать, что происходит в таких случаях.)

Метод "ранга" присваивает связанным группам ранг, равный среднему числу рангов, которые они будут покрывать (т.е. трехсторонняя связь для 2-го места получает звание 3, потому что она "занимает" звания 2, 3 и 4). "Слабый" метод присваивает процентиль, исходя из доли точек данных, меньших или равных данной точке; "строгий" - это то же самое, но счет пропорции точек строго меньше данной точки. "Средним" методом является среднее из последних двух.

Как заметил Кевин Х. Лин, вызов 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])

В последнем случае ранги корректируются на единицу, чтобы заставить их начинать с 0 вместо 1. (я пропустил "средний", но его можно было легко получить, усреднив результаты последних двух методов.)

Я сделал несколько таймингов. С небольшими данными, такими как в вашем примере, использование rankdata несколько медленнее, чем решение Кевина Х. Линя (предположительно из-за того, что накладные расходы несут при преобразовании вещей в массивы numpy под капотом), но быстрее, чем вызов 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 в 10 раз быстрее, чем Kevin 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

Это преимущество будет только более выраженным в больших и больших наборах данных.

Ответ 2

Я думаю, что вы хотите scipy.stats.percentileofscore

Пример:

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

Ответ 3

Чистая версия решения Кевина

Как сказал Кевин, оптимальное решение работает в O (n log (n)) времени. Вот быстрая версия его кода в numpy, который работает почти в то же время, что и stats.rankdata:

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

PS. Это один из моих любимых трюков в numpy.

Ответ 4

С точки зрения сложности, я думаю, что ответ 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) для сортировки.

EDIT: в зависимости от вашего определения "процентиль" это может не всегда давать правильный результат. См. Ответ BrenBarn для получения дополнительных пояснений и лучшего решения, которое использует scipy/numpy.

Ответ 5

это может показаться излишним, но как насчет этого:

def percentile(x):
    pc = float(1)/(len(x)-1)
    return ["%.2f"%(n*pc) for n, i in enumerate(x)]

EDIT:

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

Если я правильно вас понимаю, все, что вы хотите сделать, это определить процентиль, который этот элемент представляет в массиве, насколько массив находится перед этим элементом. как в [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]

Ответ 7

Для меня лучшим решением является использование QuantileTransformer в sklearn.preprocessing.

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.  ]

Примечание: эта функция имеет две характерные особенности:

  1. исходные данные необязательно сортируются.
  2. исходные данные НЕ обязательно должны быть одним столбцом.

Ответ 8

Эта версия также позволяет передавать точные процентили, используемые для ранжирования:

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

Ответ 9

для чистой функции Python для вычисления процентного значения для данного элемента по сравнению с распределением населения (списком показателей) я scipy это из исходного кода 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, это лучше всего подходит для облегченного развертывания. Исходный код лучше фильтрует только ненулевые значения, но в остальном математика та же. Необязательный параметр управляет тем, как он обрабатывает значения, которые находятся между двумя другими значениями.

Для этого варианта использования можно вызвать эту функцию для каждого элемента в списке, используя функцию map().