Concatenación de cadenas de dos columnas pandas

84

Tengo un siguiente DataFrame:

from pandas import *
df = DataFrame({'foo':['a','b','c'], 'bar':[1, 2, 3]})

Se parece a esto:

    bar foo
0    1   a
1    2   b
2    3   c

Ahora quiero tener algo como:

     bar
0    1 is a
1    2 is b
2    3 is c

¿Cómo puedo conseguir esto? Intenté lo siguiente:

df['foo'] = '%s is %s' % (df['bar'], df['foo'])

pero me da un resultado incorrecto:

>>>print df.ix[0]

bar                                                    a
foo    0    a
1    b
2    c
Name: bar is 0    1
1    2
2
Name: 0

Perdón por una pregunta tonta, pero esta pandas: combinar dos columnas en un DataFrame no fue útil para mí.

nat
fuente

Respuestas:

127

df['bar'] = df.bar.map(str) + " is " + df.foo.

BrenBarn
fuente
65

Esta pregunta ya ha sido respondida, pero creo que sería bueno incluir en la mezcla algunos métodos útiles que no se discutieron anteriormente y comparar todos los métodos propuestos hasta ahora en términos de rendimiento.

A continuación, se muestran algunas soluciones útiles a este problema, en orden creciente de rendimiento.


DataFrame.agg

Este es un str.formatenfoque de base simple .

df['baz'] = df.agg('{0[bar]} is {0[foo]}'.format, axis=1)
df
  foo  bar     baz
0   a    1  1 is a
1   b    2  2 is b
2   c    3  3 is c

También puede usar el formato de cadena f aquí:

df['baz'] = df.agg(lambda x: f"{x['bar']} is {x['foo']}", axis=1)
df
  foo  bar     baz
0   a    1  1 is a
1   b    2  2 is b
2   c    3  3 is c

char.arrayconcatenación basada en

Convierta las columnas para concatenar como chararrays, luego súmelas.

a = np.char.array(df['bar'].values)
b = np.char.array(df['foo'].values)

df['baz'] = (a + b' is ' + b).astype(str)
df
  foo  bar     baz
0   a    1  1 is a
1   b    2  2 is b
2   c    3  3 is c

Comprensión de listas conzip

No puedo exagerar lo subestimados que están las listas por comprensión en pandas.

df['baz'] = [str(x) + ' is ' + y for x, y in zip(df['bar'], df['foo'])]

Alternativamente, usando str.joinpara concat (también escalará mejor):

df['baz'] = [
    ' '.join([str(x), 'is', y]) for x, y in zip(df['bar'], df['foo'])]

df
  foo  bar     baz
0   a    1  1 is a
1   b    2  2 is b
2   c    3  3 is c

Las listas por comprensión sobresalen en la manipulación de cadenas, porque las operaciones de cadenas son intrínsecamente difíciles de vectorizar, y la mayoría de las funciones "vectorizadas" de los pandas son básicamente envoltorios de bucles. He escrito mucho sobre este tema en For bucles con pandas: ¿cuándo debería importarme? . En general, si no tiene que preocuparse por la alineación del índice, use una lista de comprensión cuando se trate de operaciones de cadenas y expresiones regulares.

La lista de compilación anterior de forma predeterminada no maneja NaN. Sin embargo, siempre puede escribir una función que envuelva un intento, excepto si necesita manejarlo.

def try_concat(x, y):
    try:
        return str(x) + ' is ' + y
    except (ValueError, TypeError):
        return np.nan


df['baz'] = [try_concat(x, y) for x, y in zip(df['bar'], df['foo'])]

perfplot Medidas de desempeño

ingrese la descripción de la imagen aquí

Gráfico generado usando perfplot . Aquí está la lista completa de códigos .

Funciones

def brenbarn(df):
    return df.assign(baz=df.bar.map(str) + " is " + df.foo)

def danielvelkov(df):
    return df.assign(baz=df.apply(
        lambda x:'%s is %s' % (x['bar'],x['foo']),axis=1))

def chrimuelle(df):
    return df.assign(
        baz=df['bar'].astype(str).str.cat(df['foo'].values, sep=' is '))

