Cómo aplicar una función a dos columnas del dataframe de Pandas

Supongamos que tengo un df que tiene columnas de 'ID', 'col_1', 'col_2' . Y defino una función:

f = lambda x, y : my_function_expression .

Ahora quiero aplicar las columnas 'col_1', 'col_2' f to df para calcular en forma 'col_3' una nueva columna 'col_3' , algo así como:

 df['col_3'] = df[['col_1','col_2']].apply(f) # Pandas gives : TypeError: ('() takes exactly 2 arguments (1 given)' 

Cómo hacer ?

** Añadir muestra de detalle como abajo ***

 import pandas as pd df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]}) mylist = ['a','b','c','d','e','f'] def get_sublist(sta,end): return mylist[sta:end+1] #df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1) # expect above to output df as below ID col_1 col_2 col_3 0 1 0 1 ['a', 'b'] 1 2 2 4 ['c', 'd', 'e'] 2 3 3 5 ['d', 'e', 'f'] 

Aquí hay un ejemplo de uso de apply en el dataframe, al que estoy llamando con axis = 1 .

Tenga en cuenta que la diferencia es que, en lugar de intentar pasar dos valores a la función f , reescriba la función para aceptar un objeto Serie de pandas y luego indexe la Serie para obtener los valores necesarios.

 In [49]: df Out[49]: 0 1 0 1.000000 0.000000 1 -0.494375 0.570994 2 1.000000 0.000000 3 1.876360 -0.229738 4 1.000000 0.000000 In [50]: def f(x): ....: return x[0] + x[1] ....: In [51]: df.apply(f, axis=1) #passes a Series object, row-wise Out[51]: 0 1.000000 1 0.076619 2 1.000000 3 1.646622 4 1.000000 

Dependiendo de su caso de uso, a veces es útil crear un objeto de group pandas y luego usar apply en el grupo.

Una solución simple es:

 df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1) 

Una pregunta interesante! mi respuesta de la siguiente manera:

 import pandas as pd def sublst(row): return lst[row['J1']:row['J2']] df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]}) print df lst = ['a','b','c','d','e','f'] df['J3'] = df.apply(sublst,axis=1) print df 

Salida:

  ID J1 J2 0 1 0 1 1 2 2 4 2 3 3 5 ID J1 J2 J3 0 1 0 1 [a] 1 2 2 4 [c, d] 2 3 3 5 [d, e] 

Cambié el nombre de la columna a ID, J1, J2, J3 para garantizar ID

Una versión más breve:

 import pandas as pd df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]}) print df lst = ['a','b','c','d','e','f'] df['J3'] = df.apply(lambda row:lst[row['J1']:row['J2']],axis=1) print df 

Hay una forma limpia y de una sola línea de hacer esto en Pandas:

 df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1) 

Esto permite que f sea ​​una función definida por el usuario con múltiples valores de entrada y utiliza nombres de columna (seguros) en lugar de índices numéricos (inseguros) para acceder a las columnas.

Ejemplo con datos (basado en la pregunta original):

 import pandas as pd df = pd.DataFrame({'ID':['1', '2', '3'], 'col_1': [0, 2, 3], 'col_2':[1, 4, 5]}) mylist = ['a', 'b', 'c', 'd', 'e', 'f'] def get_sublist(sta,end): return mylist[sta:end+1] df['col_3'] = df.apply(lambda x: get_sublist(x.col_1, x.col_2), axis=1) 

Salida de print(df) :

  ID col_1 col_2 col_3 0 1 0 1 [a, b] 1 2 2 4 [c, d, e] 2 3 3 5 [d, e, f] 

El método que está buscando es Series.combine. Sin embargo, parece que hay que tener cuidado con los tipos de datos. En su ejemplo, usted (como lo hice cuando probé la respuesta) llamaría ingenuamente

 df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist) 

Sin embargo, esto arroja el error:

 ValueError: setting an array element with a sequence. 

Mi mejor conjetura es que parece esperar que el resultado sea del mismo tipo que la serie que llama al método (df.col_1 aquí). Sin embargo, los siguientes trabajos:

 df['col_3'] = df.col_1.astype(object).combine(df.col_2, func=get_sublist) df ID col_1 col_2 col_3 0 1 0 1 [a, b] 1 2 2 4 [c, d, e] 2 3 3 5 [d, e, f] 

La forma en que has escrito f necesita dos entradas. Si observa el mensaje de error, indica que no está proporcionando dos entradas a f, solo una. El mensaje de error es correcto.
La discrepancia se debe a que df [[‘col1’, ‘col2’]] devuelve un único dataframe con dos columnas, no dos columnas separadas.

Debe cambiar su f para que tome una sola entrada, mantenga el cuadro de datos anterior como entrada, luego divídalo en x, y dentro del cuerpo de la función. Luego haga lo que necesite y devuelva un solo valor.

Necesita la firma de esta función porque la syntax es .apply (f) Por lo tanto, f necesita tomar lo único = dataframe y no dos cosas, lo que su f actual espera.

