Pisahkan (meledak) panda entri string dataframe ke baris terpisah

200

Saya memiliki pandas dataframedi mana satu kolom string teks berisi nilai yang dipisahkan koma. Saya ingin membagi setiap bidang CSV dan membuat baris baru per entri (asumsikan bahwa CSV bersih dan hanya perlu dibagi pada ','). Misalnya, aharus menjadi 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

Sejauh ini, saya telah mencoba berbagai fungsi sederhana, tetapi .applymetode ini tampaknya hanya menerima satu baris sebagai nilai balik ketika digunakan pada suatu sumbu, dan saya tidak dapat mulai .transformbekerja. Setiap saran akan sangat dihargai!

Contoh data:

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

Saya tahu ini tidak akan berhasil karena kami kehilangan meta-data DataFrame dengan melewati numpy, tetapi itu akan memberi Anda gambaran tentang apa yang saya coba lakukan:

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
sumber
2
solusi lain pada halaman ini berfungsi tetapi saya menemukan solusi yang singkat dan efektif. stackoverflow.com/questions/27263805/…
desaiankitb
1
Bagi yang lain yang datang ke halaman ini dan mencari solusi yang membuat banyak kolom, lihat pertanyaan ini: stackoverflow.com/questions/17116814/…
Sos

Jawaban:

81

Bagaimana dengan sesuatu yang seperti ini:

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

Maka Anda hanya perlu mengganti nama kolom

Chang She
sumber
1
Sepertinya ini akan berhasil. Terima kasih atas bantuan Anda! Secara umum, apakah ada pendekatan yang lebih disukai untuk Split-Terapkan-Combine di mana Terapkan mengembalikan kerangka data ukuran sewenang-wenang (tapi konsisten untuk semua bongkahan), dan Combine hanya vstacks DF yang dikembalikan?
Vincent
GroupBy.apply harus bekerja (saya baru saja mencoba melawan master). Namun, dalam hal ini Anda tidak benar-benar perlu melalui langkah pengelompokan tambahan karena Anda menghasilkan data berdasarkan baris, bukan?
Chang She
1
Hai teman-teman. Maaf karena terlambat begini, tetapi bertanya-tanya apakah tidak ada solusi yang lebih baik untuk ini. Saya mencoba bereksperimen dengan iterrows untuk pertama kalinya karena sepertinya ini adalah tiket untuk ini. Saya juga bingung dengan solusi yang diajukan. Apa yang dilambangkan "_"? Bisakah Anda menjelaskan bagaimana solusinya bekerja? --Terima kasih
horatio1701d
11
Bisakah solusi diperluas ke lebih dari dua kolom?
horatio1701d
1
silakan periksa pendekatan vektor ini ...
Maks
147

UPDATE2: fungsi vektorisasi yang lebih umum, yang akan berfungsi untuk banyak normaldan beberapa listkolom

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:

Beberapa listkolom - semua listkolom harus memiliki # elemen yang sama di setiap baris:

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

mempertahankan nilai indeks asli:

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

Mempersiapkan:

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

Kolom 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

menggunakan trik kecil ini kita dapat mengonversi kolom mirip CSV ke listkolom:

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: pendekatan umum vektor (akan bekerja juga untuk banyak kolom):

DF asli:

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

Larutan:

pertama mari kita mengonversi string CSV ke daftar:

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

Sekarang kita bisa melakukan ini:

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

Jawaban LAMA:

