J'aimerais créer une fonction qui prend une liste (triée) comme argument et génère une liste contenant le centile correspondant à 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-il s'il vous plaît soit:
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 renvoyée.
Je pense que votre exemple d’entrée/sortie ne correspond pas aux méthodes habituelles de calcul du centile. Si vous calculez le centile comme "proportion de points de données strictement inférieurs à cette valeur", la valeur maximale doit être de 0,8 (étant donné que 4 valeurs sur 5 sont inférieures à la plus grande). Si vous la calculez en "pourcentage de points de données inférieurs ou égaux à cette valeur", la valeur inférieure doit être de 0,2 (étant donné qu'une des cinq valeurs est égale à la plus petite). Ainsi, les centiles 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é 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 courante (voir par exemple Wikipédia ).
Avec les définitions typiques des centiles, le centile 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 statistiques SE demandant comment faire la même chose dans R.) Différences dans la manière de calculer le centile en différences dans la manière de calculer le rang (par exemple, comment classer des valeurs liées) . La fonction scipy.stats.percentileofscore
fournit quatre méthodes de calcul des centiles:
>>> 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 "rang" attribue aux groupes à égalité un rang égal à la moyenne des rangs qu’ils couvriraient (c’est-à-dire qu’une égalité à trois pour la 2e place obtient un rang de 3 car elle "occupe" les rangs 2, 3 et 4). La méthode "faible" attribue un centile basé sur la proportion de points de données inférieurs ou égaux à un point donné; "strict" est identique, mais compte la proportion de points strictement inférieur au point donné. La méthode "moyenne" est la moyenne des deux derniers.
Comme l'a noté Kevin H. Lin, appeler percentileofscore
dans une boucle est inefficace car il doit recalculer les rangs à chaque passe. Cependant, ces calculs de centiles peuvent être facilement répliqués à 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 ramenés d'un rang vers le bas pour les faire partir de 0 au lieu de 1. J'ai omis «moyen», mais on pourrait facilement l'obtenir en calculant la moyenne des résultats des deux dernières méthodes.)
J'ai fait des timings. Avec de petites données telles que celle de votre exemple, l'utilisation de rankdata
est un peu plus lente que la solution de Kevin H. Lin (probablement en raison de la surcharge de temps générée par la conversion de choses en tableaux numpy sous le capot), mais plus rapide que d'appeler percentileofscore
dans une boucle comme dans reptilicus réponse:
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 jeu de données volumineux, toutefois, l'avantage de performance de numpy prend effet et l'utilisation de rankdata
est 10 fois plus rapide que le list_to_percentiles
de Kevin:
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 deviendra plus prononcé que sur des jeux de données de plus en plus grands.
Je pense que tu veux scipy.stats.percentileofscore
Exemple:
percentileofscore([1, 2, 3, 4], 3)
75.0
percentiles = [percentileofscore(data, i) for i in data]
Comme Kevin l'a dit, la solution optimale fonctionne en 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 Ceci est un si mes astuces préférées dans numpy
.
En termes de complexité, je pense que la réponse de reptilicus n’est pas optimale. Cela prend O (n ^ 2) fois.
Voici une solution qui prend 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 la complexité temporelle optimale que vous pouvez obtenir. La raison approximative qui me semble optimale est que les informations de tous les centiles sont essentiellement équivalentes à celles de la liste triée et que vous ne pouvez pas obtenir mieux que O (n log n) pour le tri.
EDIT: Selon votre définition du "percentile", cela peut ne pas toujours donner le résultat correct. Voir la réponse de BrenBarn pour plus d'explications et pour une meilleure solution qui utilise scipy/numpy.
cela peut sembler exagéré, 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)]
MODIFIER:
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]
Si je vous ai bien compris, tout ce que vous voulez faire est de définir le centile que cet élément représente dans le tableau. 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]
Pour moi, la meilleure solution consiste à 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. ]
Remarque: cette fonction présente deux caractéristiques principales:
Cette version permet également de passer les valeurs exactes en centiles utilisées 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 savoir quelle est la valeur du nombre centile qui tombe pour les centiles 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