Pandas: moyenne pondérée

Créé le 30 avr. 2015  ·  46Commentaires  ·  Source: pandas-dev/pandas

Un paramètre étiqueté « poids » dans la méthode df.mean serait extrêmement utile. Dans numpy, cette fonctionnalité est fournie via np.average au lieu de np.mean, ce qui, je suppose, est la manière dont des fonctionnalités similaires seraient ajoutées aux pandas.

ex fonctionnalité demandée :

> a = np.array([[2,4,6],[8,10,12]])
> w = [.5, 0, .5]
> np_avg = np.average(a, weights = w, axis=1)
#output ->  array([ 4., 10.])
> pd_avg = pandas.DataFrame(a).mean(weights = w, axis=1)
#desired output -> series with entries 4 and 10

S'il s'agit d'une fonctionnalité souhaitée, je la compléterai et soumettrai une demande d'extraction si vous le souhaitez. Si c'est déjà quelque part que j'ai oublié, mes excuses, j'ai essayé de regarder attentivement.

Enhancement Numeric Window

Commentaire le plus utile

Je suis d'accord avec @bgrayburn , les statistiques pondérées seraient très utiles chez les pandas. On peut utiliser statsmodel mais étendre les méthodes DataFrame pour utiliser le poids serait très utile pour les personnes utilisant des données d'enquête pondérées.

Tous les 46 commentaires

Pourquoi ne pas simplement écrire :

pd_avg = (np.array(w) * pandas.DataFrame(a)).mean(axis=1)

Je suis d'accord avec @bgrayburn , les statistiques pondérées seraient très utiles chez les pandas. On peut utiliser statsmodel mais étendre les méthodes DataFrame pour utiliser le poids serait très utile pour les personnes utilisant des données d'enquête pondérées.

@Stephan : Je suis d'accord que votre extrait de code accomplit (presque) la même chose
d'un point de vue fonctionnel, mais du point de vue de la lisibilité du code, et
un point de vue de réutilisation de code, incluant un paramètre de poids semble optimal. Aussi
le paramètre de poids de numpy normalise automatiquement le vecteur de poids qui
est aussi extrêmement utile.

Le samedi 2 mai 2015 à 15h04, Mahdi Ben Jelloul [email protected]
a écrit:

Je suis d'accord avec @bgrayburn https://github.com/bgrayburn , pondéré
les statistiques seraient très utiles chez les pandas. On peut utiliser statsmodel mais
étendre les méthodes DataFrame pour utiliser le poids serait très utile pour les personnes
en utilisant des données d'enquête pondérées.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/pydata/pandas/issues/10030#issuecomment -98386585.

D'accord, assez juste. Cela semble à la portée des pandas. Nous avons récemment ajouté une méthode sample qui inclut un argument similaire weights (#9666) qui pourrait être utile comme point de départ.

