Bereken het gewogen gemiddelde met een panda’s/dataframe

Ik heb de volgende tabel. Ik wil een gewogen gemiddelde berekenen, gegroepeerd op elke datum op basis van de onderstaande formule. Ik kan dit doen met behulp van een standaard conventionele code, maar ervan uitgaande dat deze gegevens zich in een panda-dataframe bevinden, is er dan een eenvoudigere manier om dit te bereiken dan door middel van iteratie?

Date        ID      wt      value   w_avg
01/01/2012  100     0.50    60      0.791666667
01/01/2012  101     0.75    80
01/01/2012  102     1.00    100
01/02/2012  201     0.50    100     0.722222222
01/02/2012  202     1.00    80

01/01/2012 w_avg = 0,5 * ( 60/ som(60,80,100)) + .75 * (80/
som(60,80,100)) + 1,0 * (100/som(60,80,100))

02/01/2012 w_avg = 0,5 * ( 100/ som(100,80)) + 1,0 * ( 80/
som(100,80))


Antwoord 1, autoriteit 100%

Laten we eerst het voorbeeld van een panda-dataframe maken:

In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: index = pd.Index(['01/01/2012','01/01/2012','01/01/2012','01/02/2012','01/02/2012'], name='Date')
In [4]: df = pd.DataFrame({'ID':[100,101,102,201,202],'wt':[.5,.75,1,.5,1],'value':[60,80,100,100,80]},index=index)

Vervolgens wordt het gemiddelde van ‘wt’ gewogen op ‘waarde’ en gegroepeerd op de index verkregen als:

In [5]: df.groupby(df.index).apply(lambda x: np.average(x.wt, weights=x.value))
Out[5]: 
Date
01/01/2012    0.791667
01/02/2012    0.722222
dtype: float64

Als alternatief kan men ook een functie definiëren:

In [5]: def grouped_weighted_avg(values, weights, by):
   ...:     return (values * weights).groupby(by).sum() / weights.groupby(by).sum()
In [6]: grouped_weighted_avg(values=df.wt, weights=df.value, by=df.index)
Out[6]: 
Date
01/01/2012    0.791667
01/02/2012    0.722222
dtype: float64

Antwoord 2, autoriteit 67%

Ik denk dat ik dit met twee groupby’s zou doen.

Eerst die het “gewogen gemiddelde” berekent:

In [11]: g = df.groupby('Date')
In [12]: df.value / g.value.transform("sum") * df.wt
Out[12]:
0    0.125000
1    0.250000
2    0.416667
3    0.277778
4    0.444444
dtype: float64

Als u dit instelt als een kolom, kunt u eroverheen groeperen:

In [13]: df['wa'] = df.value / g.value.transform("sum") * df.wt

Nu is de som van deze kolom de gewenste:

In [14]: g.wa.sum()
Out[14]:
Date
01/01/2012    0.791667
01/02/2012    0.722222
Name: wa, dtype: float64

of mogelijk:

In [15]: g.wa.transform("sum")
Out[15]:
0    0.791667
1    0.791667
2    0.791667
3    0.722222
4    0.722222
Name: wa, dtype: float64

Antwoord 3, autoriteit 30%

Volgens mij is het volgende een elegante oplossing voor dit probleem van:(Pandas DataFrame-aggregatiefunctie met meerdere kolommen)

grouped = df.groupby('Date')
def wavg(group):
    d = group['value']
    w = group['wt']
    return (d * w).sum() / w.sum()
grouped.apply(wavg)

Antwoord 4, autoriteit 22%

Ik heb de tabel opgeslagen in het .csv-bestand

df=pd.read_csv('book1.csv')
grouped=df.groupby('Date')
g_wavg= lambda x: np.average(x.wt, weights=x.value)
grouped.apply(g_wavg)

Antwoord 5, autoriteit 13%

Als snelheid een belangrijke factor voor u is, is vectoriseren van cruciaal belang. Dus, gebaseerd op het antwoord van Andy Hayden, is hier een oplossing die alleen de native functies van Panda gebruikt:

def weighted_mean(df, values, weights, groupby):
    df = df.copy()
    grouped = df.groupby(groupby)
    df['weighted_average'] = df[values] / grouped[weights].transform('sum') * df[weights]
    return grouped['weighted_average'].sum(min_count=1) #min_count is required for Grouper objects

Ter vergelijking: het gebruik van een aangepaste lambda-functie is minder code, maar langzamer:

import numpy as np
def weighted_mean_by_lambda(df, values, weights, groupby):
    return df.groupby(groupby).apply(lambda x: np.average(x[values], weights=x[weights]))

Snelheidstest:

import time
import numpy as np
import pandas as pd
n = 100000000
df = pd.DataFrame({
    'values': np.random.uniform(0, 1, size=n), 
    'weights': np.random.randint(0, 5, size=n),
    'groupby': np.random.randint(0, 10000, size=n), 
})
time1 = time.time()
weighted_mean(df, 'values', 'weights', 'groupby')
print('Time for `weighted_mean`:', time.time() - time1)
time2 = time.time()
weighted_mean_by_lambda(df, 'values', 'weights', 'groupby')
print('Time for `weighted_mean_by_lambda`:', time.time() - time2)

Uitvoer snelheidstest:

Time for `weighted_mean`: 3.4519572257995605
Time for `weighted_mean_by_lambda`: 11.41335940361023

Antwoord 6

Een andere optie voor een oude vraag (met behulp van @kadeevoorbeeldgegevens):

(df.drop(columns='ID')
  .assign(wt = lambda df: df.prod(1)) # product of weight and value
  .groupby('Date').sum()
  .pipe(lambda df: df.wt / df.value) # weighted average computation
) 
Date
01/01/2012    0.791667
01/02/2012    0.722222

Zou veel sneller moeten zijn als de berekening wordt gedaan zonder het gebruik van anonieme functies:

temp = df.drop(columns='ID')
temp = temp.assign(wt = temp.prod(1)).groupby('Date').sum()
temp.wt / temp.value
Date
01/01/2012    0.791667
01/02/2012    0.722222
dtype: float64

Other episodes