Podziel (rozbij) pandas ciąg danych ramki danych do oddzielnych wierszy

200

Mam pandas dataframew którym jedna kolumna ciągów tekstowych zawiera wartości oddzielone przecinkami. Chcę podzielić każde pole CSV i utworzyć nowy wiersz dla każdego wpisu (załóżmy, że CSV są czyste i trzeba je tylko podzielić na „,”). Na przykład apowinien stać się 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

Do tej pory próbowałem różnych prostych funkcji, ale .applymetoda wydaje się przyjmować tylko jeden wiersz jako wartość zwracaną, gdy jest używana na osi, i nie mogę zabrać się .transformdo pracy. Wszelkie sugestie będą mile widziane!

Przykładowe dane:

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

Wiem, że to nie zadziała, ponieważ tracimy metadane DataFrame przechodząc przez numpy, ale powinno dać ci poczucie tego, co próbowałem zrobić:

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)
Vincent
źródło
2
inne rozwiązania na tej stronie działają, ale znalazłem jeden krótki i skuteczny. stackoverflow.com/questions/27263805/…
desaiankitb
1
Dla innych, którzy przybywają na tę stronę i szukają rozwiązania, które zachowuje wiele kolumn, spójrz na to pytanie: stackoverflow.com/questions/17116814/…
Sos

Odpowiedzi:

81

Co powiesz na coś takiego:

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

Następnie wystarczy zmienić nazwy kolumn

Chang She
źródło
1
Wygląda na to, że to zadziała. Dzięki za pomoc! Ogólnie jednak, czy istnieje preferowane podejście do Split-Apply-Combine, w którym Apply zwraca ramkę danych o dowolnym rozmiarze (ale spójnym dla wszystkich porcji), a Combine po prostu vstackuje zwrócone DF?
Vincent
GroupBy.apply powinien działać (właśnie wypróbowałem to przeciwko master). Jednak w tym przypadku tak naprawdę nie musisz przechodzić przez dodatkowy krok grupowania, ponieważ generujesz dane według wiersza, prawda?
Chang She
1
Cześć ludzie. Przepraszam, że wskoczyłem w to tak późno, ale zastanawiam się, czy nie ma lepszego rozwiązania tego problemu. Próbuję po raz pierwszy eksperymentować z iteracjami, ponieważ wydaje się, że jest to bilet na to. Jestem również zdezorientowany proponowanym rozwiązaniem. Co oznacza „_”? Czy możesz wyjaśnić, jak działa to rozwiązanie? --Dziękuję
horatio1701d
11
Czy rozwiązanie można rozszerzyć na więcej niż dwie kolumny?
horatio1701d
1
sprawdź to wektoryzowane podejście ...
MaxU
146

UPDATE2: bardziej ogólna funkcja wektoryzowana, która będzie działać dla wielu normali wielu listkolumn

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

Próbny:

Wiele listkolumn - wszystkie listkolumny muszą mieć tę samą liczbę elementów w każdym wierszu:

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

zachowując oryginalne wartości indeksu:

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

Ustawiać:

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: []}
})

Kolumna CSV:

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

za pomocą tej małej sztuczki możemy przekonwertować kolumnę podobną do CSV na listkolumnę:

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

AKTUALIZACJA: ogólne podejście wektoryzowane (będzie działać również dla wielu kolumn):

Oryginalny DF:

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

Rozwiązanie:

najpierw przekonwertujmy ciągi CSV na listy:

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

Teraz możemy to zrobić:

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

Stara odpowiedź:

