renombrar - libreria pandas python
Cómo aplicar una función a dos columnas del marco de datos de Pandas (11)
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: (''<lambda>() 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 marco de datos, 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.
Devolver una lista de apply
es una operación peligrosa, ya que no se garantiza que el objeto resultante sea una serie o un marco de datos. 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
a b c
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
a b c
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 para la primera fila pero tiene al menos una fila donde la lista tiene un número diferente de elementos que el 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 marco de datos 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)
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]
Estoy seguro de que esto no es tan rápido como las soluciones que utilizan las 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]
Hay una forma más limpia de hacer esto:
df[''col_3''] = df.apply(lambda x: f(x.col_1, x.col2), axis=1)
Esto permite que f
sea una función con múltiples valores de entrada y utiliza nombres de columna en lugar de índices numéricos para acceder a las columnas, lo que es más seguro.
Ejemplo completo con datos:
import pandas as pd
import numpy as np
def f(x, y):
return x ** 2 + y
np.random.seed(0)
df = pd.DataFrame({''col1'': np.random.randn(5), ''col2'': np.random.randn(5)})
df[''col3''] = df.apply(lambda x: f(x.col1, x.col2), axis=1)
Salida:
col1 col2 col3
0 1.764052 -0.977278 2.134603
1 0.400157 0.950088 1.110214
2 0.978738 -0.151357 0.806571
3 2.240893 -0.103219 4.918383
4 1.867558 0.410599 3.898371
Por supuesto, en este caso, f
es una función simple, por lo que podría escribir df[''col3''] = df[''col1''] ** 2 + df[''col2'']
, pero el enfoque de apply
es útil cuando f
es demasiado complicado Para escribir como una operación de pandas.
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 marco de datos con dos columnas, no dos columnas separadas.
Debe cambiar su f para que tome una sola entrada, mantenga el marco de datos anterior como entrada, luego divídalo en x, y dentro del cuerpo de la función. Luego haz lo que necesites y devuelve un solo valor.
Necesita la firma de esta función porque la sintaxis es .apply (f) Por lo tanto, f necesita tomar lo único = marco de datos 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
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 quieres cambiar la función get_sublist
, y solo quieres 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 df[[''col_1'',''col_2'']]
para aplicar esas dos funciones al df[[''col_1'',''col_2'']]
DataFrame 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
Una pregunta interesante! mi respuesta como abajo:
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 <J1 <J2 <J3, para que la columna se muestre en la secuencia correcta.
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
Una solución simple es:
df[''col_3''] = df[[''col_1'',''col_2'']].apply(lambda x: f(*x), axis=1)
Voy a poner en un voto para np.vectorize. Le permite simplemente disparar sobre x número de columnas y no manejar el marco de datos 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]