Teilen Sie den Eintrag für Pandas-Datenrahmenzeichenfolgen (explodieren Sie), um die Zeilen zu trennen


200

Ich habe eine, pandas dataframein der eine Spalte von Textzeichenfolgen durch Kommas getrennte Werte enthält. Ich möchte jedes CSV-Feld aufteilen und eine neue Zeile pro Eintrag erstellen (vorausgesetzt, die CSV ist sauber und muss nur auf ',' aufgeteilt werden). Zum Beispiel asollte werden b:

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 .applyMethode scheint nur eine Zeile als Rückgabewert zu akzeptieren, wenn sie auf einer Achse verwendet wird, und ich kann nicht .transformarbeiten. Anregungen wäre sehr dankbar!

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 DataFrame-Metadaten verlieren, indem wir numpy durchlaufen, aber es sollte Ihnen einen Eindruck davon geben, was ich 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)

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

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

Antworten:


81

Wie wäre es mit so etwas:

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
Sieht so aus, als würde das funktionieren. Danke für Ihre Hilfe! Gibt es jedoch im Allgemeinen einen bevorzugten Ansatz für Split-Apply-Combine, bei dem Apply einen Datenrahmen beliebiger Größe zurückgibt (aber für alle Chunks konsistent ist) und Combine nur die zurückgegebenen DFs stapelt?
Vincent

GroupBy.apply sollte funktionieren (ich habe es gerade gegen Master versucht). In diesem Fall müssen Sie jedoch nicht wirklich den zusätzlichen Schritt der Gruppierung durchlaufen, da Sie die Daten zeilenweise generieren, oder?
Chang She

1
Hallo Leute. Es tut mir leid, dass ich so spät darauf eingegangen bin, aber ich frage mich, ob es dafür keine bessere Lösung gibt. Ich versuche zum ersten Mal mit Iterrows zu experimentieren, da dies das Ticket dafür zu sein scheint. Ich bin auch verwirrt über die vorgeschlagene Lösung. Was bedeutet das "_"? Können Sie möglicherweise erklären, wie die Lösung funktioniert? --Thank Sie
horatio1701d

11
Kann die Lösung auf mehr als zwei Spalten erweitert werden?
horatio1701d

1
Bitte überprüfen Sie diesen vektorisierten Ansatz ...
MaxU

146

UPDATE2: Allgemeinere vektorisierte Funktion, die für mehrere normalund mehrere listSpalten funktioniert

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # make sure `lst_cols` is list-alike
    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]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    # create "exploded" DF
    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}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

Demo:

Mehrere listSpalten - Alle listSpalten müssen dieselbe 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

Beibehaltung der ursprünglichen Indexwerte:

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

Konfiguration:

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 CSV-ähnliche Spalten in listSpalten 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: generischer vektorisierter Ansatz (funktioniert auch für mehrere Spalten):

Original 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:

Lassen Sie uns zuerst CSV-Zeichenfolgen in Listen konvertieren:

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 das 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:

Inspiriert von der @ AFsteinstein-Lösung wollte ich sie etwas verallgemeinern, was auf DF mit mehr als zwei Spalten angewendet werden kann und so schnell, fast so schnell wie die AFinkelstein-Lösung):

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

7
Alter, wenn du eine Diskussion in Git Pandas eröffnen kannst, denke ich, dass wir eine eingebaute Funktion wie diese brauchen !!! Ich habe so viele Fragen über die Auflistung und Unnesting in SO für Pandas gesehen
YOBEN_S

wie man dies für mehrere Spalten verwendet. Zum Beispiel, wenn ich durch Kommas getrennte Daten in 2 Spalten habe und dies nacheinander tun möchte?
Jaskaran Singh Puri

@JaskaranSinghPuri, Sie möchten zuerst alle CSV-Spalten in Listen konvertieren.
MaxU

