Расчет по формуле значений таблицы

Есть две таблицы с разными людьми и оценкой их средней точности.

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 шт):

Автор решения: strawdog

Я не уверен, что такую задачу можно полностью решить векторным путем, но можно попытаться сделать так еще:

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
→ Ссылка
Автор решения: MaxU

Векторизированное решение:

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
→ Ссылка