De cómo hacer esto en pandas:

Tengo una función extract_text_features en una sola columna de texto, volviendo varias columnas de salida. Específicamente, la función devuelve 6 valores.

La función de las obras, sin embargo, no parece ser ningún tipo de retorno (pandas DataFrame/numpy matriz/Python lista) para que la salida puede obtener correctamente asignados df.ix[: ,10:16] = df.textcol.map(extract_text_features)

Así que creo que tengo que dejar atrás a recorrer con df.iterrows(), como por este?

ACTUALIZACIÓN:
Iteración con df.iterrows() es al menos 20 veces más lento, así que me rendí y me parta la función en seis diferentes .map(lambda ...) llamadas.

ACTUALIZACIÓN 2: esta pregunta se le pidió de nuevo alrededor de v0.11.0. Por lo tanto mucho de la pregunta y las respuestas no son demasiado relevantes.

  • Creo que no se pueden hacer múltiples asignación de la manera que usted ha escrito: df.ix[: ,10:16]. Creo que voy a tener que merge sus características en el conjunto de datos.
  • Para aquellos que quieran una forma mucho más eficiente solución de marque esta de abajo, que no utiliza apply
  • La mayoría de las operaciones numéricas con los pandas pueden ser vectorizadas – esto significa que son mucho más rápidos que los convencionales de la iteración. OTOH, algunas operaciones (como cadena de caracteres y expresiones regulares (regex) son inherentemente difícil de vectorización. Este este caso, es importante entender cómo bucle sobre sus datos. Más información sobre cuándo y cómo un bucle por los datos que se debe hacer, por favor, lea Para los bucles con los Pandas – Cuando debo hacer?.
  • el tema principal no era la elección, que fue el de mayor rendimiento entre varias opciones, fue la lucha de los pandas sintaxis para conseguir que esto funcione en todos, alrededor de la espalda v0.11.0.
  • De hecho, el comentario es la intención para el futuro de los lectores que estás buscando iterativo soluciones, que no conocen nada mejor, o que saben lo que están haciendo.
InformationsquelleAutor smci | 2013-04-26

12 Comentarios

  1. 87

    Edificio de user1827356 ‘s respuesta, usted puede hacer la tarea en un solo paso utilizando 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

    EDICIÓN:
    Por favor, ser conscientes de la enorme consumo de memoria y la velocidad baja: https://ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply/ !

    • sólo por curiosidad, se espera a utilizar una gran cantidad de memoria haciendo esto? Estoy haciendo esto en un dataframe que contiene 2.5 mil filas, y yo casi corrió hacia los problemas de la memoria (también es mucho más lento que el de regresar a solo 1 columna).
    • El método es bueno, pero los costes de memoria es demasiado.
    • ‘df.join(df.textcol.aplicar(lambda s: pd.Serie({‘feature1’:s+1, ‘feature2′:s-1})))’ sería una mejor opción, creo.
    • ¿por qué crees que tu sugerencia sería la mejor opción? Sería más eficiente que piensan o tienen menos costes de memoria?
    • Por favor, tener en cuenta la velocidad y la memoria necesaria: ys-l.github.io/posts/2015/08/28/cómo-no-para-usar-pandas-aplicar
  2. 148

    Me suelen hacer esto utilizando 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
    • Pero ¿qué hacer si usted tiene 50 columnas como esta en vez de 6?
    • lista(zip(*df[‘num’].mapa(poderes))); for i, c en enumerar(columnas): df[c] = temp[c]
    • Creo que quiso decir for i, c in enumerate(columns): df[c] = temp[i]. Gracias a esto, yo realmente tiene el propósito de enumerate 😀
    • Este es por lejos el más elegante y legible de la solución que he encontrado para esto. A menos que usted está recibiendo los problemas de rendimiento, el lenguaje zip(*df['col'].map(function)) es probablemente el camino a seguir.
    • mientras voy a añadir un comentario un poco tarde, si alguien viene a través y darse cuenta de mi error, te agradecería la visión. obviamente los dos eso es publicado aquí no funcionan, parece ser for i, c in enumerate(temp): df[c] = temp[c]
    • Consulte stackoverflow.com/questions/3394835/args-and-kwargs

  3. 69

    Esto es lo que he hecho 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 de la integridad de

    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
    • concat() se parece más sencilla que la de combinación() para la conexión de la nueva cols a la original dataframe.
  4. 51

    Este es el correcto y de la forma más sencilla de lograr esto para 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
    • no debería escribir: df = df.aplicar(ejemplo(df), eje=1) me corrija si me equivoco, solo soy un novato
    • No en este caso son el tratamiento de ejemplo como un objeto de primera clase por lo que está pasando en la propia función. Esta función se aplica a cada fila.
    • hola Michael, tu respuesta me ha ayudado en mi problema. Sin duda, su solución es mejor que la original de los pandas’ df.asignar() método, porque esto es una vez por columna. El uso de asignar(), si desea crear 2 nuevas columnas, usted tiene que utilizar df1 para trabajar en df para obtener nuevos columna1, a continuación, utilizar df2 para trabajar en df1 para crear la segunda columna nueva…esto es bastante monótono. Pero su método salvó mi vida!!! Gracias!!!
    • No se de que se ejecute la columna de asignación de código de una vez por fila? ¿No sería mejor regresar en un pd.Series({k:v}) y serializar la columna de asignación como en Ewan la respuesta?
  5. 16

    Resumen: Si sólo desea crear un par de columnas, el uso de 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 que va a crear debe ser igual al número de columnas que se utiliza como entrada para el .aplicar() función. Si quieres hacer algo más, echar un vistazo a las otras respuestas.

    Detalles
    Digamos que usted tiene dos columnas dataframe. La primera columna es la estatura de las personas cuando son de 10; el segundo se dice de la persona de altura cuando están a 20.

    Supongamos que usted necesita para calcular la media de cada persona de la altura y la suma de cada persona alturas. Que los dos valores por cada fila.

    Usted puede hacer esto a través de la siguiente, pronto-a-ser-función aplicada:

    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 así:

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

    (Para que quede claro: esto se aplica la función toma los valores de cada fila en el crea un subconjunto de dataframe y devuelve una lista).

    Sin embargo, si usted hace esto:

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

    vas a crear 1 nueva columna que contiene el [media,suma] de las listas, la que te presumiblemente queremos evitar, porque eso requeriría otro Lambda/Aplicar.

    Lugar, desea dividir cada valor en su propia columna. Para hacer esto, usted 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)
    • Para pandas de 0,23, tendrás que utilizar la sintaxis: df["mean"], df["sum"] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1)
  6. 11

    En 2018, yo uso apply() con el argumento de result_type='expand'

    >>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand')
    >>> df = pd.concat([df, appiled_df], axis='columns')
    • Que es la forma de hacerlo, hoy en día!
  7. 10

    He mirado varias maneras de hacerlo y el método que se muestra aquí (la devolución de un pandas de la serie) no parece ser más eficiente.

    Si empezamos con un largeish dataframe 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, (r.A, r.B, r.C))), 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 bucles, la mejor de las 3: 2.77 s por bucle

    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 bucles, la mejor de las 3: 8.85 ms por bucle

    Por mi juicio es mucho más eficiente para tomar una serie de tuplas y luego convertirlo a un DataFrame. Me interesaría escuchar el pensamiento de la gente, aunque si hay un error en mi trabajo.

  8. 9

    Para mí esto funcionó:

    De entrada df

    df = pd.DataFrame({'col x': [1,2,3]})
    col x
    0      1
    1      2
    2      3

    Función

    def f(x):
    return pd.Series([x*x, x*x*x])

    Crear 2 nuevas columnas:

    df[['square x', 'cube x']] = df['col x'].apply(f)

    De salida:

       col x  square x  cube x
    0      1         1       1
    1      2         4       8
    2      3         9      27
  9. 8

    La aceptación de la solución va a ser muy lento por la gran cantidad de datos. La solución con el mayor número de upvotes es un poco difícil de leer y también lento con datos numéricos. Si cada nueva columna puede ser calculado de forma independiente de las otras, se me acaba de asignar a cada uno de ellos directamente sin el uso de apply.

    Ejemplo con falsos datos de carácter

    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 hemos querido extraer algunas características del texto, como se hace en la pregunta original. Por ejemplo, vamos a extraer la primera letra, contar la aparición de la letra » e » y capitalizar 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, usted puede obtener un mejor rendimiento de bucle a través de 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 falsos datos numéricos

    Crear 1 millón de números al azar y la prueba de la powers función 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)

    La asignación de cada columna es 25 veces más rápido y fácil de leer:

    %%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 no suele ser el camino a seguir.

  10. 6

    Han publicado la misma respuesta en dos otras preguntas similares. La manera en que yo prefiero hacer esto es para envolver los valores de retorno de la función en una serie:

    def f(x):
    return pd.Series([x**2, x**3])

    Y, a continuación, utilizar aplican de la siguiente manera para crear columnas separadas:

    df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
  11. 4

    Solo uso result_type="expand"

    df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random", "a"])
    df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand")
    • Es útil señalar que la opción es nuevo en 0.23. La pregunta que se le pidió de nuevo sobre 0.11
  12. 1

    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
    • No, yo No quiero aplicar extract_text_features a cada columna de la df, sólo a la columna de texto df.textcol

Dejar respuesta

Please enter your comment!
Please enter your name here