1
Leider funktioniert es nicht, wenn Ihre Listenelemente Tupel sind. Aber nachdem das gesamte Tupel in einen String konvertiert wurde, funktioniert es wie ein Zauber!
Guido

2
Es sieht so aus, als ob WenBens Plädoyer von den Pandas-Göttern gehört wurde. Sie haben eine .explode()Methode in die API installiert (siehe auch diese Antwort ).
CS95

117

Nach schmerzhaften Experimenten, um etwas schneller als die akzeptierte Antwort zu finden, brachte ich dies zum Laufen. Es lief ungefähr 100x schneller auf dem Datensatz, den ich anprobiert habe.

Wenn jemand einen Weg kennt, dies eleganter zu gestalten, ändern Sie auf jeden Fall meinen Code. Ich konnte keinen Weg finden, der funktioniert, ohne die anderen Spalten, die Sie als Index behalten möchten, festzulegen und dann den Index zurückzusetzen und die Spalten umzubenennen, aber ich würde mir vorstellen, dass noch etwas anderes funktioniert.

b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack()
b = b.reset_index()[[0, 'var2']] # var1 variable is currently labeled 0
b.columns = ['var1', 'var2'] # renaming var1

2
Diese Lösung arbeitete deutlich schneller und scheint weniger Speicher zu
verbrauchen

1
Dies ist eine schöne vektorisierte Pandas-Lösung, nach der ich gesucht habe. Vielen Dank!
Dennis Golomazov

Wenn ich dies an meinem eigenen Datensatz versuche, TypeError: object of type 'float' has no len()DataFrame(df.var1.str.split(',').tolist())
komme

@ user5359531 Ihr Datensatz hat wahrscheinlich einige NaNin dieser Spalte, so ist der Ersatzb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair

Nur zu Ihrer Information, hier ist eine schöne Zusammenfassung dieser Lösung mit Beispiel.
hhbilly

46

Hier ist eine Funktion, die ich für diese allgemeine Aufgabe geschrieben habe. Es ist effizienter als die Series/ stackMethoden. Spaltenreihenfolge und Namen bleiben erhalten.

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column's values
    keep : bool
        whether to retain the presplit value as it's own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    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 ursprüngliche Frage so einfach wie:

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

1
Das geht unglaublich schnell! Vielen Dank dafür.
Anurag N. Sharma

42

Pandas> = 0,25

Serien- und DataFrame-Methoden definieren eine .explode()Methode, die Listen in separate Zeilen auflöst . Weitere Informationen finden Sie im Abschnitt "Dokumente" zum Auflösen einer listenähnlichen Spalte .

Da Sie eine Liste von durch Kommas getrennten Zeichenfolgen haben, teilen Sie die Zeichenfolge durch Komma auf, um eine Liste der Elemente zu erhalten, und rufen Sie dann explodediese 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 diesexplode (vorerst) nur für eine einzelne Spalte funktioniert .


NaNs und leere Listen erhalten die Behandlung, die sie verdienen, ohne dass Sie durch Reifen springen 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  # empty list entry becomes empty string after exploding 
2  NaN     3  # NaN left un-touched

Dies ist ein schwerwiegender Vorteil gegenüber ravel+ repeat-basierten Lösungen (die leere Listen vollständig ignorieren und NaNs ersticken).


4
Dieser ist der einfachste und passt am besten in meinen Fall! Vielen Dank!
Isaac Sim

14

Ähnliche Frage wie: pandas: Wie teile ich Text in einer Spalte in mehrere Zeilen auf?

Du könntest es tun:

>> a=pd.DataFrame({"var1":"a,b,c d,e,f".split(),"var2":[1,2]})
>> s = a.var1.str.split(",").apply(pd.Series, 1).stack()
>> s.index = s.index.droplevel(-1)
>> del a['var1']
>> a.join(s)
   var2 var1
0     1    a
0     1    b
0     1    c
1     2    d
1     2    e
1     2    f

