python - Columnas pandas específicas como argumentos en la nueva columna de resultados de df.apply
numpy scikit-learn (2)
El enfoque 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
Dado un marco de datos de pandas como a continuación:
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'']
Quiero crear una nueva columna de valores RMSE (de scikit-learn ) usando columnas específicas para los argumentos. A saber, las columnas y_true = df[''a'',''b'',''c'']
vs y_pred = df[''x'',''y'',''x'']
. Esto fue fácil de hacer usando un enfoque iterativo:
for tup in df.itertuples():
df.at[tup[0], ''rmse''] = mean_squared_error(tup[1:4], tup[4:7])**0.5
Y eso da el resultado deseado:
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
Pero quiero una solución de mayor rendimiento, posiblemente usando vectorización, ya que mi dataframe tiene forma (180000000, 52). Tampoco me gusta indexar por posición de tupla en lugar de por nombre de columna. El intento a continuación:
df[''rmse''] = df.apply(mean_squared_error(df[[''a'',''b'',''c'']], df[[''d'',''e'',''y'']])**0.5, axis=1)
Obtiene el error:
TypeError: ("''numpy.float64'' object is not callable", ''occurred at index a'')
Entonces, ¿qué estoy haciendo mal con mi uso de df.apply()
? ¿Esto incluso maximiza el rendimiento sobre la iteración?
Prueba de rendimiento
Probé los tiempos de pared para cada uno de los primeros dos encuestados usando el df de prueba siguiente:
# 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)]
Los resultados son los siguientes:
%%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
La solución que usa la función aplicar aún se está ejecutando después de varios minutos ...
Enfoque # 1
Un enfoque para el rendimiento sería utilizar los datos de matriz subyacentes junto con NumPy ufuncs, junto con cortar esos dos bloques de columnas para usar esos ufuncs de manera vectorializada, como ese:
a = df.values
rmse_out = np.sqrt(((a[:,0:3] - a[:,3:6])**2).mean(1))
df[''rmse_out''] = rmse_out
Enfoque # 2
Una manera alternativa más rápida de calcular los valores np.einsum
con np.einsum
para reemplazar la squared-summation
al squared-summation
-
diffs = a[:,0:3] - a[:,3:6]
rmse_out = np.sqrt(np.einsum(''ij,ij->i'',diffs,diffs)/3.0)
Enfoque n. ° 3
Otra forma de calcular rmse_out
usando la fórmula:
(a - b) ^ 2 = a ^ 2 + b ^ 2 - 2ab
sería extraer las rebanadas:
s0 = a[:,0:3]
s1 = a[:,3:6]
Entonces, rmse_out
sería -
np.sqrt(((s0**2).sum(1) + (s1**2).sum(1) - (2*s0*s1).sum(1))/3.0)
que con einsum
convierte en
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)
Obtener los índices de columna respectivos
Si no está seguro de si las columnas a,b,..
estarían en ese orden o no, podríamos encontrar esos índices con column_index
.
Así, a[:,0:3]
sería reemplazado por a[:,column_index(df, [''a'',''b'',''c''])]
y a[:,3:6]
por a[:,column_index(df, [''d'',''e'',''y''])]
.