359 Stimmen

Teilen (aufteilen) Sie den Zeichenfolgeneintrag des Pandas-Datenrahmens in separate Zeilen auf

Ich habe ein Pandas DataFrame, in dem eine Spalte mit Textzeichenfolgen Komma-getrennte Werte enthält. Ich möchte jedes CSV-Feld aufteilen und für jeden Eintrag eine neue Zeile erstellen (angenommen, dass das CSV sauber ist und nur auf ',' aufgeteilt werden muss). Zum Beispiel sollte a zu b werden:

In [7]: a
Out[7]: 
    var1  var2
0  a,b,c     1
1  d,e,f     2

In [8]: b
Out[8]: 
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Bisher habe ich verschiedene einfache Funktionen ausprobiert, aber die Methode .apply scheint nur einen Zeilenwert als Rückgabewert zu akzeptieren, wenn sie auf einer Achse verwendet wird, und ich kann .transform nicht zum Laufen bringen. Über jede Hilfe würde ich mich freuen!

Beispieldaten:

from pandas import DataFrame
import numpy as np
a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
b = DataFrame([{'var1': 'a', 'var2': 1},
               {'var1': 'b', 'var2': 1},
               {'var1': 'c', 'var2': 1},
               {'var1': 'd', 'var2': 2},
               {'var1': 'e', 'var2': 2},
               {'var1': 'f', 'var2': 2}])

Ich weiß, dass dies nicht funktioniert, weil wir durch die Verwendung von NumPy die Meta-Daten des DataFrame verlieren, aber es sollte Ihnen eine Vorstellung davon geben, was ich zu tun versucht habe:

def fun(row):
    letters = row['var1']
    letters = letters.split(',')
    out = np.array([row] * len(letters))
    out['var1'] = letters
a['idx'] = range(a.shape[0])
z = a.groupby('idx')
z.transform(fun)

3 Stimmen

Andere Lösungen auf dieser Seite funktionieren, aber ich fand die folgende kurz und effektiv. stackoverflow.com/questions/27263805/…

2 Stimmen

Für andere, die diese Seite besuchen und nach einer Lösung suchen, die mehrere Spalten beibehält, werfen Sie einen Blick auf diese Frage: stackoverflow.com/questions/17116814/…

242voto

UPDATE 3: es macht mehr Sinn, die Series.explode() / DataFrame.explode() Methoden zu verwenden (implementiert in Pandas 0.25.0 und erweitert in Pandas 1.3.0 zur Unterstützung von Multi-Column Explode), wie im Beispiel gezeigt:

für eine einzelne Spalte:

In [1]: df = pd.DataFrame({'A': [[0, 1, 2], 'foo', [], [3, 4]],
   ...:                    'B': 1,
   ...:                    'C': [['a', 'b', 'c'], np.nan, [], ['d', 'e']]})

In [2]: df
Out[2]:
           A  B          C
0  [0, 1, 2]  1  [a, b, c]
1        foo  1        NaN
2         []  1         []
3     [3, 4]  1     [d, 'e']

In [3]: df.explode('A')
Out[3]:
     A  B          C
0    0  1  [a, b, c]
0    1  1  [a, b, c]
0    2  1  [a, b, c]
1  foo  1        NaN
2  NaN  1         []
3    3  1     [d, e]
3    4  1     [d, e]

für mehrere Spalten (für Pandas 1.3.0+):

In [4]: df.explode(['A', 'C'])
Out[4]:
     A  B    C
0    0  1    a
0    1  1    b
0    2  1    c
1  foo  1  NaN
2  NaN  1  NaN
3    3  1    d
3    4  1    e

UPDATE 2: generische vektorisierte Funktion, die für mehrere normale und mehrere list-Spalten funktioniert

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # Stellen Sie sicher, dass `lst_cols` listenähnlich ist
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # Alle Spalten außer `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # Längen der Listen berechnen
    lens = df[lst_cols[0]].str.len()
    # Originale Index-Werte beibehalten    
    idx = np.repeat(df.index.values, lens)
    # "explodierte" DF erstellen
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # Zeilen anfügen, die leere Listen haben
    if (lens == 0).any():
        # mindestens eine Liste in den Zellen ist leer
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # die ursprüngliche Indexreihenfolge umkehren
    res = res.sort_index()
    # Index zurücksetzen, wenn angefordert
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

Demo:

Mehrere list Spalten - alle list-Spalten müssen die gleiche Anzahl von Elementen in jeder Zeile haben:

In [134]: df
Out[134]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2         []            []
2   12     3     [1, 2]      [cc, dd]
3   13     4         []            []

In [135]: explode(df, ['num','text'], fill_value='')
Out[135]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2
4   12     3   1   cc
5   12     3   2   dd
6   13     4