2
Es funktioniert nach dem Hinzufügen eines weiteren Umbenennungscodes s.name = 'var1'
Jesse

14

TL; DR

import pandas as pd
import numpy as np

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

Demonstration

explode_str(a, 'var1', ',')

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

Erstellen wir einen neuen Datenrahmen dmit Listen

d = a.assign(var1=lambda d: d.var1.str.split(','))

explode_list(d, 'var1')

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

Allgemeine Kommentare

Ich werde np.arangemit verwenden repeat, um Dataframe-Indexpositionen zu erstellen, mit denen ich arbeiten kann iloc.

FAQ

Warum benutze ich nicht loc?

Weil der Index möglicherweise nicht eindeutig ist und verwendet wird loc wird jede Zeile zurückgegeben, die einem abgefragten Index entspricht.

Warum benutzt du nicht das valuesAttribut und schneidest das?

Wenn sich valuesder gesamte Datenrahmen beim Aufruf in einem zusammenhängenden "Block" befindet, gibt Pandas eine Ansicht des Arrays zurück, das der "Block" ist. Andernfalls müssen Pandas ein neues Array zusammenschustern. Beim Kopfsteinpflaster muss dieses Array einen einheitlichen Typ haben. Oft bedeutet dies, ein Array mit dem Typ dtype zurückzugeben object. Indem ich ilocdas valuesAttribut verwende, anstatt es zu zerschneiden , lasse ich mich davon abhalten, damit umgehen zu müssen.

Warum benutzt du assign?

Wenn ich benutze assign denselben Spaltennamen verwende, den ich explodiere, überschreibe ich die vorhandene Spalte und behalte ihre Position im Datenrahmen bei.

Warum wiederholen sich die Indexwerte?

Aufgrund der Verwendung ilocan wiederholten Positionen zeigt der resultierende Index das gleiche wiederholte Muster. Eine Wiederholung für jedes Element der Liste oder Zeichenfolge.
Dies kann mit zurückgesetzt werdenreset_index(drop=True)


Für Streicher

Ich möchte die Saiten nicht vorzeitig teilen müssen. Stattdessen zähle ich die Vorkommen des sepArguments unter der Annahme, dass bei einer Aufteilung die Länge der resultierenden Liste um eins größer wäre als die Anzahl der Trennzeichen.

Ich benutze das dann sepfür joindie Saiten split.

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

Für Listen

Ähnlich wie bei Zeichenfolgen, außer dass ich keine Vorkommen von zählen muss sep da diese bereits aufgeteilt sind.

Ich benutze Numpy's, concatenateum die Listen zusammen zu jammen.

import pandas as pd
import numpy as np

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})


Ich mag diesen. Wirklich prägnant und die Leistung sollte auch wirklich gut sein. Eine Frage ist jedoch: Ist df.iloc [i] dasselbe wie das Wiederholen von Zeilen des Datenrahmens oder ist es effizienter als das? Vielen Dank!
Tim

7

Es besteht die Möglichkeit, den Datenrahmen zu teilen und zu explodieren, ohne die Struktur des Datenrahmens zu ändern

Teilen und erweitern Sie Daten bestimmter Spalten

Eingang:

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



#Get the indexes which are repetative with the split 
temp = df['var1'].str.split(',')
df = df.reindex(df.index.repeat(temp.apply(len)))


df['var1'] = np.hstack(temp)

Aus:

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

Edit-1

Teilen und Erweitern von Zeilen für mehrere Spalten