@shoyer a demandé ailleurs (#10000) une liste de méthodes qui pourraient être améliorées par une version « pondérée ».
Presque toutes les fonctions statistiques que l'on peut trouver ici sont candidates. Je peux aussi penser aux calculs de describe, value_counts, qcut, hist et margin dans les tableaux croisés dynamiques.

Encore une fois, je pense que nous sommes ouverts à la plupart de ces changements (qui sont tous rétrocompatibles avec weights=None ). Le principal obstacle est que nous avons besoin d'implémentations, de documentation et de références pour montrer que nous ne ralentissons rien. Les PR seraient les bienvenus. Cependant, il vaudrait également la peine de vérifier si l'un d'entre eux pourrait être poussé en amont vers numpy.

@shoyer @benjello désolé pour le retard à ce sujet, je prévois toujours de soumettre un PR, je vais coder ce week-end.

en ce qui concerne la chose numpy, pour les moyens pondérés, ils utilisent .average que vous pouvez voir ici . Mon plan était de mettre en œuvre

pd_avg = (np.array(w) * pandas.DataFrame(a)).mean(axis=1)

à peu près comme écrit, en multipliant les colonnes de la trame de données d'entrée par le vecteur de poids. Alternativement, nous pourrions appeler np.average lorsqu'un paramètre de poids est présent, OU (3ème option) nous pourrions implémenter un pandas.DataFrame(a).average(weights=[...]) pour refléter les pandas.

une dernière question, la pondération doit-elle être applicable en mode axe=0 ou axe=1 ? Je suppose que oui, mais je voulais vérifier.

Faites-moi savoir vos préférences, ou si d'une manière ou d'une autre cela devrait être incorporé avec un changement plus important comme mentionné ci-dessus.

Meilleur

pourquoi n'ajoutez-vous pas simplement un mot-clé de poids à .mean ?

beaucoup plus cohérent dans l'API et nous n'implémentons pas la moyenne, je suppose, car c'est juste déroutant

et cela doit se répercuter sur nanops.py où tout le calcul réel est effectué - cela gère de nombreux types différents

@bgrayburn : je pense que la suggestion de suivie : utiliser une moyenne avec des poids est ce à quoi vous vous attendez pour une moyenne pondérée

+1 rendrait le travail avec des échantillons de microdonnées (pensez PUMS ) tellement plus agréable.

Je suis d'accord qu'il serait préférable d'incorporer cela directement dans des fonctions d'agrégation comme mean et var au lieu d'ajouter des méthodes spécialisées comme average .

+1 à @mattayes & @shoyer -- lorsque vous travaillez avec des données pondérées, vous voulez à peu près pondérer CHAQUE statistique et graphique que vous générez. C'est à peu près une nécessité d'avoir une option de pondération si vous travaillez avec de telles données.

L'ajout d'un argument de poids à autant de fonctions que possible au fil du temps semble être la voie à suivre dans la mesure où il ne sera pas géré dans numpy/statsmodels/matplotlib.

Stata, par exemple, permet une option de poids pour pratiquement toutes les fonctions. J'utilise très fréquemment le tabstat de stata avec l'option de poids et pour le moment, il n'y a pas de bon analogue chez les pandas à ma connaissance.

Une complication possible à considérer : il existe potentiellement différents types de poids. Stata, par exemple, définit 4 types de poids : fréquence, analytique, probabilité et importance (bien que le dernier ne soit qu'un fourre-tout abstrait). [http://www.stata.com/help.cgi?weight]

Je pense que dans ce fil, la plupart des gens pensent aux poids de fréquence, mais il pourrait être nécessaire de clarifier cela. En outre, cela n'aura probablement pas d'importance pour quelque chose comme la moyenne ou la médiane, mais cela pourrait affecter quelque chose comme la variance.

Il y a eu des discussions récentes sur la mise en œuvre d'algorithmes efficaces pour la partition pondérée (par exemple, pour faire une médiane pondérée) en amont dans NumPy, ainsi :
https://mail.scipy.org/pipermail/numpy-discussion/2016-February/075000.html

Dans tous les cas, une première ébauche qui utilise le tri pour faire une médiane pondérée serait toujours utile.

Depuis https://github.com/pydata/xarray/pull/650 :

Que diriez-vous de concevoir cela comme une interface de type groupby ? De la même manière que .rolling (ou .expanding & .ewm chez les pandas) ?

Ainsi par exemple ds.weighted(weights=ds.dim).mean() .

Et puis c'est extensible, propre, pandan-tic.

que feriez-vous d'autre avec une interface .weighted(..).mean() ?

IOW quels autres paramètres accepterait-il en dehors des poids réels ?

@jreback Je pense que .weighted() n'accepterait que weights , qui pourrait être soit un tableau, soit un appelable de la forme habituelle ( lambda df: .... ). Mais la classe WeightedMethods pourrait également exposer des implémentations pondérées d'autres méthodes, telles que std, var, median, sum, value_counts, hist, etc. J'envisagerais même de passer à sample et de déprécier le weights argumentation.

@shoyer, je peux voir une syntaxe à partir de cela, par exemple

df.weighted('A').B.mean() est assez clair

bien que df.B.mean(weights=df.A) soit tout aussi clair, alors cherchez un cas où cela est nettement plus agréable.

une idée de comment/fait R faire ça ? (Julia?)

J'ai utilisé R wtd.stats .

wtd.mean(x, weights=NULL, normwt="ignored", na.rm=TRUE)
wtd.var(x, weights=NULL, normwt=FALSE, na.rm=TRUE)
wtd.quantile(x, weights=NULL, probs=c(0, .25, .5, .75, 1), 
             type=c('quantile','(i-1)/(n-1)','i/(n+1)','i/n'), 
             normwt=FALSE, na.rm=TRUE)
wtd.Ecdf(x, weights=NULL, 
         type=c('i/n','(i-1)/(n-1)','i/(n+1)'), 
         normwt=FALSE, na.rm=TRUE)
wtd.table(x, weights=NULL, type=c('list','table'), 
          normwt=FALSE, na.rm=TRUE)
wtd.rank(x, weights=NULL, normwt=FALSE, na.rm=TRUE)
wtd.loess.noiter(x, y, weights=rep(1,n), robust=rep(1,n), 
                 span=2/3, degree=1, cell=.13333, 
                 type=c('all','ordered all','evaluate'), 
                 evaluation=100, na.rm=TRUE)

@benjello hmm, c'est intéressant.

bien que df.B.mean(weights=df.A) soit tout aussi clair, alors recherchez un cas où cela est nettement plus agréable.

À première vue, cela a l'air aussi bien. Mais du point de vue de la conception de l'API, l'ajout d'un argument de mot-clé pour weights est beaucoup moins élégant.

Le fait d'être « pondéré » est orthogonal au type de calcul statistique. Avec cette proposition, au lieu d'ajouter l'argument mot-clé weights à des N différentes méthodes, nous définissons une seule méthode weighted et y ajoutons des méthodes statistiques qui correspondent exactement à la signature de les mêmes méthodes sur DataFrame/Series. Cela montre clairement que toutes ces méthodes partagent la même approche et empêchent les signatures de méthode de développer des arguments supplémentaires qui déclenchent des chemins de code entièrement indépendants (ce qui est un signe d'odeur de code).

Séparément : peut-être que weightby est un nom légèrement meilleur que weighted ? Cela suggère plus de similitude avec groupby .

ouais je pense que je pourrais me remettre de .weightby(...) . Ce n'est pas si difficile car la plupart des machines existent déjà...... alors si quelqu'un veut faire un prototype...!

Le fait d'être « pondéré » est orthogonal au type de calcul statistique.

Pouvez-vous développer?

Comment définissons-nous les poids ici ? Les poids négatifs sont-ils acceptés (je pense que le domaine pourrait dépendre de la fonction) ? Les poids doivent-ils être de la même longueur que les points de données ? Cette API effectue-t-elle une validation ou est-elle différée vers les fonctions de statistiques (si les poids peuvent être un lambda , j'imagine que c'est le dernier) ?

Comment définissons-nous les poids ici ? Les poids négatifs sont-ils acceptés (je pense que le domaine pourrait dépendre de la fonction) ? Les poids doivent-ils être de la même longueur que les points de données ?

Ma vision de l'argument weights est assez similaire à la façon dont il est actuellement défini sur DataFrame.sample :
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sample.html

Les poids doivent donc être des nombres non négatifs pouvant être alignés avec l'objet pondéré. Ils n'ont pas besoin d'être prénormalisés, bien qu'il devrait peut-être y avoir un argument de mot-clé sur weightby pour indiquer que cela n'a pas besoin d'être vérifié (s'il s'agit en fait d'un réel problème de performances). Nous pouvons également ajouter un argument axis , qui vous permet de sélectionner l'axe sur lequel les poids sont appliqués. Cela permettrait par exemple de pondérer différemment les colonnes d'un DataFrame.

Cette API effectue-t-elle une validation ou est-elle différée vers les fonctions de statistiques (si les poids peuvent être un lambda, j'imagine que c'est ce dernier) ?

Idéalement, nous pourrions partager la validation entre différentes méthodes sur weightby . Je ne pense pas qu'il soit particulièrement important de valider avant ou après l'appel de la sous-méthode sur l'objet WeightByMethods .

Je vois. Ça a du sens. Les endroits où j'ai rencontré des « pondérations » négatives incluent le calcul des rendements du portefeuille (qui est une combinaison linéaire, mais pas nécessairement convexe, des rendements des titres individuels), mais ce type de « pondérations » est bien sûr moins dense.

salut les gars,

Je pense que c'est une excellente idée! Cependant, quand on essaie de regarder plus profondément, les choses ne sont pas aussi simples que prévu. Par exemple, ce sont les types de poids utilisés dans Stata

    1.  fweights, or frequency weights, are weights that indicate the number of duplicated observations.

    2.  pweights, or sampling weights, are weights that denote the inverse of the probability that the
        observation is included because of the sampling design.

    3.  aweights, or analytic weights, are weights that are inversely proportional to the variance of an
        observation; that is, the variance of the jth observation is assumed to be sigma^2/w_j, where w_j are
        the weights.  Typically, the observations represent averages and the weights are the number of elements
        that gave rise to the average.  For most Stata commands, the recorded scale of aweights is irrelevant;
        Stata internally rescales them to sum to N, the number of observations in your data, when it uses them.

    4.  iweights, or importance weights, are weights that indicate the "importance" of the observation in some
        vague sense.  iweights have no formal statistical definition; any command that supports iweights will
        define exactly how they are treated.  Usually, they are intended for use by programmers who want to
        produce a certain computation.

regardez aussi ici une comparaison plus approfondie http://www.ats.ucla.edu/stat/sas/faq/weights.htm

L'essentiel est : cela vaut vraiment la peine d'avoir une bonne discussion sur les poids avant de les inclure.

Voulez-vous être super flexible et mettre la responsabilité sur l'utilisateur ? Ou voulez-vous avoir des poids non négatifs simples et clairs (d'où une forte limitation sur ce qui peut être fait ?)

Tout dépend du nombre de mots-clés que vous souhaitez ajouter à la fonction pd.weight .

@randomgambit eh bien, un problème beaucoup plus important est que quelqu'un se porte volontaire pour mettre cela en œuvre. Avoir la discussion, c'est bien. Mais quelqu'un devrait intervenir.

@Jeff Reback, ouais désolé, j'ai dit que je le ferais il y a un bon moment, mais la vie est
a eu raison de moi. Je le ferais toujours si je peux trouver le temps, mais n'importe qui
autrement disposé devrait certainement faire avancer les choses.

@bgrayburn haha, t'en veux pas :>

hehe assez juste @jreback , mais comme je vous l'ai dit, je n'ai pas les compétences pour aider à coder. Je ne peux partager mes remarques (je suis un doctorat en économie qui fait de la recherche universitaire) que si vous pensez que c'est utile.

Je ne suis peut-être pas la meilleure personne pour essayer de résoudre ce problème - mais je suis particulièrement intéressé à avoir une moyenne pondérée pour le groupby. Spécifiquement pour qu'il puisse être inclus dans un dictionnaire groupby().agg(). Je ne veux pas dupliquer ce que quelqu'un d'autre a fait à ce sujet. Donc, si quelqu'un peut m'indiquer d'autres ressources ou parties spécifiques du code où je peux commencer à regarder cela, je lui en serais reconnaissant.

Un problème à considérer est que .weightedby() peut devoir être combiné avec .resample() , .groupby() et des amis : data.resample("1D").weightedby("count")["size"].mean() , data.groupby("category").weightedby("count")["size"].mean() .

ok j'ai une implémentation ici

Fonctionne bien (je n'ai pas encore complètement testé l'API, mais .sum()/.mean() ok pour l'instant).

In [1]: df = DataFrame({'A': [1, 2, 3, 4],
   ...:                 'B': [1, 2, 3, 4]})

In [2]: df
Out[2]: 
   A  B
0  1  1
1  2  2
2  3  3
3  4  4

In [3]: w = df.weightby('A')

In [4]: w.sum()
Out[4]: 
B    3.0
dtype: float64

In [5]: w.mean()
Out[5]: 
B    0.75
dtype: float64

In [6]: w._weights
Out[6]: array([ 0.1,  0.2,  0.3,  0.4])

In [7]: w.sample(2)
Out[7]: 
   B
2  3
1  2

cc @josef-pkt

donc je fais juste values * normalized_weights puis je cours à travers nos routines pour sum/mean.

que diriez-vous de moments plus élevés, var , skew , kurt ?

( @jreback désolé d'être en retard, j'ai déjà écrit ceci il y a quelques jours mais j'ai oublié d'appuyer sur « commentaire », et maintenant je vois que vous avez ouvert un PR)

Personnellement, je trouve la syntaxe weightby un peu maladroite, et je pense qu'un argument weights= peut être plus facile à comprendre pour les utilisateurs. Mais je vois qu'il y avait quelques défenseurs de cette syntaxe ci-dessus.

Je trouve également que la "référence" (en tant qu'API similaire) à groupby/resample/rolling n'est pas vraiment un plus. Dans mon esprit, il s'agit d'un concept totalement différent de groupby/resample/rolling, qui consiste à diviser les données en groupes, à appliquer une fonction et à réassembler les résultats dans une nouvelle trame (comment est divisé et s'il y a chevauchement entre les groupes, bien sûr, diffère selon les méthodes).

Bonjour! travail incroyable de @jreback comme toujours. Pour ce que ça vaut (c'est-à-dire au cas où vous vous ficheriez de mon conseil), je suis également d'accord avec @jorisvandenbossche pour

Le concept de pondération est _dépendant du calcul_. Vous souhaiterez peut-être obtenir une moyenne pondérée, mais aussi une somme sans pondération.

Donc quelque chose comme
df.sum(weight = df.myweights) ou df.groupby('jeff').mean(weights = df.myweights) me semble plus naturel.

J'ai réalisé que j'avais commenté le PR (mais mis ci-dessous aussi)

voir ici : https://github.com/pandas-dev/pandas/pull/15031#issuecomment -269992903

Il est assez simple de passer à une API comme la suivante (en fait, l'implémentation réelle est déjà comme celle-ci, mais je passe un kwargs caché _weights ).

df.sum(...., weights=...)
df.groupby(...).sum(...., weights=...)
df.sample(....weights=...) (reste le même)

J'arracherais le code de validation de poids et le mettrais ailleurs (en tant que fonction).

Le seul inconvénient de cela, il serait difficile de spécifier ensuite différents types de poids (comme le lien dans la section supérieure), car nous pourrions alors avoir besoin d'ajouter d'autres kwargs comme iweights, aweights etc. .

Package intéressant traitant des calculs pondérés ici - https://github.com/jsvine/weightedcalcs. Possède une API du type weightby .

Je recherche des moyennes pondérées en groupe par agrégats, où les poids sont une autre colonne de la base de données. Ce fil inclut-il un support pour cela? Pour expliciter ma question :

lcc:

snid    mjd band    flux    weights
obsHistID                   
609734  141374000   60493.416959    lsstg   2.825651e-09    6.442312e+20
609733  141374000   60493.416511    lsstg   2.893961e-09    5.962141e+20
609732  141374000   60493.416062    lsstg   2.834461e-09    6.590458e+20
....
611542  141374000   60495.426047    lssti   6.722778e-09    1.307280e+20
610790  141374000   60494.432074    lsstz   6.619978e-09    6.156260e+19

et je fais des opérations comme :
grouped = lcc.groupby(['snid', 'band', 'night']) res = grouped.agg(dict(flux=np.mean))
Ce que je veux vraiment faire, c'est rapide

res = grouped.agg(dict(flux=weightedmean(weights='weights'))

Le vrai problème est que cela nécessite deux colonnes dans l'entrée agrégée. J'ai examiné des solutions de contournement comme celles suggérées ici, mais je trouve que cela est lent :

Dans mon cas, lorsque la trame de données d'origine contient environ 10 000 lignes et 10 000 groupes, un np.mean direct de

Même problème ici.
Pour le moment, j'utilise .apply comme solution de contournement, mais c'est lent.
Pouvoir appeler .agg sur des objets groupby en utilisant plus d'une colonne en entrée serait très apprécié.

Vous pouvez améliorer considérablement les performances en n'utilisant pas apply, mais en créant le calcul à partir des opérations vectorisées existantes. Exemple de moyenne ci-dessous.

In [49]: df = pd.DataFrame({'id': np.repeat(np.arange(100, dtype='i8'), 100),
    ...:                    'v': np.random.randn(10000),
    ...:                    'w': np.random.randn(10000)})


In [46]: %timeit mean1 = df.groupby('id').apply(lambda x: (x['v'] * x['w']).sum() / x['w'].sum())
32.4 ms ± 2.25 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [47]: %%timeit
    ...: df['interim'] = df['v'] * df['w']
    ...: gb = df.groupby('id')
    ...: mean2 = gb['interim'].sum() / gb['w'].sum()
1.21 ms ± 16.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [48]: np.allclose(mean1, mean2)
Out[48]: True

C'est tellement compliqué entre les types de poids, l'API, etc. mais juste pour intervenir pendant que les choses sont fraîches dans mon esprit :

D'un point de vue statistique, les statistiques de base semblent se diviser en 2 catégories :

  1. moyennes et statistiques d'ordre (min/max/médiane/quantiles)
  2. moments d'ordre supérieur (std dev/variance/skew/kurtosis)

Je pense que la première catégorie est très simple à gérer. Je suis au mieux un statisticien amateur, mais je pense qu'il n'y a vraiment qu'une seule méthode de base pour calculer la moyenne/max/médiane, etc.

Inversement, std dev & variance sont beaucoup plus compliqués que vous ne le pensez - non pas que les mathématiques soient si difficiles, mais plus que "std dev" peut signifier plus d'une chose ici. Vraiment un excellent article ici qui expose les problèmes:
https://www.stata.com/support/faqs/statistics/weights-and-summary-statistics/

Par exemple, si vous saisissez ces deux commandes dans stata :
sum x [fw=weight], detail
sum x [aw=weight], detail
vous obtiendrez les mêmes résultats pour toutes les statistiques sauf std et var

De plus, dans la mesure où les pandas transmettent ce genre de chose aux modèles de statistiques, ils ont ici une bibliothèque qui pondère la plupart des statistiques de base (bien que les valeurs min et max semblent manquantes). Voir ce lien pour en savoir plus (une réponse récente que j'ai écrite à SO en utilisant la bibliothèque statsmodel):

https://stackoverflow.com/questions/17689099/using-describe-with-weighted-data-mean-standard-deviation-median-quantil/47368071#47368071

regardez, je suis désolé mais c'est en grande partie incorrect. les statistiques pondérées sont vraiment des trucs de base

@randomgambit

OK, alors laquelle de ces réponses est correcte ?

sum x [fw=weight], detail
sum x [aw=weight], detail

FWI :
J'avais besoin d'un quantile pondéré rééchantillonné et je l'ai mis en œuvre comme suit.

def resample_weighted_quantile(frame, weight=None, rule='D', q=0.5):
    if weight is None:
        return frame.resample(rule).apply(lambda x: x.quantile(q))
    else:
        data = [series.resample(rule).apply(_weighted_quantile, weight=weight[col], q=q).rename(col)
                          for col, series in frame.items()]
        return pd.concat(data, axis=1)

def _weighted_quantile(series, weight, q):
    series = series.sort_values()
    cumsum = weight.reindex(series.index).cumsum()
    cutoff = cumsum.iloc[-1] * q
    return series[cumsum >= cutoff].iloc[0]

frame et weight sont des dataframes avec le même index et les mêmes colonnes. Cela pourrait probablement être optimisé, mais au moins cela fonctionne.

Ce serait un excellent ajout aux pandas!

Cette page vous a été utile?
0 / 5 - 0 notes