Originale Index-Werte beibehalten:

In [136]: explode(df, ['num','text'], fill_value='', preserve_index=True)
Out[136]:
   aaa  myid num text
0   10     1   1   aa
0   10     1   2   bb
0   10     1   3   cc
1   11     2
2   12     3   1   cc
2   12     3   2   dd
3   13     4

Setup:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [], 2: [1, 2], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: [], 2: ['cc', 'dd'], 3: []}
})

CSV-Spalte:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

mit diesem kleinen Trick können wir eine CSV-ähnliche Spalte in eine list-Spalte konvertieren:

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

UPDATE: allgemeiner vektorisierter Ansatz (funktioniert auch für mehrere Spalten):

Ursprüngliches DF:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

Lösung:

Zuerst wandeln wir CSV-Zeichenfolgen in Listen um:

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

Jetzt können wir dies tun:

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

ALTE Antwort:

Insipriert von @AFinkelstein Lösung, wollte ich es etwas allgemeiner machen, was auf DF mit mehr als zwei Spalten angewendet werden könnte und so schnell wie, nun fast so schnell wie, wie die Lösung von AFinkelstein):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

25 Stimmen

Kumpel, wenn du eine Diskussion in Git pandas öffnen könntest, denke ich, dass wir eine Funktion wie diese benötigen !!! Ich habe so viele Fragen zu "unlistify" und "unnesting" in SO für pandas gesehen.

1 Stimmen

Wie man dies für mehrere Spalten verwenden kann. Wenn ich z.B. durch Kommas getrennte Daten in 2 Spalten habe und diese nacheinander verarbeiten möchte?

0 Stimmen

@JaskaranSinghPuri, du möchtest zunächst alle CSV-Spalten in Listen umwandeln.

145voto

DMulligan Punkte 8743

Nach schmerzhaften Experimenten, um etwas Schnelleres als die akzeptierte Antwort zu finden, habe ich das zum Laufen gebracht. Es lief etwa 100-mal schneller auf dem Datensatz, den ich ausprobiert habe.

Wenn jemand eine elegantere Methode kennt, bitte ändern Sie meinen Code. Ich konnte keinen Weg finden, der funktioniert, ohne die anderen Spalten, die Sie behalten möchten, als Index zu setzen und dann den Index zurückzusetzen und die Spalten umzubenennen, aber ich stelle mir vor, es gibt noch etwas anderes, das funktioniert.

b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack()
b = b.reset_index()[[0, 'var2']] # Die Variable var1 ist derzeit als 0 bezeichnet
b.columns = ['var1', 'var2'] # umbenennen var1

4 Stimmen

Diese Lösung funktionierte signifikant schneller und scheint weniger Speicher zu verwenden.

3 Stimmen

Dies ist eine schöne vektorisierte Pandas-Lösung, die ich gesucht habe. Vielen Dank!

1 Stimmen

Wenn ich dies mit meinem eigenen Datensatz versuche, erhalte ich beim allerersten Schritt (DataFrame(df.var1.str.split(',').tolist())) immer wieder TypeError: object of type 'float' has no len().

143voto

cs95 Punkte 325143

Pandas >= 0,25

Series- und DataFrame-Methoden definieren eine .explode()-Methode, die Listen in separate Zeilen aufteilt. Siehe den Abschnitt in der Dokumentation über Aufspaltung einer listenähnlichen Spalte.

Da Sie eine Liste von durch Kommas getrennten Zeichen haben, teilen Sie den String beim Komma auf, um eine Liste von Elementen zu erhalten, und rufen Sie dann explode für diese Spalte auf.

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 'var2': [1, 2]})
df
    var1  var2
0  a,b,c     1
1  d,e,f     2

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Beachten Sie, dass explode nur auf einer einzelnen Spalte funktioniert (im Moment). Um mehrere Spalten gleichzeitig aufzuspalten, siehe unten.

NaNs und leere Listen werden entsprechend behandelt, ohne dass Sie sich verrenken müssen, um es richtig zu machen.

df = pd.DataFrame({'var1': ['d,e,f', '', np.nan], 'var2': [1, 2, 3]})
df
    var1  var2
0  d,e,f     1
1            2
2    NaN     3

df['var1'].str.split(',')

0    [d, e, f]
1           []
2          NaN

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    d     1
0    e     1
0    f     1
1          2  # leeres Listen-Element wird nach dem Aufspalten zu einem leeren String 
2  NaN     3  # NaN bleibt unberührt

Dies ist ein echter Vorteil gegenüber ravel/repeat-basierten Lösungen (die leere Listen komplett ignorieren und bei NaNs scheitern).


