String-Verkettung von zwei Pandas-Spalten


84

Ich habe folgendes DataFrame:

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

Es sieht aus wie das:

    bar foo
0    1   a
1    2   b
2    3   c

Jetzt möchte ich etwas haben wie:

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

Wie kann ich das erreichen? Ich habe folgendes versucht:

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

aber es gibt mir ein falsches Ergebnis:

>>>print df.ix[0]

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

Entschuldigung für eine dumme Frage, aber diese eine Pandas: Das Kombinieren von zwei Spalten in einem DataFrame war für mich nicht hilfreich.

Antworten:



65

Diese Frage wurde bereits beantwortet, aber ich glaube, es wäre gut, einige nützliche Methoden, die zuvor nicht erörtert wurden, in den Mix aufzunehmen und alle bisher vorgeschlagenen Methoden hinsichtlich der Leistung zu vergleichen.

Hier sind einige nützliche Lösungen für dieses Problem in aufsteigender Reihenfolge der Leistung.


DataFrame.agg

Dies ist ein einfacher str.formatAnsatz.

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

Sie können hier auch die F-String-Formatierung verwenden:

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-basierte Verkettung

Konvertieren Sie die Spalten so chararrays, dass sie verkettet werden , und fügen Sie sie dann zusammen.

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

Listenverständnis mitzip

Ich kann nicht übertreiben, wie unterschätzt das Listenverständnis bei Pandas ist.

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

Alternativ mit str.joinconcat (wird auch besser skaliert):

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

Listenverständnisse zeichnen sich durch die Manipulation von Zeichenfolgen aus, da Zeichenfolgenoperationen von Natur aus schwer zu vektorisieren sind und die meisten "vektorisierten" Pandas-Funktionen im Grunde genommen Wrapper um Schleifen sind. Ich habe ausführlich über dieses Thema in For-Schleifen mit Pandas geschrieben - Wann sollte es mich interessieren? . Wenn Sie sich keine Gedanken über die Indexausrichtung machen müssen, verwenden Sie im Allgemeinen ein Listenverständnis, wenn Sie mit Zeichenfolgen- und Regex-Operationen arbeiten.

Die obige Liste enthält standardmäßig keine NaNs. Sie können jedoch jederzeit eine Funktion schreiben, die einen Versuch umschließt, es sei denn, Sie müssen damit umgehen.

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 Leistungsmessungen

Geben Sie hier die Bildbeschreibung ein

Grafik erstellt mit Perfplot . Hier ist die vollständige Codeliste .

Funktionen

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

4
Das ist alles, was ich schon immer über String-Verkettung bei Pandas wissen wollte, aber ich hatte zu viel Angst, um zu fragen!
IanS

Können Sie bitte den Plot auf die nächste Stufe 10 4 (oder noch höher) aktualisieren ? Eine schnelle visuelle Antwort mit dem aktuellen Plot, der auf 10 3 begrenzt ist (1000, was für die heutige Bedingung sehr klein ist), ist, dass cs3 das Beste ist, wenn Sie es sehen brenbarn sieht weniger exponentiell aus als cs3, daher ist brenbarn für große Datenmengen höchstwahrscheinlich die beste (schnellere) Antwort.
Velizar VESSELINOV

1
@VelizarVESSELINOV Aktualisiert! Was mich überrascht ist, dass die Numpy-Verkettung langsamer ist als die List-Comp- und die Pandas-Verkettung.
CS95

1
Haben Sie darüber nachgedacht, df['bar'].tolist()und df['foo'].tolist()in zu verwenden cs3()? Ich vermute, dass es die "Basis" -Zeit leicht erhöhen würde, aber es würde besser skalieren.
Shadowtalker

44

Das Problem in Ihrem Code ist, dass Sie die Operation auf jede Zeile anwenden möchten. Die Art und Weise, wie Sie es geschrieben haben, nimmt die gesamten Spalten 'bar' und 'foo', konvertiert sie in Zeichenfolgen und gibt Ihnen eine große Zeichenfolge zurück. Sie können es schreiben wie:

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

Es ist länger als die andere Antwort, aber allgemeiner (kann mit Werten verwendet werden, die keine Zeichenfolgen sind).


13

Sie könnten auch verwenden

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

1
Dies funktioniert nicht, da df ['bar'] keine Zeichenfolgenspalte ist. Die richtige Zuordnung ist 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

Diese Antwort funktioniert auch mit einer unbestimmten Anzahl von Spalten (> 1) und unbestimmten Spaltennamen, was sie nützlicher macht als die anderen.
johnDanger

4

@ DanielVelkov Antwort ist die richtige, aber die Verwendung von String-Literalen ist schneller:

# 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)

4

series.str.cat ist der flexibelste Weg, um dieses Problem anzugehen:

Zum 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

ODER

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

Am wichtigsten (und anders als .join()) ist, dass Sie damit NullWerte ignorieren oder durch den na_repParameter ersetzen können .


warum diese Funktionalität nicht eingepackt ist, .join()verwirrt mich
johnDanger
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.