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

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

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

В настоящее время, если я отправляю percentile([1,2,3,4,17]) , возвращается список [0.0, 0.0, 0.5, 0.0, 1.0] .

    Я думаю, что ваш пример ввода / вывода не соответствует типичным способам вычисления процентиля. Если вы вычисляете процентиль как «пропорцию точек данных, строго меньших этого значения», то верхнее значение должно быть 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 коэффициента в цикле, как в reptilicus's ответ:

     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 раз быстрее, чем 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 

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

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

    Пример:

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

    С точки зрения сложности я считаю, что ответ Рептилия не является оптимальным. Требуется время 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.

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

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

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

    PS. Это одно, если мои любимые трюки в numpy .

    это может выглядеть чрезмерно, но как насчет этого:

     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] 

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