python pandas numpy scikit-learn

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''])] .