Aufspaltung mehrerer Spalten

pandas 1.3 Update

df.explode funktioniert ab pandas 1.3 auf mehreren Spalten:

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 
                   'var2': ['i,j,k', 'l,m,n'], 
                   'var3': [1, 2]})
df
    var1   var2  var3
0  a,b,c  i,j,k     1
1  d,e,f  l,m,n     2

(df.set_index(['var3']) 
       .apply(lambda col: col.str.split(','))
       .explode(['var1', 'var2'])
       .reset_index()
       .reindex(df.columns, axis=1))

  var1 var2  var3
0    a    i     1
1    b    j     1
2    c    k     1
3    d    l     2
4    e    m     2
5    f    n     2

In älteren Versionen würde man die explode-Spalte innerhalb des apply verschieben, was wesentlich weniger performant ist:

(df.set_index(['var3']) 
   .apply(lambda col: col.str.split(',').explode())
   .reset_index()
   .reindex(df.columns, axis=1))

Die Idee besteht darin, alle Spalten, die NICHT aufgespalten werden sollen, als Index zu setzen, und dann die übrigen Spalten über apply aufzuspalten. Dies funktioniert gut, wenn die Listen gleich groß sind.

1 Stimmen

+1. Aber wenn ich eine neue Spalte verwende, funktioniert es nicht wie erwartet. Wie df.assign(var3=df['var1'].str.split(',')).explode('var1') Können Sie bitte helfen? Ich habe var1 mit var3 ersetzt.

0 Stimmen

@Avinash das Argument für explode sollte auch var3 sein. Dies ist ein grundlegendes Problem, daher nehmen Sie sich einige Minuten Zeit, um zu verstehen, warum.

0 Stimmen

Wenn Sie Zeilen mit Zeichenfolgen und Ganzzahlen haben, müssen Sie .astype(str) verwenden, da Sie andernfalls NaN-Werte für die Ganzzahlen erhalten.

104voto

Chang She Punkte 16322

Wie wäre es damit:

In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))              
                    for _, row in a.iterrows()]).reset_index()
Out[55]: 
  index  0
0     a  1
1     b  1
2     c  1
3     d  2
4     e  2
5     f  2

Dann müssen Sie nur noch die Spalten umbenennen

1 Stimmen

Sieht so aus, als würde das funktionieren. Vielen Dank für Ihre Hilfe! Ist es generell jedoch bevorzugt, den Split-Apply-Combine-Ansatz zu verwenden, bei dem Apply ein DataFrame beliebiger Größe (aber konsistent für alle Chunks) zurückgibt und Combine einfach die zurückgegebenen DFs stapelt?

0 Stimmen

GroupBy.apply sollte funktionieren (ich habe es gerade gegen Master ausprobiert). In diesem Fall brauchen Sie jedoch nicht wirklich den zusätzlichen Schritt des Groupings durchzuführen, da Sie die Daten Zeile für Zeile generieren, oder?

1 Stimmen

Hey Leute. Es tut mir leid, dass ich so spät dazu komme, aber ich frage mich, ob es nicht eine bessere Lösung dafür gibt. Ich versuche zum ersten Mal mit iterrows zu experimentieren, da das scheinbar das Richtige dafür ist. Ich bin auch verwirrt über die vorgeschlagene Lösung. Was stellt das "_" dar? Könntest du eventuell erklären, wie die Lösung funktioniert? - Danke

55voto

Daniel Himmelstein Punkte 1490

Hier ist eine Funktion, die ich geschrieben habe für diese häufige Aufgabe. Es ist effizienter als die Methoden Series/stack. Die Reihenfolge und Namen der Spalten bleiben erhalten.

def tidy_split(df, column, sep='|', keep=False):
    """
    Teilt die Werte einer Spalte auf und erweitert sie, sodass das neue DataFrame einen aufgeteilten Wert pro Zeile hat. Filtert Zeilen, in denen die Spalte fehlt.

    Parameter
    ---------
    df : pandas.DataFrame
        DataFrame mit der zu aufteilenden und zu erweiternden Spalte
    column : str
        die Spalte, die aufgeteilt und erweitert werden soll
    sep : str
        der String, der zum Aufteilen der Werte der Spalte verwendet wird
    keep : bool
        ob der prä-aufgeteilte Wert als eigene Zeile erhalten bleiben soll

    Rückgabe
    --------
    pandas.DataFrame
        Gibt ein DataFrame mit denselben Spalten wie `df` zurück.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df

Mit dieser Funktion ist die originale Frage so einfach wie:

tidy_split(a, 'var1', sep=',')

2 Stimmen

Dies ist wahnsinnig schnell! Vielen Dank dafür.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X