Cara meledakkan daftar di dalam sel Dataframe menjadi baris terpisah

93

Saya ingin mengubah sel panda yang berisi daftar menjadi baris untuk masing-masing nilai tersebut.

Jadi, ambil ini:

masukkan deskripsi gambar di sini

Jika saya ingin membongkar dan menumpuk nilai di nearest_neighborskolom sehingga setiap nilai akan menjadi baris di dalam setiap opponentindeks, bagaimana cara terbaik untuk melakukannya? Apakah ada metode panda yang dimaksudkan untuk operasi seperti ini?

SpicyClubSauce
sumber
Bisakah Anda memberikan contoh hasil yang Anda inginkan, dan apa yang telah Anda coba sejauh ini? Paling mudah bagi orang lain untuk membantu Anda jika Anda memberikan beberapa data sampel yang juga dapat dipotong & ditempel.
dagrha
Anda dapat menggunakan pd.DataFrame(df.nearest_neighbors.values.tolist())untuk membongkar kolom ini dan kemudian pd.mergemerekatkannya dengan yang lain.
hellpanderr
@helpanderr saya rasa values.tolist()tidak melakukan apa-apa di sini; kolom sudah menjadi daftar
maxymoo
2
@maxymoo i.imgur.com/YGQAYOY.png
hellpanderr
1
Terkait tetapi berisi lebih detail stackoverflow.com/questions/53218931/…
BEN_YO

Jawaban:

54

Pada kode di bawah ini, saya pertama kali mengatur ulang indeks untuk membuat iterasi baris lebih mudah.

Saya membuat daftar daftar di mana setiap elemen dari daftar luar adalah baris dari DataFrame target dan setiap elemen dari daftar dalam adalah salah satu kolom. Daftar bertingkat ini pada akhirnya akan digabungkan untuk membuat DataFrame yang diinginkan.

Saya menggunakan lambdafungsi bersama dengan daftar iterasi untuk membuat baris untuk setiap elemen yang nearest_neighborsdipasangkan dengan yang relevan namedanopponent .

Akhirnya, saya membuat DataFrame baru dari daftar ini (menggunakan nama kolom asli dan mengatur indeks kembali ke namedan opponent).

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))

>>> df
                                                    nearest_neighbors
name       opponent                                                  
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]

df.reset_index(inplace=True)
rows = []
_ = df.apply(lambda row: [rows.append([row['name'], row['opponent'], nn]) 
                         for nn in row.nearest_neighbors], axis=1)
df_new = pd.DataFrame(rows, columns=df.columns).set_index(['name', 'opponent'])

>>> df_new
                    nearest_neighbors
name       opponent                  
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia

EDIT JUNI 2017

Metode alternatifnya adalah sebagai berikut:

>>> (pd.melt(df.nearest_neighbors.apply(pd.Series).reset_index(), 
             id_vars=['name', 'opponent'],
             value_name='nearest_neighbors')
     .set_index(['name', 'opponent'])
     .drop('variable', axis=1)
     .dropna()
     .sort_index()
     )
Alexander
sumber
apply(pd.Series)baik-baik saja pada bingkai terkecil, tetapi untuk bingkai berukuran wajar, Anda harus mempertimbangkan kembali solusi yang lebih berkinerja. Lihat Kapan saya harus menggunakan pandas apply () di kode saya? (Solusi yang lebih baik adalah dengan mendengarkan kolom terlebih dahulu.)
cs95
2
Meledakkan kolom seperti daftar telah disederhanakan secara signifikan dalam panda 0.25 dengan penambahan explode()metode. Saya menambahkan jawaban dengan contoh menggunakan pengaturan df yang sama seperti di sini.
joelostblom
@joelostblom Senang mendengarnya. Terima kasih telah menambahkan contoh dengan penggunaan saat ini.
Alexander
35
df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))

df.explode('nearest_neighbors')

Di luar:

                    nearest_neighbors
name       opponent                  
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia
joelostblom
sumber
2
Perhatikan bahwa ini hanya berfungsi untuk satu kolom (mulai 0,25). Lihat di sini dan di sini untuk solusi yang lebih umum.
cs95
ini adalah solusi tercepat termudah (memang jika Anda hanya memiliki satu kolom dengan daftar untuk meledak atau "untuk bersantai" seperti yang akan disebut di mongodb)
annakeuchenius
34

Gunakan apply(pd.Series)dan stack, lalu reset_indexdanto_frame

In [1803]: (df.nearest_neighbors.apply(pd.Series)
              .stack()
              .reset_index(level=2, drop=True)
              .to_frame('nearest_neighbors'))
Out[1803]:
                    nearest_neighbors
name       opponent
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia

Detail

In [1804]: df
Out[1804]:
                                                   nearest_neighbors
