Percentile pondéré à l'aide de numpy
Est-il possible d'utiliser le numpy.fonction de percentile pour calculer le percentile pondéré? Ou est-ce que quelqu'un est au courant d'une fonction python alternative pour calculer le percentile pondéré?
merci!
9 réponses
malheureusement, numpy n'a pas de fonctions pondérées intégrées pour tout, mais, vous pouvez toujours mettre quelque chose ensemble.
def weight_array(ar, weights):
zipped = zip(ar, weights)
weighted = []
for i in zipped:
for j in range(i[1]):
weighted.append(i[0])
return weighted
np.percentile(weight_array(ar, weights), 25)
Entièrement vectorisé numpy solution
Voici le code que j'utilise. Il n'est pas optimal (que je ne peux pas écrire en numpy
), mais encore beaucoup plus rapide et plus fiable que la solution retenue
def weighted_quantile(values, quantiles, sample_weight=None, values_sorted=False, old_style=False):
""" Very close to numpy.percentile, but supports weights.
NOTE: quantiles should be in [0, 1]!
:param values: numpy.array with data
:param quantiles: array-like with many quantiles needed
:param sample_weight: array-like of the same length as `array`
:param values_sorted: bool, if True, then will avoid sorting of initial array
:param old_style: if True, will correct output to be consistent with numpy.percentile.
:return: numpy.array with computed quantiles.
"""
values = numpy.array(values)
quantiles = numpy.array(quantiles)
if sample_weight is None:
sample_weight = numpy.ones(len(values))
sample_weight = numpy.array(sample_weight)
assert numpy.all(quantiles >= 0) and numpy.all(quantiles <= 1), 'quantiles should be in [0, 1]'
if not values_sorted:
sorter = numpy.argsort(values)
values = values[sorter]
sample_weight = sample_weight[sorter]
weighted_quantiles = numpy.cumsum(sample_weight) - 0.5 * sample_weight
if old_style:
# To be convenient with numpy.percentile
weighted_quantiles -= weighted_quantiles[0]
weighted_quantiles /= weighted_quantiles[-1]
else:
weighted_quantiles /= numpy.sum(sample_weight)
return numpy.interp(quantiles, weighted_quantiles, values)
Exemples:
poids pesé([1, 2, 9, 3.2, 4], [0.0, 0.5, 1.])
array ([1. , 3.2, 9. ])
poids pesé([1, 2, 9, 3.2, 4], [0.0, 0.5, 1.], sample_weight=[2, 1, 2, 4, 1])
array ([1. , 3.2, 9. ])
une solution rapide, en triant d'abord puis en interpolant:
def weighted_percentile(data, percents, weights=None):
''' percents in units of 1%
weights specifies the frequency (count) of data.
'''
if weights is None:
return np.percentile(data, percents)
ind=np.argsort(data)
d=data[ind]
w=weights[ind]
p=1.*w.cumsum()/w.sum()*100
y=np.interp(percents, p, d)
return y
excuses pour la réponse supplémentaire (non originale) (pas assez de rep pour commenter sur @nayyarv's). Sa solution a fonctionné pour moi (c'est à dire. il reproduit le comportement par défaut de np.percentage
), mais je pense que vous pouvez éliminer la boucle for avec des indices de la façon dont l'original np.percentage
est écrit.
def weighted_percentile(a, q=np.array([75, 25]), w=None):
"""
Calculates percentiles associated with a (possibly weighted) array
Parameters
----------
a : array-like
The input array from which to calculate percents
q : array-like
The percentiles to calculate (0.0 - 100.0)
w : array-like, optional
The weights to assign to values of a. Equal weighting if None
is specified
Returns
-------
values : np.array
The values associated with the specified percentiles.
"""
# Standardize and sort based on values in a
q = np.array(q) / 100.0
if w is None:
w = np.ones(a.size)
idx = np.argsort(a)
a_sort = a[idx]
w_sort = w[idx]
# Get the cumulative sum of weights
ecdf = np.cumsum(w_sort)
# Find the percentile index positions associated with the percentiles
p = q * (w.sum() - 1)
# Find the bounding indices (both low and high)
idx_low = np.searchsorted(ecdf, p, side='right')
idx_high = np.searchsorted(ecdf, p + 1, side='right')
idx_high[idx_high > ecdf.size - 1] = ecdf.size - 1
# Calculate the weights
weights_high = p - np.floor(p)
weights_low = 1.0 - weights_high
# Extract the low/high indexes and multiply by the corresponding weights
x1 = np.take(a_sort, idx_low) * weights_low
x2 = np.take(a_sort, idx_high) * weights_high
# Return the average
return np.add(x1, x2)
# Sample data
a = np.array([1.0, 2.0, 9.0, 3.2, 4.0], dtype=np.float)
w = np.array([2.0, 1.0, 3.0, 4.0, 1.0], dtype=np.float)
# Make an unweighted "copy" of a for testing
a2 = np.repeat(a, w.astype(np.int))
# Tests with different percentiles chosen
q1 = np.linspace(0.0, 100.0, 11)
q2 = np.linspace(5.0, 95.0, 10)
q3 = np.linspace(4.0, 94.0, 10)
for q in (q1, q2, q3):
assert np.all(weighted_percentile(a, q, w) == np.percentile(a2, q))
Je ne sais pas ce que signifie percentile pondéré, mais de la réponse de @Joan Smith, il semble que vous avez juste besoin de répéter chaque élément dans ar
, vous pouvez utiliser numpy.repeat()
:
import numpy as np
np.repeat([1,2,3], [4,5,6])
le résultat est:
array([1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3])
j'utilise cette fonction pour mes besoins:
def quantile_at_values(values, population, weights=None):
values = numpy.atleast_1d(values).astype(float)
population = numpy.atleast_1d(population).astype(float)
# if no weights are given, use equal weights
if weights is None:
weights = numpy.ones(population.shape).astype(float)
normal = float(len(weights))
# else, check weights
else:
weights = numpy.atleast_1d(weights).astype(float)
assert len(weights) == len(population)
assert (weights >= 0).all()
normal = numpy.sum(weights)
assert normal > 0.
quantiles = numpy.array([numpy.sum(weights[population <= value]) for value in values]) / normal
assert (quantiles >= 0).all() and (quantiles <= 1).all()
return quantiles
- il est vectorisé aussi loin que j'ai pu aller.
- Il a beaucoup de vérifications.
- il fonctionne avec des flotteurs comme poids.
- il peut fonctionner sans poids (→poids égaux).
- il peut calculer plusieurs quantiles à la fois.
multiplier les résultats par 100 Si vous voulez des percentiles au lieu de quantiles.
comme mentionné dans les commentaires, la simple répétition des valeurs est impossible pour les masses flottantes et impraticable pour les très grands ensembles de données. Il y a une bibliothèque qui fait des percentiles pondérés ici: http://kochanski.org/gpk/code/speechresearch/gmisclib/gmisclib.weighted_percentile-module.html Il a travaillé pour moi.
def weighted_percentile(a, percentile = np.array([75, 25]), weights=None):
"""
O(nlgn) implementation for weighted_percentile.
"""
percentile = np.array(percentile)/100.0
if weights is None:
weights = np.ones(len(a))
a_indsort = np.argsort(a)
a_sort = a[a_indsort]
weights_sort = weights[a_indsort]
ecdf = np.cumsum(weights_sort)
percentile_index_positions = percentile * (weights.sum()-1)+1
# need the 1 offset at the end due to ecdf not starting at 0
locations = np.searchsorted(ecdf, percentile_index_positions)
out_percentiles = np.zeros(len(percentile_index_positions))
for i, empiricalLocation in enumerate(locations):
# iterate across the requested percentiles
if ecdf[empiricalLocation-1] == np.floor(percentile_index_positions[i]):
# i.e. is the percentile in between 2 separate values
uppWeight = percentile_index_positions[i] - ecdf[empiricalLocation-1]
lowWeight = 1 - uppWeight
out_percentiles[i] = a_sort[empiricalLocation-1] * lowWeight + \
a_sort[empiricalLocation] * uppWeight
else:
# i.e. the percentile is entirely in one bin
out_percentiles[i] = a_sort[empiricalLocation]
return out_percentiles
C'est ma fonction, il donne un comportement identique à
np.percentile(np.repeat(a, weights), percentile)
avec moins de mémoire au-dessus. np.percentile est une implémentation O(n) donc il est potentiellement plus rapide pour les petits poids. Il a tous les cas de bord triés - c'est une solution exacte. Les réponses d'interpolation ci-dessus supposent linéaire, quand c'est un pas pour la plupart des cas, sauf quand le poids est 1.
disons que nous avons des données [1,2,3] avec des poids [3, 11, 7] et je veux le percentile de 25%. Mon ecdf est va être [3, 10, 21] et je suis à la recherche de la 5ème valeur. L'interpolation verra [3,1] et [10, 2] comme les allumettes et l'interpolation donnant 1,28 malgré être entièrement dans la 2ème bin avec une valeur de 2.
voici ma solution:
def my_weighted_perc(data,perc,weights=None):
if weights==None:
return nanpercentile(data,perc)
else:
d=data[(~np.isnan(data))&(~np.isnan(weights))]
ix=np.argsort(d)
d=d[ix]
wei=weights[ix]
wei_cum=100.*cumsum(wei*1./sum(wei))
return interp(perc,wei_cum,d)
il calcule simplement le CDF pondéré des données et ensuite il utilise pour estimer les percentiles pondérés.