Zainspirowany rozwiązaniem @AFinkelstein , chciałem uczynić go nieco bardziej uogólnionym, który można zastosować do DF z więcej niż dwiema kolumnami i tak szybko, prawie prawie tak szybko, jak rozwiązanie AFinkelsteina):

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
MaxU
źródło
7
koleś, jeśli możesz otworzyć dyskusję w pandach Git, myślę, że potrzebujemy takiej wbudowanej funkcji !!! Widziałem tak wiele pytań na temat niezarejestrowania i niepokoju w SO dla pand
YOBEN_S
jak używać tego do wielu kolumn. Na przykład, jeśli mam dane oddzielone przecinkami w 2 kolumnach i chcę to zrobić sekwencyjnie?
Jaskaran Singh Puri
@JaskaranSinghPuri, najpierw chcesz przekonwertować wszystkie kolumny CSV na listy.
MaxU
1
Bezskutecznie nie działa, jeśli elementy listy są krotkami. Ale po przekonwertowaniu całej krotki na sznurek działa jak urok!
Guido
2
Wygląda na to, że prośba WenBen została usłyszana przez bogów pand, którzy zainstalowali .explode()metodę w API (zobacz również tę odpowiedź ).
cs95
117

Po bolesnych eksperymentach, by znaleźć coś szybszego niż zaakceptowana odpowiedź, zacząłem działać. Działał około 100 razy szybciej w zestawie danych, na którym go wypróbowałem.

Jeśli ktoś zna sposób, aby uczynić to bardziej eleganckim, to proszę zmodyfikuj mój kod. Nie mogłem znaleźć sposobu, który działałby bez ustawienia innych kolumn, które chcesz zachować jako indeks, a następnie zresetowania indeksu i zmiany nazw kolumn, ale wyobrażam sobie, że działa coś jeszcze.

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
DMulligan
źródło
2
To rozwiązanie działało znacznie szybciej i wydaje się, że
zużywa
1
To miłe wektoryzacyjne rozwiązanie dla pand, szukałem tego. Dzięki!
Dennis Golomazov
Kiedy próbuję tego na własnym zbiorze danych, wciąż zaczynam TypeError: object of type 'float' has no len()od pierwszego kroku ( DataFrame(df.var1.str.split(',').tolist()))
user5359531,
@ user5359531 Twój zestaw danych prawdopodobnie ma trochę NaNw tej kolumnie, więc zastąpienie tob = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair
Fyi, oto fajny opis tego rozwiązania z przykładem.
hhbilly,
46

Oto funkcja, którą napisałem dla tego wspólnego zadania. Jest bardziej wydajny niż metody Series/ stack. Kolejność kolumn i nazwy są zachowane.

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

Dzięki tej funkcji oryginalne pytanie jest tak proste, jak:

tidy_split(a, 'var1', sep=',')
Daniel Himmelstein
źródło
1
To jest niesamowicie szybkie! Wielkie dzięki za to.
Anurag N. Sharma,
42

Pandy> = 0,25

Metody Series i DataFrame definiują .explode()metodę, która rozbija listy na osobne wiersze. Zobacz sekcję Dokumenty na temat Rozbijanie kolumny podobnej do listy .

Ponieważ masz listę ciągów oddzielonych przecinkami, podziel ciąg na przecinku, aby uzyskać listę elementów, a następnie wywołaj explodetę kolumnę.

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

Pamiętaj, że explodedziała tylko na jedną kolumnę (na razie).


NaN i puste listy otrzymują leczenie, na które zasługują, bez konieczności przeskakiwania przez obręcze, aby zrobić to dobrze.

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

Jest to poważna zaleta w porównaniu z rozwiązaniami opartymi na ravel+repeat (które całkowicie ignorują puste listy i dławią się na NaN).

cs95
źródło
4
Ten jest najłatwiejszy i najlepiej pasuje w moim przypadku! dzięki!
Isaac Sim
14

Podobne pytanie jak: pandy: Jak podzielić tekst w kolumnie na wiele wierszy?

Mógłbyś:

>> 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
inodb
źródło
2
Działa po dodaniu jeszcze jednego kodu zmiany nazwy 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)})

Demonstracja

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

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

Utwórzmy nową ramkę danych z dlistami

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

Uwagi ogólne

Użyję np.arangez repeatprodukować dataframe indeksach, które można używać z iloc.

FAQ

Dlaczego nie używam loc?

Ponieważ indeks może nie być unikalny i użycie locspowoduje zwrócenie każdego wiersza pasującego do indeksu, którego dotyczy zapytanie.

