¿Aplica la función pandas a la columna para crear múltiples columnas nuevas?

Cómo hacer esto en pandas:

Tengo una función extract_text_features en una sola columna de texto, devolviendo múltiples columnas de salida. Específicamente, la función devuelve 6 valores.

La función funciona, sin embargo, no parece haber ningún tipo de retorno adecuado (pandas DataFrame / numpy array / Python list) para que la salida se pueda asignar correctamente df.ix[: ,10:16] = df.textcol.map(extract_text_features)

¿Entonces creo que necesito volver a iterar con df.iterrows() , de acuerdo con esto ?

ACTUALIZACIÓN: la df.iterrows() con df.iterrows() es al menos 20 veces más lenta, por lo que me df.iterrows() y df.iterrows() la función en seis llamadas .map(lambda ...) distintas.

A partir de la respuesta del usuario1827356, puede hacer la asignación en una sola pasada usando df.merge :

 df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})), left_index=True, right_index=True) textcol feature1 feature2 0 0.772692 1.772692 -0.227308 1 0.857210 1.857210 -0.142790 2 0.065639 1.065639 -0.934361 3 0.819160 1.819160 -0.180840 4 0.088212 1.088212 -0.911788 

Normalmente hago esto usando zip :

 >>> df = pd.DataFrame([[i] for i in range(10)], columns=['num']) >>> df num 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 >>> def powers(x): >>> return x, x**2, x**3, x**4, x**5, x**6 >>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \ >>> zip(*df['num'].map(powers)) >>> df num p1 p2 p3 p4 p5 p6 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 4 8 16 32 64 3 3 3 9 27 81 243 729 4 4 4 16 64 256 1024 4096 5 5 5 25 125 625 3125 15625 6 6 6 36 216 1296 7776 46656 7 7 7 49 343 2401 16807 117649 8 8 8 64 512 4096 32768 262144 9 9 9 81 729 6561 59049 531441 

Esto es lo que hice en el pasado

 df = pd.DataFrame({'textcol' : np.random.rand(5)}) df textcol 0 0.626524 1 0.119967 2 0.803650 3 0.100880 4 0.017859 df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})) feature1 feature2 0 1.626524 -0.373476 1 1.119967 -0.880033 2 1.803650 -0.196350 3 1.100880 -0.899120 4 1.017859 -0.982141 

Edición para completar

 pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1) textcol feature1 feature2 0 0.626524 1.626524 -0.373476 1 0.119967 1.119967 -0.880033 2 0.803650 1.803650 -0.196350 3 0.100880 1.100880 -0.899120 4 0.017859 1.017859 -0.982141 

Esta es la forma correcta y más fácil de lograr esto en el 95% de los casos de uso:

 >>> df = pd.DataFrame(zip(*[range(10)]), columns=['num']) >>> df num 0 0 1 1 2 2 3 3 4 4 5 5 >>> def example(x): ... x['p1'] = x['num']**2 ... x['p2'] = x['num']**3 ... x['p3'] = x['num']**4 ... return x >>> df = df.apply(example, axis=1) >>> df num p1 p2 p3 0 0 0 0 0 1 1 1 1 1 2 2 4 8 16 3 3 9 27 81 4 4 16 64 256 

Resumen: si solo quiere crear algunas columnas, use df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

Para esta solución, el número de columnas nuevas que está creando debe ser igual al número de columnas que utiliza como entrada para la función .apply (). Si quieres hacer otra cosa, echa un vistazo a las otras respuestas.

Detalles Digamos que tiene dataframe de dos columnas. La primera columna es la altura de una persona cuando tiene 10; el segundo es la altura de dicha persona cuando tienen 20 años.

Supongamos que necesita calcular tanto la media de las alturas de cada persona como la sum de las alturas de cada persona. Eso es dos valores por cada fila.

Puede hacerlo a través de la siguiente función que se aplicará próximamente:

 def mean_and_sum(x): """ Calculates the mean and sum of two heights. Parameters: :x -- the values in the row this function is applied to. Could also work on a list or a tuple. """ sum=x[0]+x[1] mean=sum/2 return [mean,sum] 

Puede utilizar esta función de esta manera:

  df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) 