def vladimiryashin(df):
    return df.assign(baz=df.astype(str).apply(lambda x: ' is '.join(x), axis=1))

def erickfis(df):
    return df.assign(
        baz=df.apply(lambda x: f"{x['bar']} is {x['foo']}", axis=1))

def cs1_format(df):
    return df.assign(baz=df.agg('{0[bar]} is {0[foo]}'.format, axis=1))

def cs1_fstrings(df):
    return df.assign(baz=df.agg(lambda x: f"{x['bar']} is {x['foo']}", axis=1))

def cs2(df):
    a = np.char.array(df['bar'].values)
    b = np.char.array(df['foo'].values)

    return df.assign(baz=(a + b' is ' + b).astype(str))

def cs3(df):
    return df.assign(
        baz=[str(x) + ' is ' + y for x, y in zip(df['bar'], df['foo'])])
cs95
fuente
4
Eso es todo lo que siempre quise saber sobre la concatenación de cadenas en pandas, ¡pero tenía demasiado miedo de preguntar!
IanS
¿Puede actualizar la trama al siguiente nivel 10 4 (o incluso superior), una respuesta visual rápida con la trama actual limitada a 10 3 (1000 que es muy pequeña para la condición actual) es que cs3 es la mejor, eventualmente cuando vea brenbarn parece menos exponencial que cs3, por lo que lo más probable es que para conjuntos de datos grandes, brenbarn sea la mejor respuesta (más rápida).
Velizar VESSELINOV
1
@VelizarVESSELINOV ¡Actualizado! Lo que me sorprende es que la concatenación numpy es más lenta que la compilación de listas y la concatenación de pandas.
cs95
1
¿Ha considerado usar df['bar'].tolist()y df['foo'].tolist()adentro cs3()? Supongo que aumentaría ligeramente el tiempo "base", pero escalaría mejor.
Shadowtalker
44

El problema en su código es que desea aplicar la operación en cada fila. Sin embargo, la forma en que lo ha escrito toma todas las columnas 'bar' y 'foo', las convierte en cadenas y le devuelve una cadena grande. Puedes escribirlo como:

df.apply(lambda x:'%s is %s' % (x['bar'],x['foo']),axis=1)

Es más larga que la otra respuesta pero es más genérica (se puede usar con valores que no son cadenas).

Daniel
fuente
13

También podrías usar

df['bar'] = df['bar'].str.cat(df['foo'].values.astype(str), sep=' is ')
chrimuelle
fuente
1
Esto no funciona ya que df ['bar'] no es una columna de cadena. La asignación correcta es df['bar'] = df['bar'].astype(str).str.cat(df['foo'], sep=' is ').
cbrnr
8
df.astype(str).apply(lambda x: ' is '.join(x), axis=1)

0    1 is a
1    2 is b
2    3 is c
dtype: object
Vladimir Iashin
fuente
Esta respuesta también funciona con un número indeterminado de columnas (> 1) y nombres de columna indeterminados, lo que la hace más útil que el resto.
johnDanger
4

La respuesta de @DanielVelkov es la adecuada, PERO usar cadenas literales es más rápido:

# Daniel's
%timeit df.apply(lambda x:'%s is %s' % (x['bar'],x['foo']),axis=1)
## 963 µs ± 157 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# String literals - python 3
%timeit df.apply(lambda x: f"{x['bar']} is {x['foo']}", axis=1)
## 849 µs ± 4.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
erickfis
fuente
4

series.str.cat es la forma más flexible de abordar este problema:

por df = pd.DataFrame({'foo':['a','b','c'], 'bar':[1, 2, 3]})

df.foo.str.cat(df.bar.astype(str), sep=' is ')

>>>  0    a is 1
     1    b is 2
     2    c is 3
     Name: foo, dtype: object

O

df.bar.astype(str).str.cat(df.foo, sep=' is ')

>>>  0    1 is a
     1    2 is b
     2    3 is c
     Name: bar, dtype: object

Lo más importante (ya diferencia de .join()), esto le permite ignorar o reemplazar Nullvalores con el na_repparámetro.

john Peligro
fuente
por qué esta funcionalidad no está envuelta en .join()me confunde
johnDanger