name       opponent
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
Nol
sumber
1
Cintai keanggunan solusi Anda! Apakah Anda pernah membandingkannya dengan pendekatan lain?
rpyzh
1
Hasil dari df.nearest_neighbors.apply(pd.Series)sangat mencengangkan bagi saya;
Calum You
1
@rpyzh Ya, ini cukup elegan, tapi sangat lambat.
cs95
16

Saya pikir ini pertanyaan yang sangat bagus, di Hive yang akan Anda gunakan EXPLODE, saya pikir ada kasus yang harus dibuat bahwa Panda harus menyertakan fungsi ini secara default. Saya mungkin akan meledakkan kolom daftar dengan pemahaman generator bersarang seperti ini:

pd.DataFrame({
    "name": i[0],
    "opponent": i[1],
    "nearest_neighbor": neighbour
    }
    for i, row in df.iterrows() for neighbour in row.nearest_neighbors
    ).set_index(["name", "opponent"])
maxymoo
sumber
Saya suka bagaimana solusi ini memungkinkan jumlah item daftar berbeda untuk setiap baris.
pengguna1718097
Apakah ada cara untuk mempertahankan indeks asli dengan metode ini?
SummerEla
2
@SummerEla lol ini adalah jawaban yang sangat lama, saya telah memperbarui untuk menunjukkan bagaimana saya akan melakukannya sekarang
maxymoo
1
@maxymoo Ini masih pertanyaan yang bagus. Terima kasih telah memperbarui!
SummerEla
Saya menemukan ini berguna dan mengubahnya menjadi sebuah paket
Oren
11

The tercepat metode yang saya temukan sejauh memperpanjang DataFrame dengan .ilocdan menugaskan kembali datar kolom target.

Diberikan input biasa (direplikasi sedikit):

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))
df = pd.concat([df]*10)

df
Out[3]: 
                                                   nearest_neighbors
name       opponent                                                 
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
...

Diberikan alternatif yang disarankan berikut ini:

col_target = 'nearest_neighbors'

def extend_iloc():
    # Flatten columns of lists
    col_flat = [item for sublist in df[col_target] for item in sublist] 
    # Row numbers to repeat 
    lens = df[col_target].apply(len)
    vals = range(df.shape[0])
    ilocations = np.repeat(vals, lens)
    # Replicate rows and add flattened column of lists
    cols = [i for i,c in enumerate(df.columns) if c != col_target]
    new_df = df.iloc[ilocations, cols].copy()
    new_df[col_target] = col_flat
    return new_df

def melt():
    return (pd.melt(df[col_target].apply(pd.Series).reset_index(), 
             id_vars=['name', 'opponent'],
             value_name=col_target)
            .set_index(['name', 'opponent'])
            .drop('variable', axis=1)
            .dropna()
            .sort_index())

def stack_unstack():
    return (df[col_target].apply(pd.Series)
            .stack()
            .reset_index(level=2, drop=True)
            .to_frame(col_target))

Menurut saya itu extend_iloc()yang tercepat :

%timeit extend_iloc()
3.11 ms ± 544 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit melt()
22.5 ms ± 1.25 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit stack_unstack()
11.5 ms ± 410 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Oleg
sumber
evaluasi yang bagus
javadba
2
Terima kasih untuk ini, ini sangat membantu saya. Saya menggunakan solusi extend_iloc dan menemukan bahwa cols = [c for c in df.columns if c != col_target] harus: cols = [i for i,c in enumerate(df.columns) if c != col_target] The df.iloc[ilocations, cols].copy()kesalahan jika tidak disajikan dengan indeks kolom.
jdungan
Terima kasih sekali lagi atas saran iloc. Saya menulis penjelasan rinci tentang cara kerjanya di sini: medium.com/@johnadungan/… . Semoga dapat membantu siapa pun dengan tantangan serupa.
jdungan
7

Solusi alternatif yang lebih baik dengan apply (pd.Series):

df = pd.DataFrame({'listcol':[[1,2,3],[4,5,6]]})

# expand df.listcol into its own dataframe
tags = df['listcol'].apply(pd.Series)

# rename each variable is listcol
tags = tags.rename(columns = lambda x : 'listcol_' + str(x))

# join the tags dataframe back to the original dataframe
df = pd.concat([df[:], tags[:]], axis=1)
Philipp Schwarz
sumber
Yang ini memperluas kolom, bukan baris.
Oleg
@Oleg benar, tetapi Anda selalu dapat mengubah urutan DataFrame dan kemudian menerapkan pd. Seri - cara yang lebih sederhana daripada kebanyakan saran lainnya
Philipp Schwarz
7

Mirip dengan fungsi EXPLODE Hive:

import copy