Filename    RGB                                             RGB_type
0   A   [[0, 1650, 6, 39], [0, 1691, 1, 59], [50, 1402...   [r, g, b]
1   B   [[0, 1423, 16, 38], [0, 1445, 16, 46], [0, 141...   [r, g, b]

Indizierung basierend auf der Referenzspalte und Ausrichten der Spaltenwertinformationen mit dem Stapel

df = df.reindex(df.index.repeat(df['RGB_type'].apply(len)))
df = df.groupby('Filename').apply(lambda x:x.apply(lambda y: pd.Series(y.iloc[0])))
df.reset_index(drop=True).ffill()

Aus:

                Filename    RGB_type    Top 1 colour    Top 1 frequency Top 2 colour    Top 2 frequency
    Filename                            
 A  0       A   r   0   1650    6   39
    1       A   g   0   1691    1   59
    2       A   b   50  1402    49  187
 B  0       B   r   0   1423    16  38
    1       B   g   0   1445    16  46
    2       B   b   0   1419    16  39

5

Ich habe eine Lösung für Datenrahmen mit einer beliebigen Anzahl von Spalten gefunden (wobei immer nur die Einträge einer Spalte gleichzeitig getrennt werden).

def splitDataFrameList(df,target_column,separator):
    ''' df = dataframe to split,
    target_column = the column containing the values to split
    separator = the symbol used to perform the split

    returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
    The values in the other columns are duplicated across the newly divided rows.
    '''
    def splitListToRows(row,row_accumulator,target_column,separator):
        split_row = row[target_column].split(separator)
        for s in split_row:
            new_row = row.to_dict()
            new_row[target_column] = s
            row_accumulator.append(new_row)
    new_rows = []
    df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
    new_df = pandas.DataFrame(new_rows)
    return new_df

2
nett, aber leider langsam wegen dieser todict () Konvertierung :(
MAQ

4

Hier ist eine ziemlich einfache Nachricht, die die splitMethode von Pandas verwendetstr accessor verwendet und dann NumPy verwendet, um jede Zeile in ein einzelnes Array zu reduzieren.

Die entsprechenden Werte werden abgerufen, indem die nicht geteilte Spalte die richtige Anzahl mit wiederholt wird np.repeat.

var1 = df.var1.str.split(',', expand=True).values.ravel()
var2 = np.repeat(df.var2.values, len(var1) / len(df))

pd.DataFrame({'var1': var1,
              'var2': var2})

  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

1
Das könnte eine sehr schöne Antwort sein. Leider skaliert es nicht für viele Spalten, oder?
Michael Dorner

3

Ich hatte Probleme mit Speicherproblemen, bei denen meine Listen auf verschiedene Weise aufgelöst wurden. Daher habe ich einige Benchmarks vorbereitet, um zu entscheiden, welche Antworten positiv bewertet werden sollen. Ich habe fünf Szenarien mit unterschiedlichen Anteilen der Listenlänge an der Anzahl der Listen getestet. Teilen Sie die Ergebnisse unten:

Zeit: (weniger ist besser, klicken, um eine große Version anzuzeigen)

Geschwindigkeit

Maximale Speichernutzung: (weniger ist besser)

Maximale Speichernutzung

Schlussfolgerungen :

  • @ MaxUs Antwort (Update 2), Codename concatenate bietet die beste Geschwindigkeit in fast jedem Fall, während die peek Speicherverbrauch niedrig zu halten,
  • siehe @ DMulligans Antwort (Codename Stapel ) , wenn Sie mit relativ kleinen Listen zu verarbeiten viele Zeilen benötigen und sich leisten können , erhöhte Spitzenspeicher,
  • das akzeptiert Antwort von @ Chang funktioniert gut für Datenrahmen mit wenigen Zeilen, aber sehr großen Listen.

Ausführliche Informationen (Funktionen und Benchmarking-Code) finden Sie in dieser GitHub-Übersicht . Bitte beachten Sie, dass das Benchmark-Problem vereinfacht wurde und keine Aufteilung von Zeichenfolgen in die Liste beinhaltete - die meisten Lösungen wurden auf ähnliche Weise durchgeführt.


Schöner Vergleich! Haben Sie etwas dagegen, einen Code zu veröffentlichen, den Sie zum Zeichnen der Benchmarks verwendet haben?
MaxU

1
Bitte sehen Sie diesen Link: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (bereits in der Antwort enthalten) - IMO wäre es etwas zu lang, um alles hier einzufügen .
Krassowski

2

Basierend auf der exzellenten @ DMulligan- Lösung gibt es hier eine generische vektorisierte Funktion (keine Schleifen), die eine Spalte eines Datenrahmens in mehrere Zeilen aufteilt und sie wieder mit dem ursprünglichen Datenrahmen zusammenführt. Es verwendet auch eine großartige generische change_column_orderFunktion aus dieser Antwort .

def change_column_order(df, col_name, index):
    cols = df.columns.tolist()
    cols.remove(col_name)
    cols.insert(index, col_name)
    return df[cols]

def split_df(dataframe, col_name, sep):
    orig_col_index = dataframe.columns.tolist().index(col_name)
    orig_index_name = dataframe.index.name
    orig_columns = dataframe.columns
    dataframe = dataframe.reset_index()  # we need a natural 0-based index for proper merge
    index_col_name = (set(dataframe.columns) - set(orig_columns)).pop()
    df_split = pd.DataFrame(
        pd.DataFrame(dataframe[col_name].str.split(sep).tolist())
        .stack().reset_index(level=1, drop=1), columns=[col_name])
    df = dataframe.drop(col_name, axis=1)
    df = pd.merge(df, df_split, left_index=True, right_index=True, how='inner')
    df = df.set_index(index_col_name)
    df.index.name = orig_index_name
    # merge adds the column to the last place, so we need to move it back
    return change_column_order(df, col_name, orig_col_index)

Beispiel:

df = pd.DataFrame([['a:b', 1, 4], ['c:d', 2, 5], ['e:f:g:h', 3, 6]], 
                  columns=['Name', 'A', 'B'], index=[10, 12, 13])
df
        Name    A   B
    10   a:b     1   4
    12   c:d     2   5
    13   e:f:g:h 3   6

split_df(df, 'Name', ':')
    Name    A   B
10   a       1   4
10   b       1   4
12   c       2   5
12   d       2   5
13   e       3   6
13   f       3   6    
13   g       3   6    
13   h       3   6    

Beachten Sie, dass der ursprüngliche Index und die Reihenfolge der Spalten beibehalten werden. Es funktioniert auch mit Datenrahmen, die keinen nicht sequentiellen Index haben.


2
das hat dieses für mich geknackt, gute
Evan

2

Die Aufteilung der Zeichenfolgenfunktion kann ein boolesches Optionsargument 'expand' annehmen.

Hier ist eine Lösung mit diesem Argument:

(a.var1
  .str.split(",",expand=True)
  .set_index(a.var2)
  .stack()
  .reset_index(level=1, drop=True)
  .reset_index()
  .rename(columns={0:"var1"}))

1

Ich habe gerade Jilns ausgezeichnete Antwort von oben verwendet, musste aber erweitert werden, um mehrere Spalten zu teilen. Ich dachte, ich würde teilen.

def splitDataFrameList(df,target_column,separator):
''' df = dataframe to split,
target_column = the column containing the values to split
separator = the symbol used to perform the split

returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
The values in the other columns are duplicated across the newly divided rows.
'''
def splitListToRows(row, row_accumulator, target_columns, separator):
    split_rows = []
    for target_column in target_columns:
        split_rows.append(row[target_column].split(separator))
    # Seperate for multiple columns
    for i in range(len(split_rows[0])):
        new_row = row.to_dict()
        for j in range(len(split_rows)):
            new_row[target_columns[j]] = split_rows[j][i]
        row_accumulator.append(new_row)
new_rows = []
df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
new_df = pd.DataFrame(new_rows)
return new_df

1

Die Antwort von MaxU wurde mit MultiIndex-Unterstützung aktualisiert

def explode(df, lst_cols, fill_value='', preserve_index=False):
    """
    usage:
        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
    """
    # make sure `lst_cols` is list-alike
    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]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    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}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)

    # if original index is MultiIndex build the dataframe from the multiindex
    # create "exploded" DF
    if isinstance(df.index, pd.MultiIndex):
        res = res.reindex(
            index=pd.MultiIndex.from_tuples(
                res.index,
                names=['number', 'color']
            )
    )
    return res

1

Einzeiler mit split(___, expand=True)und levelund nameArgumente reset_index():

>>> b = a.var1.str.split(',', expand=True).set_index(a.var2).stack().reset_index(level=0, name='var1')
>>> b
   var2 var1
0     1    a
1     1    b
2     1    c
0     2    d
1     2    e
2     2    f

Wenn Sie bgenau wie in der Frage aussehen müssen , können Sie zusätzlich Folgendes tun:

>>> b = b.reset_index(drop=True)[['var1', 'var2']]
>>> b
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

0

Ich habe die folgende Lösung für dieses Problem gefunden:

def iter_var1(d):
    for _, row in d.iterrows():
        for v in row["var1"].split(","):
            yield (v, row["var2"])

new_a = DataFrame.from_records([i for i in iter_var1(a)],
        columns=["var1", "var2"])

0

Eine andere Lösung, die Python-Kopierpaket verwendet

import copy
new_observations = list()
def pandas_explode(df, column_to_explode):
    new_observations = list()
    for row in df.to_dict(orient='records'):
        explode_values = row[column_to_explode]
        del row[column_to_explode]
        if type(explode_values) is list or type(explode_values) is tuple:
            for explode_value in explode_values:
                new_observation = copy.deepcopy(row)
                new_observation[column_to_explode] = explode_value
                new_observations.append(new_observation) 
        else:
            new_observation = copy.deepcopy(row)
            new_observation[column_to_explode] = explode_values
            new_observations.append(new_observation) 
    return_df = pd.DataFrame(new_observations)
    return return_df

df = pandas_explode(df, column_name)

0

Hier gibt es viele Antworten, aber ich bin überrascht, dass niemand die eingebaute Pandas-Explosionsfunktion erwähnt hat. Überprüfen Sie den Link unten: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Aus irgendeinem Grund konnte ich nicht auf diese Funktion zugreifen, daher habe ich den folgenden Code verwendet:

import pandas_explode
pandas_explode.patch()
df_zlp_people_cnt3 = df_zlp_people_cnt2.explode('people')

Geben Sie hier die Bildbeschreibung ein

Oben ist ein Beispiel meiner Daten. Wie Sie sehen können, hatte die Personenspalte eine Reihe von Personen, und ich habe versucht, sie zu explodieren. Der von mir angegebene Code funktioniert für Listentypdaten. Versuchen Sie also, Ihre durch Kommas getrennten Textdaten in das Listenformat zu bringen. Da mein Code integrierte Funktionen verwendet, ist er viel schneller als benutzerdefinierte / angewendete Funktionen.

Hinweis: Möglicherweise müssen Sie pandas_explode mit pip installieren.


0

Ich hatte ein ähnliches Problem. Meine Lösung bestand darin, den Datenrahmen zuerst in eine Liste von Wörterbüchern zu konvertieren und dann den Übergang durchzuführen. Hier ist die Funktion:

import copy
import re

def separate_row(df, column_name):
    ls = []
    for row_dict in df.to_dict('records'):
        for word in re.split(',', row_dict[column_name]):
            row = copy.deepcopy(row_dict)
            row[column_name]=word
            ls(row)
    return pd.DataFrame(ls)

Beispiel:

>>> from pandas import DataFrame
>>> import numpy as np
>>> a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
>>> a
    var1  var2
0  a,b,c     1
1  d,e,f     2
>>> separate_row(a, "var1")
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Sie können die Funktion auch ein wenig ändern, um das Trennen von Zeilen vom Listentyp zu unterstützen.

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.