Terinspirasi oleh solusi @AFinkelstein , saya ingin membuatnya sedikit lebih umum yang dapat diterapkan pada DF dengan lebih dari dua kolom dan secepat, juga hampir, secepat solusi 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
MaxU
sumber
7
kawan, jika Anda bisa membuka diskusi di Git panda, saya pikir kita perlu fungsi build in seperti ini !!! Saya telah melihat begitu banyak pertanyaan tentang unlistify dan unesting dalam SO untuk panda
YOBEN_S
cara menggunakan ini untuk banyak kolom. Seperti jika saya memiliki data yang dipisahkan koma dalam 2 kolom dan ingin melakukannya secara berurutan?
Jaskaran Singh Puri
@JaskaranSinghPuri, Anda ingin mengonversi semua kolom CSV menjadi daftar terlebih dahulu.
MaxU
1
Tanpa sadar, itu tidak berfungsi jika elemen daftar Anda tupel. Tetapi setelah mengubah seluruh tuple menjadi string, itu berfungsi seperti pesona!
Guido
2
Sepertinya permohonan WenBen didengar oleh para dewa panda, mereka telah menginstal .explode()metode ke API (juga lihat jawaban ini ).
cs95
117

Setelah percobaan yang menyakitkan untuk menemukan sesuatu yang lebih cepat daripada jawaban yang diterima, saya berhasil. Itu berlari sekitar 100x lebih cepat pada dataset saya mencobanya.

Jika seseorang tahu cara membuat ini lebih elegan, silakan modifikasi kode saya. Saya tidak dapat menemukan cara yang berfungsi tanpa mengatur kolom lain yang ingin Anda simpan sebagai indeks dan kemudian mengatur ulang indeks dan penamaan ulang kolom, tetapi saya membayangkan ada hal lain yang berfungsi.

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
sumber
2
Solusi ini bekerja lebih cepat dan tampaknya menggunakan lebih sedikit memori,
cyril
1
Ini adalah solusi panda vektor yang bagus, saya mencari itu. Terima kasih!
Dennis Golomazov
Ketika saya mencoba ini pada dataset saya sendiri, saya terus mendapatkan TypeError: object of type 'float' has no len()pada langkah pertama ( DataFrame(df.var1.str.split(',').tolist()))
user5359531
@ user5359531 dataset Anda mungkin memiliki beberapa NaNdi kolom itu, jadi penggantinyab = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair
Hanya fyi inilah penulisan yang bagus dari solusi ini dengan contoh.
hhbilly
46

Inilah fungsi yang saya tulis untuk tugas umum ini. Ini lebih efisien daripada metode Series/ stack. Urutan kolom dan nama dipertahankan.

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

Dengan fungsi ini, pertanyaan aslinya semudah:

tidy_split(a, 'var1', sep=',')
Daniel Himmelstein
sumber
1
Ini sangat cepat! Terima kasih banyak untuk ini.
Anurag N. Sharma
42

Panda> = 0,25

Metode Series dan DataFrame menentukan .explode()metode yang meledakkan daftar menjadi baris terpisah. Lihat bagian dokumen pada Meledak kolom seperti daftar .

Karena Anda memiliki daftar string yang dipisahkan koma, pisahkan string pada koma untuk mendapatkan daftar elemen, lalu panggil explodekolom itu.

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

Perhatikan bahwa explodehanya berfungsi pada satu kolom (untuk saat ini).


NaNs dan daftar kosong mendapatkan perawatan yang layak tanpa Anda harus melompat melalui lingkaran untuk melakukannya dengan benar.

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

Ini adalah keuntungan serius dari solusi berbasis ravel+repeat (yang mengabaikan daftar kosong sepenuhnya, dan tersedak NaNs).

cs95
sumber
4
Yang ini paling mudah dan paling cocok untuk saya! Terima kasih!
Isaac Sim
14

Pertanyaan serupa sebagai: panda: Bagaimana cara membagi teks dalam kolom menjadi beberapa baris?

Anda bisa melakukannya:

>> 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
sumber
2
Ini berfungsi setelah menambahkan satu lagi ganti nama kode 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)})

Demonstrasi

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

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

Mari kita membuat kerangka data baru dyang memiliki daftar

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

Komentar umum

Saya akan gunakan np.arangedengan repeatuntuk menghasilkan posisi indeks dataframe yang dapat saya gunakan dengan iloc.

