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!

16
demandé sur user308827 2014-02-18 07:55:32

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)
4
répondu Joan Smith 2014-02-18 04:16:13

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

28
répondu Alleo 2015-12-05 11:12:21

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
9
répondu Kambrian 2016-02-22 17:25:36

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))
6
répondu grovduck 2015-08-26 00:08:23

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])
4
répondu HYRY 2014-02-18 05:43:46

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.

3
répondu PiHalbe 2015-02-25 13:35:48

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.

2
répondu Qwerty 2015-01-22 07:06:51
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.

2
répondu nayyarv 2015-08-16 10:32:27

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.

0
répondu Luca Jokull 2016-11-23 13:15:08