Attribuer la valeur de chaque liste à son percentile correspondant

j'aimerais créer une fonction qui prend une liste (triée) comme argument et produit une liste contenant le centile correspondant de chaque élément.

par exemple, fn([1,2,3,4,17]) renvoie [0.0, 0.25, 0.50, 0.75, 1.00] .

quelqu'un Peut s'il vous plaît soit:

  1. Aidez-moi à corriger mon code ci-dessous? Ou
  2. offre une meilleure alternative que mon code pour mapper les valeurs dans une liste à leurs percentiles correspondants?

mon code actuel:

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

actuellement, si je soumets percentile([1,2,3,4,17]) , la liste [0.0, 0.0, 0.5, 0.0, 1.0] est retournée.

21
demandé sur Matthew Adams 2012-09-14 00:10:37

8 réponses

je pense que votre exemple input/output ne correspond pas aux méthodes typiques de calcul du centile. Si vous calculez le percentile comme "proportion de points de données strictement inférieure à cette valeur", alors la valeur supérieure devrait être 0,8 (puisque 4 de 5 valeurs sont inférieures à la plus grande). Si vous le calculez comme "pourcentage de points de données inférieur ou égal à cette valeur", alors la valeur inférieure devrait être 0,2 (puisque 1 de 5 valeurs égale la plus petite). Ainsi, les percentiles seraient [0, 0.2, 0.4, 0.6, 0.8] ou [0.2, 0.4, 0.6, 0.8, 1] . Votre définition semble être "le nombre de points de données strictement inférieur à cette valeur, considérée comme une proportion du nombre de points de données non égal à cette valeur", mais dans mon expérience ce n'est pas une définition commune (Voir par exemple wikipedia ).

avec les définitions typiques de percentile, le percentile d'un point de données est égal à son rang divisé par le nombre de points de données. (Voir, par exemple, cette question sur les Stats en SE demandant comment faire la même chose dans l'arrêt R.) des Différences dans la façon de calculer le percentile montant des différences dans la manière de calculer le rang (par exemple, comment le rang à égalité de valeurs). La fonction scipy.stats.percentileofscore fournit quatre façons de calculer les percentiles:

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

(j'ai utilisé un ensemble de données contenant des liens pour illustrer ce qui se passe dans de tels cas.)

la méthode du "rang" attribue un rang aux groupes liés égal à la moyenne des rangs qu'ils couvriraient (c.-à-d., un triple égalité pour la 2e place obtient un rang de 3 parce qu'il "prend" les rangs 2, 3 et 4). La méthode " faible "assigne un percentile basé sur la proportion de points de données inférieurs ou égaux à un point donné;" sévère " est le même mais compte proportion de points strictement inférieur au point donné. Le "dire" la méthode est la moyenne des deux derniers.

comme Kevin H. Lin l'a noté, appelant percentileofscore dans une boucle est inefficace puisqu'il doit recalculer les rangs à chaque passage. Toutefois, ces calculs de percentiles peuvent être facilement reproduits à l'aide de différentes méthodes de classement fournies par scipy.stats.rankdata , vous permettant de calculer tous les centiles à la fois:

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

dans le dernier cas les rangs sont ajustés vers le bas par un pour les faire commencer de 0 au lieu de 1. (J'ai omis "moyen", mais il pourrait facilement être obtenu en faisant la moyenne des résultats de ce dernier deux méthodes.)

j'ai fait quelques chronométrages. Avec de petites données comme celle dans votre exemple, l'utilisation de rankdata est un peu plus lente que la solution de Kevin H. Lin (probablement en raison de la scipy aérienne engage dans la conversion des choses en tableaux numpy sous le capot) mais plus rapide que d'appeler percentileofscore dans une boucle comme dans la réponse de 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

avec un grand ensemble de données, cependant, l'avantage de performance de numpy prend effet et en utilisant rankdata est 10 fois plus rapide que 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

cet avantage ne sera que plus prononcé sur les ensembles de données de plus en plus grands.

28
répondu BrenBarn 2017-04-13 12:44:13

je pense que vous voulez scipy.statistique.percentileofscore

exemple:

percentileofscore([1, 2, 3, 4], 3)
75.0
percentiles = [percentileofscore(data, i) for i in data]
13
répondu reptilicus 2012-09-13 20:59:46

version pure et légère de la solution de Kevin

comme Kevin l'a dit, la solution optimale fonctionne dans le temps O(N log(n)). Voici la version rapide de son code dans numpy , qui fonctionne presque en même temps que stats.rankdata :

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

PS. C'est un de mes tours préférés dans numpy .

10
répondu Alleo 2015-05-01 15:35:10

en termes de complexité, je pense que la réponse de reptilicus n'est pas optimale. Cela prend du temps (n^2).

Voici une solution qui prend du temps 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

Je ne suis pas sûr, mais je pense que c'est le temps optimal complexité que vous pouvez obtenir. La raison grossière que je pense que c'est optimal est parce que l'information de tous les centiles est essentiellement équivalent à l'information de la liste triée, et vous ne pouvez pas obtenir Mieux Que O (N log n) Pour tri.

modifier: selon votre définition de "percentile" cela peut ne pas toujours donner le bon résultat. Voir la réponse de BrenBarn pour plus d'explications et pour une meilleure solution qui fait usage de scipy/numpy.

8
répondu Kevin H. Lin 2015-08-06 17:58:26

cela peut sembler exagérément simplifié, mais qu'en est-il de ceci:

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]
2
répondu aschmid00 2012-09-13 20:56:30

si je vous comprends bien, tout ce que vous voulez faire, c'est définir le percentile que cet élément représente dans le tableau, Combien du tableau est avant cet élément. comme dans [1, 2, 3, 4, 5] devrait être [0.0, 0.25, 0.5, 0.75, 1.0]

je crois qu'un tel code suffira:

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]
1
répondu Mahmoud Aladdin 2012-09-13 20:45:09

pour moi, la meilleure solution est d'utiliser QuantileTransformer dans 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)

Voici la sortie

Input= [1, 2, 3, 4, 17]
Output= [ 0.    0.25  0.5   0.75  1.  ]

Note: Cette fonction présente deux caractéristiques importantes:

  1. entrée les données brutes ne sont pas nécessairement triées.
  2. entrée les données brutes ne sont pas nécessairement à colonne unique.
0
répondu user36624 2018-03-04 11:17:14

cette version permet également de passer les valeurs exactes des percentiles utilisés pour le classement:

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))

il est donc possible de déterminer la valeur du nombre percentile pour les percentiles fournis:

_x = np.random.randn(100, 1)
what_pctl_number_of(_x, 1.6, [25, 50, 75, 100])

sortie:

3

donc il frappe à 75 ~ 100 gamme

0
répondu mde 2018-05-07 20:39:05