Faq

Kenapa saya tidak menggunakan loc?

Karena indeks mungkin tidak unik dan menggunakan loc akan mengembalikan setiap baris yang cocok dengan indeks yang diminta.

Mengapa Anda tidak menggunakan valuesatribut dan mengirisnya?

Saat memanggil values, jika keseluruhan kerangka data berada dalam satu "blok" yang kohesif, Pandas akan mengembalikan tampilan array yang merupakan "blok". Kalau tidak, Panda harus merakit array baru. Ketika berbatu, array itu harus dari tipe yang seragam. Seringkali itu berarti mengembalikan array dengan dtype object. Dengan menggunakan ilocalih-alih mengiris valuesatribut, saya mengurangi diri saya karena harus berurusan dengan itu.

Kenapa kamu menggunakan assign ?

Saat saya gunakan assign menggunakan nama kolom yang sama dengan yang saya meledak, saya menimpa kolom yang ada dan mempertahankan posisinya dalam kerangka data.

Mengapa nilai indeks diulang?

Berdasarkan penggunaan ilocpada posisi berulang, indeks yang dihasilkan menunjukkan pola berulang yang sama. Satu pengulangan untuk setiap elemen daftar atau string.
Ini dapat diatur ulang denganreset_index(drop=True)


Untuk Strings

Saya tidak ingin harus membagi string sebelum waktunya. Jadi alih-alih saya menghitung kemunculan separgumen tersebut dengan asumsi bahwa jika saya harus membelah, panjang daftar yang dihasilkan akan menjadi satu lebih dari jumlah pemisah.

Saya kemudian menggunakannya sepuntuk joinstring itu 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)})

Untuk daftar

Mirip dengan string kecuali saya tidak perlu menghitung kemunculannya sepkarena sudah terpecah.

Saya menggunakan Numpy concatenateuntuk menyatukan daftar.

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

piRquared
sumber
Saya suka yang ini. Sangat ringkas dan performanya juga harus sangat bagus. Namun satu pertanyaan: apakah df.iloc [i] sama dengan mengulang baris dataframe atau lebih efisien dari itu? Terima kasih!
Tim
7

Ada kemungkinan untuk membagi dan meledak dataframe tanpa mengubah struktur dataframe

Membagi dan memperluas data kolom tertentu

Memasukkan:

    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)

Di luar:

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

Edit-1

Membagi dan Memperluas baris untuk Beberapa kolom

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]

Mengindeks ulang berdasarkan kolom referensi dan menyelaraskan informasi nilai kolom dengan tumpukan

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

Di luar:

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