(Para ser claros: esta función de aplicación toma los valores de cada fila en el dataframe subconjunto y devuelve una lista).

Sin embargo, si haces esto:

 df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) 

creará una nueva columna que contenga las listas [media, sum], que presumiblemente querría evitar, porque eso requeriría otro Lambda / Apply.

En cambio, desea dividir cada valor en su propia columna. Para hacer esto, puede crear dos columnas a la vez:

 df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1) 

He buscado varias formas de hacerlo y el método que se muestra aquí (devolver una serie de pandas) no parece ser más eficiente.

Si comenzamos con una gran cantidad de datos de datos aleatorios:

 # Setup a dataframe of random numbers and create a df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC')) df['D'] = df.apply(lambda r: ':'.join(map(str, (rA, rB, rC))), axis=1) columns = 'new_a', 'new_b', 'new_c' 

El ejemplo que se muestra aquí:

 # Create the dataframe by returning a series def method_b(v): return pd.Series({k: v for k, v in zip(columns, v.split(':'))}) %timeit -n10 -r3 df.D.apply(method_b) 

10 loops, lo mejor de 3: 2.77 s por ciclo

Un método alternativo:

 # Create a dataframe from a series of tuples def method_a(v): return v.split(':') %timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns) 

10 loops, lo mejor de 3: 8,85 ms por ciclo

Según mis cálculos, es mucho más eficiente tomar una serie de tuplas y luego convertirlas en un DataFrame. Sin embargo, estaría interesado en escuchar el pensamiento de la gente si hay un error en mi trabajo.

La solución aceptada será extremadamente lenta para muchos datos. La solución con el mayor número de upvotes es un poco difícil de leer y también lenta con datos numéricos. Si cada nueva columna puede calcularse independientemente de las demás, simplemente asignaría cada una de ellas directamente sin utilizar apply .

Ejemplo con datos de caracteres falsos

Crear 100.000 cadenas en un DataFrame

 df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'], size=100000, replace=True), columns=['words']) df.head() words 0 she ran 1 she ran 2 they hiked 3 they hiked 4 they hiked 

Digamos que queríamos extraer algunas características del texto como se hizo en la pregunta original. Por ejemplo, vamos a extraer el primer carácter, contar la aparición de la letra “e” y ponerle mayúscula a la frase.

 df['first'] = df['words'].str[0] df['count_e'] = df['words'].str.count('e') df['cap'] = df['words'].str.capitalize() df.head() words first count_e cap 0 she ran s 1 She ran 1 she ran s 1 She ran 2 they hiked t 2 They hiked 3 they hiked t 2 They hiked 4 they hiked t 2 They hiked 

Tiempos

 %%timeit df['first'] = df['words'].str[0] df['count_e'] = df['words'].str.count('e') df['cap'] = df['words'].str.capitalize() 127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) def extract_text_features(x): return x[0], x.count('e'), x.capitalize() %timeit df['first'], df['count_e'], df['cap'] = zip(*df['words'].apply(extract_text_features)) 101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

Sorprendentemente, puede obtener un mejor rendimiento al recorrer cada valor

 %%timeit a,b,c = [], [], [] for s in df['words']: a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize()) df['first'] = a df['count_e'] = b df['cap'] = c 79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 

Otro ejemplo con datos numéricos falsos

Crea 1 millón de números aleatorios y prueba la función de powers desde arriba.

 df = pd.DataFrame(np.random.rand(1000000), columns=['num']) def powers(x): return x, x**2, x**3, x**4, x**5, x**6 %%timeit df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \ zip(*df['num'].map(powers)) 1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

Asignar cada columna es 25 veces más rápido y muy legible:

 %%timeit df['p1'] = df['num'] ** 1 df['p2'] = df['num'] ** 2 df['p3'] = df['num'] ** 3 df['p4'] = df['num'] ** 4 df['p5'] = df['num'] ** 5 df['p6'] = df['num'] ** 6 51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

Hice una respuesta similar con más detalles aquí sobre por qué apply normalmente no es el camino a seguir.

puede devolver toda la fila en lugar de valores:

 df = df.apply(extract_text_features,axis = 1) 

donde la función devuelve la fila

 def extract_text_features(row): row['new_col1'] = value1 row['new_col2'] = value2 return row 
Intereting Posts