Dlaczego nie użyjesz tego valuesatrybutu i nie pokroisz go?

Podczas wywoływania values, jeśli całość ramki danych znajduje się w jednym spójnym „bloku”, Pandas zwróci widok tablicy, która jest „blokiem”. W przeciwnym razie Pandy będą musiały ułożyć razem nowy układ. Podczas brukowania tablica musi mieć jednolity typ. Często oznacza to zwrócenie tablicy o typie dtype object. Używając iloczamiast kroić valuesatrybut, zmniejszam się z konieczności radzenia sobie z tym.

Dlaczego używacie assign?

Kiedy używam assigntej samej nazwy kolumny, którą eksploduję, zastępuję istniejącą kolumnę i utrzymuję jej pozycję w ramce danych.

Dlaczego wartości indeksu są powtarzane?

Dzięki zastosowaniu ilocna powtarzanych pozycjach wynikowy indeks pokazuje ten sam powtarzany wzór. Jedno powtórzenie dla każdego elementu listy lub łańcucha.
Można to zresetować za pomocąreset_index(drop=True)


Do strun

Nie chcę przedwcześnie rozdzielać łańcuchów. Zamiast tego liczę wystąpienia separgumentu, zakładając, że gdybym miał podzielić, długość wynikowej listy byłaby o jeden większa niż liczba separatorów.

Następnie używam tego sepdo joinłańcuchów 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)})

Dla list

Podobnie jak w przypadku ciągów, z tym wyjątkiem, że nie muszę liczyć wystąpień, sepponieważ jest już podzielony.

Używam Numpy concatenatedo łączenia list razem.

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

piRSquared
źródło
Ten mi się podoba. Naprawdę zwięzłe, a wydajność też powinna być naprawdę dobra. Jedno pytanie: czy df.iloc [i] to to samo co powtarzanie wierszy ramki danych, czy też jest bardziej wydajne? Dzięki!
Tim
7

Istnieje możliwość podzielenia i rozbicia ramki danych bez zmiany jej struktury

Podziel i rozwiń dane określonych kolumn

Wejście:

    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)

Na zewnątrz:

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

Edycja-1

Podziel i rozwiń wiersze dla wielu kolumn

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]

Ponownie zaindeksuj na podstawie kolumny odniesienia i wyrównaj informacje o wartości kolumny ze stosem

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

Na zewnątrz:

                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
Naga Kiran
źródło
5

Wymyśliłem rozwiązanie dla ramek danych z dowolną liczbą kolumn (wciąż oddzielając tylko wpisy jednej kolumny na raz).

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
jlln
źródło
2
ładne, ale niestety powolne z powodu tej konwersji todict () :(
MAQ
4

Oto dość prosty komunikat, który używa splitmetody z strakcesorium pand, a następnie używa NumPy do spłaszczenia każdego wiersza w jedną tablicę.

Odpowiednie wartości są pobierane przez powtórzenie niepodzielonej kolumny z odpowiednią liczbą razy 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
Ted Petrou
źródło
1
To może być bardzo piękna odpowiedź. Niestety nie skaluje się dla wielu kolumn, prawda?
Michael Dorner,
3

Walczę z brakiem pamięci, używając różnych sposobów na rozbicie moich list, więc przygotowałem testy porównawcze, które pomogą mi zdecydować, które odpowiedzi odpowiedzieć. Testowałem pięć scenariuszy o różnych proporcjach długości listy do liczby list. Udostępnianie wyników poniżej:

Czas: (mniej znaczy lepiej, kliknij, aby wyświetlić dużą wersję)

Prędkość

Szczytowe użycie pamięci: (mniej znaczy lepiej)

Szczytowe użycie pamięci

Wnioski :

  • @ Odpowiedź MaxU (aktualizacja 2), konkatenat kryptonimu oferuje najlepszą prędkość w prawie każdym przypadku, przy zachowaniu niskiego zużycia pamięci podglądu,
  • patrz odpowiedź @ DMulligan ( stos kryptonimów ), jeśli chcesz przetworzyć wiele wierszy ze stosunkowo małymi listami i stać Cię na zwiększenie pamięci szczytowej,
  • zaakceptowana odpowiedź @ Changa działa dobrze dla ramek danych, które mają kilka wierszy, ale bardzo duże listy.

Pełne informacje (funkcje i kod porównawczy) znajdują się w tej liście GitHub . Należy pamiętać, że problem testu porównawczego został uproszczony i nie obejmował podziału ciągów znaków na listę - które większość rozwiązań działała w podobny sposób.

krassowski
źródło
Niezłe porównanie! Czy masz zamiar opublikować kod, którego użyłeś do wykreślenia testów?
MaxU
1
Zobacz ten link: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (już uwzględniony w odpowiedzi) - IMO wklejenie go tutaj byłoby trochę za długo.
krassowski
2

W oparciu o doskonałe rozwiązanie @ DMulligan , tutaj jest ogólna funkcja wektoryzacji (bez pętli), która dzieli kolumnę ramki danych na wiele wierszy i łączy ją z powrotem w oryginalną ramkę danych. Wykorzystuje również wielką ogólną change_column_orderfunkcję z tej odpowiedzi .

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)

