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:
- Aidez-moi à corriger mon code ci-dessous? Ou
- 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.
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.
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]
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
.
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.
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]
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]
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:
- entrée les données brutes ne sont pas nécessairement triées.
- entrée les données brutes ne sont pas nécessairement à colonne unique.
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