def pandas_explode(df, column_to_explode):
    """
    Similar to Hive's EXPLODE function, take a column with iterable elements, and flatten the iterable to one element 
    per observation in the output table

    :param df: A dataframe to explod
    :type df: pandas.DataFrame
    :param column_to_explode: 
    :type column_to_explode: str
    :return: An exploded data frame
    :rtype: pandas.DataFrame
    """

    # Create a list of new observations
    new_observations = list()

    # Iterate through existing observations
    for row in df.to_dict(orient='records'):

        # Take out the exploding iterable
        explode_values = row[column_to_explode]
        del row[column_to_explode]

        # Create a new observation for every entry in the exploding iterable & add all of the other columns
        for explode_value in explode_values:

            # Deep copy existing observation
            new_observation = copy.deepcopy(row)

            # Add one (newly flattened) value from exploding iterable
            new_observation[column_to_explode] = explode_value

            # Add to the list of new observations
            new_observations.append(new_observation)

    # Create a DataFrame
    return_df = pandas.DataFrame(new_observations)

    # Return
    return return_df
13Herger
sumber
1
Ketika saya menjalankan ini, saya mendapatkan kesalahan berikut:NameError: global name 'copy' is not defined
frmsaul
4

Jadi semua jawaban ini bagus tapi saya menginginkan sesuatu ^ sangat sederhana ^ jadi inilah kontribusi saya:

def explode(series):
    return pd.Series([x for _list in series for x in _list])                               

Itu saja .. cukup gunakan ini ketika Anda menginginkan seri baru di mana daftarnya 'meledak'. Berikut adalah contoh di mana kami melakukan value_counts () pada pilihan taco :)

In [1]: my_df = pd.DataFrame(pd.Series([['a','b','c'],['b','c'],['c']]), columns=['tacos'])      
In [2]: my_df.head()                                                                               
Out[2]: 
   tacos
0  [a, b, c]
1     [b, c]
2        [c]

In [3]: explode(my_df['tacos']).value_counts()                                                     
Out[3]: 
c    3
b    2
a    1
Briford Wylie
sumber
2

Berikut adalah potensi pengoptimalan untuk kerangka data yang lebih besar. Ini berjalan lebih cepat jika ada beberapa nilai yang sama di bidang "meledak". (Semakin besar kerangka data dibandingkan dengan jumlah nilai unik di bidang, semakin baik kinerja kode ini.)

def lateral_explode(dataframe, fieldname): 
    temp_fieldname = fieldname + '_made_tuple_' 
    dataframe[temp_fieldname] = dataframe[fieldname].apply(tuple)       
    list_of_dataframes = []
    for values in dataframe[temp_fieldname].unique().tolist(): 
        list_of_dataframes.append(pd.DataFrame({
            temp_fieldname: [values] * len(values), 
            fieldname: list(values), 
        }))
    dataframe = dataframe[list(set(dataframe.columns) - set([fieldname]))]\ 
        .merge(pd.concat(list_of_dataframes), how='left', on=temp_fieldname) 
    del dataframe[temp_fieldname]

    return dataframe
Sinan Ozel
sumber
1

Memperluas .ilocjawaban Oleg untuk secara otomatis meratakan semua kolom daftar:

def extend_iloc(df):
    cols_to_flatten = [colname for colname in df.columns if 
    isinstance(df.iloc[0][colname], list)]
    # Row numbers to repeat 
    lens = df[cols_to_flatten[0]].apply(len)
    vals = range(df.shape[0])
    ilocations = np.repeat(vals, lens)
    # Replicate rows and add flattened column of lists
    with_idxs = [(i, c) for (i, c) in enumerate(df.columns) if c not in cols_to_flatten]
    col_idxs = list(zip(*with_idxs)[0])
    new_df = df.iloc[ilocations, col_idxs].copy()

    # Flatten columns of lists
    for col_target in cols_to_flatten:
        col_flat = [item for sublist in df[col_target] for item in sublist]
        new_df[col_target] = col_flat

    return new_df

Ini mengasumsikan bahwa setiap kolom daftar memiliki panjang daftar yang sama.

Brian Atwood
sumber
1

Alih-alih menggunakan apply (pd.Series) Anda bisa meratakan kolom. Ini meningkatkan kinerja.

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                'opponent': ['76ers', 'blazers', 'bobcats'], 
                'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
  .set_index(['name', 'opponent']))



%timeit (pd.DataFrame(df['nearest_neighbors'].values.tolist(), index = df.index)
           .stack()
           .reset_index(level = 2, drop=True).to_frame('nearest_neighbors'))

1.87 ms ± 9.74 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


%timeit (df.nearest_neighbors.apply(pd.Series)
          .stack()
          .reset_index(level=2, drop=True)
          .to_frame('nearest_neighbors'))

2.73 ms ± 16.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
suleep kumar
sumber
Error Index: Terlalu banyak level: Indeks hanya memiliki 2 level, bukan 3, ketika saya mencoba contoh saya
vinsent paramanantham
1
Anda harus mengubah "level" di reset_index sesuai dengan contoh Anda
suleep kumar