Конкретные столбцы панд в качестве аргументов в новом столбце выходных данных df.apply
Учитывая панды DataFrame, как показано ниже:
import pandas as pd
from sklearn.metrics import mean_squared_error
df = pd.DataFrame.from_dict(
{'row': ['a','b','c','d','e','y'],
'a': [ 0, -.8,-.6,-.3, .8, .01],
'b': [-.8, 0, .5, .7,-.9, .01],
'c': [-.6, .5, 0, .3, .1, .01],
'd': [-.3, .7, .3, 0, .2, .01],
'e': [ .8,-.9, .1, .2, 0, .01],
'y': [ .01, .01, .01, .01, .01, 0],
}).set_index('row')
df.columns.names = ['col']
Я хочу создать новый столбец значений RMSE (из scikit-learn), используя конкретные столбцы для аргументов. А именно, столбцы y_true = df['a','b','c']
против y_pred = df['x','y','x']
, Это было легко сделать с помощью итеративного подхода:
for tup in df.itertuples():
df.at[tup[0], 'rmse'] = mean_squared_error(tup[1:4], tup[4:7])**0.5
И это дает желаемый результат:
col a b c d e y rmse
row
a 0.00 -0.80 -0.60 -0.30 0.80 0.01 1.003677
b -0.80 0.00 0.50 0.70 -0.90 0.01 1.048825
c -0.60 0.50 0.00 0.30 0.10 0.01 0.568653
d -0.30 0.70 0.30 0.00 0.20 0.01 0.375988
e 0.80 -0.90 0.10 0.20 0.00 0.01 0.626658
y 0.01 0.01 0.01 0.01 0.01 0.00 0.005774
Но мне нужно более высокопроизводительное решение, возможно, с использованием векторизации, поскольку мой фрейм данных имеет форму (180000000, 52). Мне также не нравится индексирование по позиции кортежа, а не по имени столбца. Попытка ниже:
df['rmse'] = df.apply(mean_squared_error(df[['a','b','c']], df[['d','e','y']])**0.5, axis=1)
Получает ошибку:
TypeError: ("'numpy.float64' object is not callable", 'occurred at index a')
Так что же я делаю не так с моим использованием df.apply()
? Это даже максимизирует производительность по итерации?
Тестирование производительности
Я проверил настенное время для каждого из первых двух респондентов, используя приведенный ниже тест df:
# set up test df
dim_x, dim_y = 50, 1000000
cols = ["a_"+str(i) for i in range(1,(dim_x//2)+1)]
cols_b = ["b_"+str(i) for i in range(1,(dim_x//2)+1)]
cols.extend(cols_b)
shuffle(cols)
df = pd.DataFrame(np.random.uniform(0,10,[dim_y, dim_x]), columns=cols) #, index=idx, columns=cols
a = df.values
# define column samples
def column_index(df, query_cols):
cols = df.columns.values
sidx = np.argsort(cols)
return sidx[np.searchsorted(cols,query_cols,sorter=sidx)]
c0 = [s for s in cols if "a" in s]
c1 = [s for s in cols if "b" in s]
s0 = a[:,column_index(df, c0)]
s1 = a[:,column_index(df, c1)]
Результаты приведены ниже:
%%time
# approach 1 - divakar
rmse_out = np.sqrt(((s0 - s1)**2).mean(1))
df['rmse_out'] = rmse_out
Wall time: 393 ms
%%time
# approach 2 - divakar
diffs = s0 - s1
rmse_out = np.sqrt(np.einsum('ij,ij->i',diffs,diffs)/3.0)
df['rmse_out'] = rmse_out
Wall time: 228 ms
%%time
# approach 3 - divakar
diffs = s0 - s1
rmse_out = np.sqrt((np.einsum('ij,ij->i',s0,s0) + \
np.einsum('ij,ij->i',s1,s1) - \
2*np.einsum('ij,ij->i',s0,s1))/3.0)
df['rmse_out'] = rmse_out
Wall time: 421 ms
Решение с использованием функции apply все еще работает через несколько минут...
2 ответа
Подход № 1
Один из подходов к производительности заключается в том, чтобы использовать базовые данные массива вместе с ufuncs NumPy, а также разрезать эти два блока столбцов, чтобы использовать эти ufuncs в векторизованном виде, например так:
a = df.values
rmse_out = np.sqrt(((a[:,0:3] - a[:,3:6])**2).mean(1))
df['rmse_out'] = rmse_out
Подход № 2
Альтернативный более быстрый способ вычисления значений RMSE с np.einsum
заменить squared-summation
-
diffs = a[:,0:3] - a[:,3:6]
rmse_out = np.sqrt(np.einsum('ij,ij->i',diffs,diffs)/3.0)
Подход № 3
Другой способ вычислить rmse_out
используя формулу:
(a - b) ^ 2 = a ^ 2 + b ^ 2 - 2ab
будет извлекать ломтики:
s0 = a[:,0:3]
s1 = a[:,3:6]
Затем, rmse_out
было бы -
np.sqrt(((s0**2).sum(1) + (s1**2).sum(1) - (2*s0*s1).sum(1))/3.0)
который с einsum
становится -
np.sqrt((np.einsum('ij,ij->i',s0,s0) + \
np.einsum('ij,ij->i',s1,s1) - \
2*np.einsum('ij,ij->i',s0,s1))/3.0)
Получение соответствующих индексов столбцов
Если вы не уверены, что столбцы a,b,..
будет в таком порядке или нет, мы могли бы найти эти индексы с column_index
,
таким образом a[:,0:3]
будет заменен a[:,column_index(df, ['a','b','c'])]
а также a[:,3:6]
от a[:,column_index(df, ['d','e','y'])]
,
Подход df.apply:
df['rmse'] = df.apply(lambda x: mean_squared_error(x[['a','b','c']], x[['d','e','y']])**0.5, axis=1)
col a b c d e y rmse
row
a 0.00 -0.80 -0.60 -0.30 0.80 0.01 1.003677
b -0.80 0.00 0.50 0.70 -0.90 0.01 1.048825
c -0.60 0.50 0.00 0.30 0.10 0.01 0.568653
d -0.30 0.70 0.30 0.00 0.20 0.01 0.375988
e 0.80 -0.90 0.10 0.20 0.00 0.01 0.626658
y 0.01 0.01 0.01 0.01 0.01 0.00 0.005774