Расчет по формуле значений таблицы
Есть две таблицы с разными людьми и оценкой их средней точности.
df1 = pd.DataFrame({'человек': [1,2,3], 'точность': [0.5,0.3,0.4], 'попыток':[9,8,7]})
df2 = pd.DataFrame({'человек': [1,6,7], 'точность': [0.3,0.4,0.1], 'попыток':[7,6,4]})
Нужно рассчитать общую точность по одинаковым людям формула такая (не суть): точность (формула среднего взвешенного) = (попыток в первой таблице * точность в первой таблице + попыток во второй таблице * точность во второй таблице) / (попыток в первой таблице + попыток во второй таблице)
Т.е., в таблицах один человек общий (1) формула (0.5 * 9 + 0.3 * 7) / (9 + 7) = 0.4125, ну и изменить столбец попыток на (9 + 7).
На выходе должна получится такая таблица:
df3 = pd.DataFrame({'человек': [1,2,3,6,7], 'точность': [0.4125,0.3,0.4,0.4,0.1], 'попыток':[16,6,4,3,4]})
df3
Решил очень тормозным, неоптимальным способом. Объединял через append, потом группировал по человеку, потом через apply вызывал свою функцию, в ней через словари делал расчет, возвращал Series, потом делал DataFrame, присоединял столбец с попытками.
df1 = pd.DataFrame({'человек': [1,2,3], 'точность': [0.5,0.3,0.4], 'попыток':[9,8,7]})
df2 = pd.DataFrame({'человек': [1,6,7], 'точность': [0.3,0.4,0.1], 'попыток':[7,6,4]})
counting1 = []
def upd_mean1(x):
x=x.to_dict()
if len((list(x['попыток'].values()))) == 2:
n1 = list(x['попыток'].values())[0]
n2 = list(x['попыток'].values())[1]
mean1 = list(x['точность'].values())[0]
mean2 = list(x['точность'].values())[1]
mean = (n1 * mean1 + n2 * mean2) / (n1 + n2)
count = n1 + n2
else:
mean = list(x['точность'].values())[0]
count = list(x['попыток'].values())[0]
counting1.append(count)
return mean
temp = df1.append(df2).reset_index()
df3 = temp.groupby("человек").apply(upd_mean1).to_frame().assign(a=counting1)
df3.columns = ['точность', 'попыток']
df3.reset_index()
Подскажите, как это лучше реализовать?
PS1: Еще решил обычным ручным питоном, через словари. Достаточно быстрый способ.
d1 = df1.to_dict()
d2 = df2.to_dict()
d3 = {'человек': {},'точность': {},'попыток': {}}
inx = 0
men = set()
for i in d1['человек']:
for ii in d2['человек']:
if d1['человек'][i] == d2['человек'][ii]:
mean = (d1['точность'][i] * d1['попыток'][i] + d2['точность'][ii] * d2['попыток'][ii]) / (d1['попыток'][i] + d2['попыток'][ii])
d3['человек'][inx] = d1['человек'][i]
d3['точность'][inx] = mean
d3['попыток'][inx] = d1['попыток'][i] + d2['попыток'][ii]
one = d1['человек'][i]
inx += 1
else:
men.update([d1['человек'][i], d2['человек'][ii]])
for i in range(len(df1)):
if d1['человек'][i] != one:
d3['человек'][i] = d1['человек'][i]
d3['точность'][i] = d1['точность'][i]
d3['попыток'][i] = d1['попыток'][i]
for i in range(len(df2)):
if d2['человек'][i] != one:
d3['человек'][i+ len(df1)] = d2['человек'][i]
d3['точность'][i + len(df1)] = d2['точность'][i]
d3['попыток'][i + len(df1)] = d2['попыток'][i]
pd.DataFrame(d3, columns=d3.keys()).reset_index(drop=True)
PS2: Векторизированный вариант от MaxU самый быстрый. На реальных данных - 29 ms.
Далее идет, как ни странно голый питон (словари) - 98 ms.
Потом с большим отрывом мой первый вариант - 1.4 s
И вариант от strawdog - 18 s.
Ответы (2 шт):
Я не уверен, что такую задачу можно полностью решить векторным путем, но можно попытаться сделать так еще:
res = pd.DataFrame()
for i, g in pd.concat([df1, df2]).groupby('человек')[["точность", "попыток"]]:
g_=g.copy()
g_["точность"] = (g_["точность"]*g_["попыток"]).sum()/g_["попыток"].sum()
g_["попыток"] = g_["попыток"].sum()
res = pd.concat([res, g_.drop_duplicates()])
res, соответственно, будет:
человек точность попыток
0 1 0.4125 16
1 2 0.3000 8
2 3 0.4000 7
1 6 0.4000 6
2 7 0.1000 4
Векторизированное решение:
def f(df):
return df.eval("x = точность * попыток").set_index("человек")
res = (f(df1)
.add(f(df2), fill_value=0)
.eval("точность = x / попыток")
.drop(columns="x")
.reset_index())
результат:
In [58]: res
Out[58]:
человек точность попыток
0 1 0.4125 16.0
1 2 0.3000 8.0
2 3 0.4000 7.0
3 6 0.4000 6.0
4 7 0.1000 4.0
