web-dev-qa-db-fra.com

Concaténation de cordes de deux colonnes de pandas

J'ai une DataFrame suivante:

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

Cela ressemble à ceci:

    bar foo
0    1   a
1    2   b
2    3   c

Maintenant, je veux avoir quelque chose comme:

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

Comment puis-je atteindre cet objectif? J'ai essayé ce qui suit:

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

mais cela me donne un résultat erroné:

>>>print df.ix[0]

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

Désolé pour une question idiote, mais celle-ci pandas: combiner deux colonnes dans un DataFrame ne m'a pas été utile.

50
nat

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

91
BrenBarn

Le problème dans votre code est que vous souhaitez appliquer l'opération à chaque ligne. La façon dont vous l'avez écrit prend cependant toutes les colonnes "bar" et "foo", les convertit en chaînes et vous rend une grande chaîne. Vous pouvez l'écrire comme ceci:

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

Il est plus long que l'autre réponse mais est plus générique (peut être utilisé avec des valeurs qui ne sont pas des chaînes).

36
Daniel Velkov

Vous pouvez aussi utiliser

df['bar'] = df['bar'].str.cat(df['foo'].values.astype(str), sep=' is ')
11
chrimuelle
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
4
Vladimir Yashin

Voici d'autres solutions, par ordre croissant de performances.

DataFrame.agg
C’est une approche simple basée sur str.format -.

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

Vous pouvez également utiliser le formatage f-string ici:

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.array addition
Convertissez les colonnes pour les concaténer en tant que chararrays, puis additionnez-les.

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

Liste Compréhension avec Zip
Je ne peux pas exagérer à quel point la compréhension des listes est sous-estimée dans les pandas. 

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

Sinon, utilisez str.join pour concaténer (sera également amélioré):

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

Compréhension de liste Excel lors de la manipulation de chaînes, car les opérations sur les chaînes de caractères sont par nature difficiles à vectoriser, et la plupart des fonctions "vectorisées" de pandas sont essentiellement des wrappers autour de boucles. J'ai beaucoup écrit sur ce sujet dans Pour les boucles avec des pandas - Quand devrais-je m'en soucier? . En général, si vous n'avez pas à vous soucier de l'alignement d'index, utilisez une compréhension de la liste pour traiter des opérations de chaîne et de regex. 

La liste comp ci-dessus par défaut ne gère pas les NaN. Cependant, vous pouvez toujours écrire une fonction encapsulant un essai, sauf si vous devez le gérer.

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 Mesures de performance - Configuration et minutages 

Nous pouvons chronométrer ces solutions en utilisant perfplot:

data = {'bar': {0: 1, 1: 2, 2: 3}, 'foo': {0: 'a', 1: 'b', 2: 'c'}}
df_ = pd.DataFrame(data)

perfplot.show(
    setup=lambda n: pd.concat([df_] * n, ignore_index=True),
    kernels=[
        brenbarn, danielvelkov, chrimuelle, vladimiryashin, erickfis, 
        cs1_format, cs1_fstrings, cs2, cs3
    ],
    labels=[
        'brenbarn', 'danielvelkov', 'chrimuelle', 'vladimiryashin', 'erickfis', 
        'cs1_format', 'cs1_fstrings', 'cs2', 'cs3'
    ],
    n_range=[2**k for k in range(0, 8)],
    xlabel='N (x len(df_))',    
    logy=True,
    equality_check=lambda x, y: (x == y).values.all()
)

 enter image description here

La performance est relative; l'intrigue est logarithmique le long de l'axe des ordonnées.

Les fonctions

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'])])
2
coldspeed

La réponse de @DanielVelkov est la bonne MAIS L'utilisation de littéraux de chaîne est 10 fois plus rapide

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

# String literals - python 3
%timeit df.apply(lambda x: f"{x['bar']} is {x['foo']}", axis=1)
0
erickfis