Przykład:

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    

Pamiętaj, że zachowuje oryginalny indeks i kolejność kolumn. Działa również z ramkami danych, które mają indeks niesekwencyjny.

Dennis Golomazov
źródło
2
to pękło dla mnie, dobra robota: stackoverflow.com/a/48554655/6672746
Evan
2

Podział funkcji łańcuchowej może przyjmować opcjonalny argument logiczny „rozwinąć”.

Oto rozwiązanie wykorzystujące ten argument:

(a.var1
  .str.split(",",expand=True)
  .set_index(a.var2)
  .stack()
  .reset_index(level=1, drop=True)
  .reset_index()
  .rename(columns={0:"var1"}))
cgels
źródło
1

Po prostu użyłem doskonałej odpowiedzi Jilna z góry, ale musiał rozwinąć się, aby podzielić wiele kolumn. Myślałem, że podzielę się.

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
Derryn Webster-Knife
źródło
1

zaktualizowałem odpowiedź MaxU z obsługą MultiIndex

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
Shahar Katz
źródło
1

One-liner użyciu split(___, expand=True)a leveli nameargumenty 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

Jeśli chcesz bwyglądać dokładnie tak jak w pytaniu, możesz dodatkowo:

>>> 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
1 ''
źródło
0

Wymyśliłem następujące rozwiązanie tego problemu:

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"])
Pavel
źródło
0

Inne rozwiązanie korzystające z pakietu kopii Pythona

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)
Ankit Maheshwari
źródło
0

Tutaj jest wiele odpowiedzi, ale jestem zaskoczony, że nikt nie wspomniał o wbudowanej funkcji wybuchania pand. Sprawdź poniższy link: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Z jakiegoś powodu nie mogłem uzyskać dostępu do tej funkcji, więc użyłem poniższego kodu:

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

wprowadź opis zdjęcia tutaj

Powyżej znajduje się próbka moich danych. Jak widać kolumna osób zawierała szereg ludzi, a ja próbowałem ją rozbić. Podany przeze mnie kod działa dla danych typu listy. Spróbuj więc przenieść dane tekstowe rozdzielone przecinkami do formatu listy. Ponieważ mój kod korzysta z wbudowanych funkcji, jest znacznie szybszy niż funkcje niestandardowe / zastosuj.

Uwaga: Może być konieczne zainstalowanie pandas_explode za pomocą pip.

Harsha Reddy
źródło
0

Miałem podobny problem, moim rozwiązaniem było najpierw przekonwertowanie ramki danych na listę słowników, a następnie przejście. Oto funkcja:

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)

Przykład:

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

Możesz także nieco zmienić funkcję, aby obsługiwała oddzielanie wierszy typu listy.

Zhiwei
źródło