Saya datang dengan solusi untuk kerangka data dengan jumlah kolom yang berubah-ubah (sementara masih hanya memisahkan entri satu kolom pada satu waktu).

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
sumber
2
bagus tapi sayangnya lambat karena konversi todict () ini :(
MAQ
4

Berikut adalah pesan yang cukup mudah yang menggunakan splitmetode dari pandastr accessor dan kemudian menggunakan NumPy untuk meratakan setiap baris menjadi satu array.

Nilai-nilai yang sesuai diambil dengan mengulangi kolom yang tidak terbagi dengan jumlah kali yang benar 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
sumber
1
Itu bisa menjadi jawaban yang sangat indah. Sayangnya, itu tidak skala untuk banyak kolom, bukan?
Michael Dorner
3

Saya telah berjuang dengan pengalaman di luar memori menggunakan berbagai cara untuk meledakkan daftar saya jadi saya menyiapkan beberapa tolok ukur untuk membantu saya memutuskan jawaban mana yang akan dihapus. Saya menguji lima skenario dengan proporsi panjang daftar yang berbeda-beda terhadap jumlah daftar. Bagikan hasil di bawah ini:

Waktu: (lebih sedikit lebih baik, klik untuk melihat versi besar)

Mempercepat

Penggunaan memori puncak: (lebih sedikit lebih baik)

Penggunaan memori puncak

Kesimpulan :

  • @Jawaban MaxU (update 2), codename concatenate menawarkan kecepatan terbaik di hampir setiap kasus, sambil tetap mengintip penggunaan memori,
  • lihat jawaban @ DMulligan (codename stack ) jika Anda perlu memproses banyak baris dengan daftar yang relatif kecil dan mampu meningkatkan memori puncak,
  • yang diterima @ Chang yang bekerja dengan baik untuk bingkai data yang memiliki beberapa baris tetapi daftar yang sangat besar.

Detail lengkap (fungsi dan kode pembandingan) ada di inti GitHub ini . Harap dicatat bahwa masalah tolok ukur disederhanakan dan tidak termasuk pemisahan string ke dalam daftar - yang sebagian besar solusi dilakukan dengan cara yang sama.

krassowski
sumber
Perbandingan bagus! Apakah Anda keberatan memposting kode, yang Anda gunakan untuk merencanakan tolok ukur?
MaxU
1
Silakan lihat tautan ini: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (sudah termasuk dalam jawaban) - IMO akan agak terlalu panjang untuk menempelkan semuanya di sini.
krassowski
2

Berdasarkan solusi @ DMulligan yang sangat baik , berikut adalah fungsi generik yang di-vectorized (tanpa loop) yang membagi kolom dari dataframe menjadi beberapa baris, dan menggabungkannya kembali ke dataframe asli. Ini juga menggunakan change_column_orderfungsi generik yang hebat dari jawaban ini .

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)

Contoh:

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    

Perhatikan bahwa ini mempertahankan indeks dan urutan kolom asli. Ini juga bekerja dengan kerangka data yang memiliki indeks non-sekuensial.

Dennis Golomazov
sumber
2
ini memecahkan yang ini untuk saya, kerja bagus: stackoverflow.com/a/48554655/6672746
Evan
2

Pemecahan fungsi string dapat mengambil opsi boolean argumen 'perluas'.

Berikut ini solusi menggunakan argumen ini:

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

Hanya menggunakan jawaban jiln yang sangat baik dari atas, tetapi perlu diperluas untuk membagi beberapa kolom. Kupikir aku akan berbagi.

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

meningkatkan jawaban MaxU dengan dukungan 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
sumber
1

One-liner using split(___, expand=True)dan leveldan nameargumen untuk 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

Jika Anda perlu bterlihat persis seperti dalam pertanyaan, Anda juga dapat melakukan:

>>> 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 ''
sumber
0

Saya datang dengan solusi berikut untuk masalah ini:

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

Solusi lain yang menggunakan paket salinan python

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

Ada banyak jawaban di sini tapi saya terkejut tidak ada yang menyebutkan fungsi meledak dalam panda. Lihat tautan di bawah ini: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Untuk beberapa alasan saya tidak dapat mengakses fungsi itu, jadi saya menggunakan kode di bawah ini:

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

masukkan deskripsi gambar di sini

Di atas adalah contoh data saya. Seperti yang Anda lihat orang kolom memiliki serangkaian orang, dan saya mencoba untuk meledakkannya. Kode yang saya berikan berfungsi untuk data tipe daftar. Jadi cobalah untuk memasukkan data teks yang dipisahkan koma ke dalam format daftar. Juga karena kode saya menggunakan fungsi bawaan, ini jauh lebih cepat daripada fungsi kustom / terapkan.

Catatan: Anda mungkin perlu menginstal pandas_explode dengan pip.

Harsha Reddy
sumber
0

Saya memiliki masalah yang sama, solusi saya adalah mengkonversi dataframe ke daftar kamus terlebih dahulu, kemudian melakukan transisi. Inilah fungsinya:

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)

Contoh:

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

Anda juga dapat mengubah fungsi sedikit untuk mendukung memisahkan baris jenis daftar.

Zhiwei
sumber