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?


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


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

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

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]

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

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)

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)

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

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

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

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.


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)

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.