Ya que no ha proporcionado el cuerpo de F, no puedo ayudarlo con más detalles, pero esto debería proporcionar la salida sin cambiar fundamentalmente su código o usar otros métodos en lugar de aplicar

Voy a poner en un voto para np.vectorize. Le permite simplemente disparar sobre x número de columnas y no manejar el dataframe en la función, por lo que es ideal para las funciones que no controla o hacer algo como enviar 2 columnas y una constante a una función (es decir, col_1, col_2, ‘foo’).

 import numpy as np import pandas as pd df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]}) mylist = ['a','b','c','d','e','f'] def get_sublist(sta,end): return mylist[sta:end+1] #df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1) # expect above to output df as below df.loc[:,'col_3'] = np.vectorize(get_sublist, otypes=["O"]) (df['col_1'], df['col_2']) df ID col_1 col_2 col_3 0 1 0 1 [a, b] 1 2 2 4 [c, d, e] 2 3 3 5 [d, e, f] 

Devolver una lista de apply es una operación peligrosa, ya que no se garantiza que el objeto resultante sea una serie o un dataframe. Y las excepciones pueden ser planteadas en ciertos casos. Veamos un ejemplo simple:

 df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)), columns=['a', 'b', 'c']) df abc 0 4 0 0 1 2 0 1 2 2 2 2 3 1 2 2 4 3 0 0 

Hay tres resultados posibles con devolver una lista de apply

1) Si la longitud de la lista devuelta no es igual al número de columnas, se devuelve una serie de listas.

 df.apply(lambda x: list(range(2)), axis=1) # returns a Series 0 [0, 1] 1 [0, 1] 2 [0, 1] 3 [0, 1] 4 [0, 1] dtype: object 

2) Cuando la longitud de la lista devuelta es igual al número de columnas, se devuelve un DataFrame y cada columna obtiene el valor correspondiente en la lista.

 df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame abc 0 0 1 2 1 0 1 2 2 0 1 2 3 0 1 2 4 0 1 2 

3) Si la longitud de la lista devuelta es igual al número de columnas de la primera fila pero tiene al menos una fila donde la lista tiene un número de elementos diferente al número de columnas, se genera un ValueError.

 i = 0 def f(x): global i if i == 0: i += 1 return list(range(3)) return list(range(4)) df.apply(f, axis=1) ValueError: Shape of passed values is (5, 4), indices imply (5, 3) 

Respondiendo al problema sin aplicar.

Usar apply con eje = 1 es muy lento. Es posible obtener un rendimiento mucho mejor (especialmente en conjuntos de datos más grandes) con métodos iterativos básicos.

Crear un dataframe más grande

 df1 = df.sample(100000, replace=True).reset_index(drop=True) 

Tiempos

 # apply is slow with axis=1 %timeit df1.apply(lambda x: mylist[x['col_1']: x['col_2']+1], axis=1) 2.59 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) # zip - similar to @Thomas %timeit [mylist[v1:v2+1] for v1, v2 in zip(df1.col_1, df1.col_2)] 29.5 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 

@Thomas responde

 %timeit list(map(get_sublist, df1['col_1'],df1['col_2'])) 34 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 

Estoy seguro de que esto no es tan rápido como las soluciones que utilizan operaciones de Pandas o Numpy, pero si no desea volver a escribir su función, puede usar el mapa. Usando los datos de ejemplo originales –

 import pandas as pd df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]}) mylist = ['a','b','c','d','e','f'] def get_sublist(sta,end): return mylist[sta:end+1] df['col_3'] = list(map(get_sublist,df['col_1'],df['col_2'])) #In Python 2 don't convert above to list 

Podríamos pasar tantos argumentos como queramos a la función de esta manera. La salida es lo que queríamos.

 ID col_1 col_2 col_3 0 1 0 1 [a, b] 1 2 2 4 [c, d, e] 2 3 3 5 [d, e, f] 

Mi ejemplo a sus preguntas:

 def get_sublist(row, col1, col2): return mylist[row[col1]:row[col2]+1] df.apply(get_sublist, axis=1, col1='col_1', col2='col_2') 

Supongo que no desea cambiar la función get_sublist , y solo quiere usar el método de apply de DataFrame para hacer el trabajo. Para obtener el resultado que desea, escribí dos funciones de ayuda: get_sublist_list y unlist . Como sugiere el nombre de la función, primero obtenga la lista de listas secundarias, luego extraiga la lista secundaria de esa lista. Finalmente, necesitamos llamar a la función de aplicación para aplicar esas dos funciones a la df[['col_1','col_2']] posteriormente.

 import pandas as pd df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]}) mylist = ['a','b','c','d','e','f'] def get_sublist(sta,end): return mylist[sta:end+1] def get_sublist_list(cols): return [get_sublist(cols[0],cols[1])] def unlist(list_of_lists): return list_of_lists[0] df['col_3'] = df[['col_1','col_2']].apply(get_sublist_list,axis=1).apply(unlist) df 

Si no usa [] para encerrar la función get_sublist , entonces la función get_sublist_list devolverá una lista simple, ValueError: could not broadcast input array from shape (3) into shape (2) , como @Ted Petrou habia mencionado