Dataframe hücresi içindeki bir liste ayrı satırlara nasıl patlatılır


96

Bir liste içeren bir panda hücresini bu değerlerin her biri için satırlara dönüştürmek istiyorum.

Öyleyse, şunu al:

görüntü açıklamasını buraya girin

nearest_neighborsSütundaki değerleri , her bir değerin her opponentdizinde bir satır olacak şekilde paketini açmak ve istiflemek istersem, bu konuda en iyi nasıl hareket edebilirim? Bunun gibi operasyonlar için tasarlanmış panda yöntemleri var mı?


İstediğiniz çıktıya ve şu ana kadar denediklerinize bir örnek verebilir misiniz? Kesilip yapıştırılabilen bazı örnek veriler sağlarsanız, başkalarının size yardımcı olması en kolay yoldur.
dagrha

Sen kullanabilirsiniz pd.DataFrame(df.nearest_neighbors.values.tolist())sonra bu sütunu açmak için ve pd.mergebaşkalarıyla tutkal.
hellpanderr

@helpanderr values.tolist()burada bir şey yaptığını sanmıyorum ; sütun zaten bir liste
maxymoo


1
Alakalı ama daha fazla ayrıntı içeriyor stackoverflow.com/questions/53218931/…
BENY

Yanıtlar:


56

Aşağıdaki kodda, satır yinelemesini kolaylaştırmak için önce indeksi sıfırladım.

Dış listenin her bir öğesinin hedef DataFrame'in bir satırı olduğu ve iç listenin her öğesinin sütunlardan biri olduğu bir liste listesi oluşturuyorum. Bu iç içe geçmiş liste nihayetinde istenen DataFrame'i oluşturmak için birleştirilecektir.

İlgili ve ile eşlenen lambdaöğenin her bir öğesi için bir satır oluşturmak için liste yinelemesiyle birlikte bir işlev kullanıyorum . nearest_neighborsnameopponent

Son olarak, bu listeden yeni DataFrame (orijinal sütun adları kullanarak ve endeks geri ayar oluşturmak nameve 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

HAZİRAN 2017

Alternatif bir yöntem aşağıdaki gibidir:

>>> (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)en küçük karelerde sorun yok, ancak makul boyuttaki çerçeveler için daha performanslı bir çözümü yeniden düşünmelisiniz. Kodumda ne zaman panda kullanmalıyım () bölümüne bakın ? (Daha iyi bir çözüm, önce sütunu listelemektir.)
cs95

2
Listeye benzer bir sütunun patlatılması , explode()yöntemin eklenmesiyle birlikte Pandalar 0.25'te önemli ölçüde basitleştirildi . Burada olduğu gibi aynı df kurulumunu kullanarak bir örnekle bir cevap ekledim .
joelostblom

@joelostblom Duymak güzel. Mevcut kullanımla örneği eklediğiniz için teşekkür ederiz.
Alexander

39
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')

Dışarı:

                    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
Bunun yalnızca tek bir sütun için çalıştığını unutmayın (0.25'ten itibaren). Daha genel çözümler için buraya ve buraya bakın .
cs95

bu en kolay hızlı çözümdür (gerçekten de mongodb'de çağrılacağı gibi patlatılacak veya "gevşetilecek" listeli tek bir sütununuz varsa)
annakeuchenius

Pandas docu ile en hızlı çözüm. Ancak dikkatli olun: .explode yerinde değildir! Aksine yapmakdf = df.explode(...)
harmonica141

34

Kullanım apply(pd.Series)ve stackardından reset_indexveto_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

Detaylar

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
Çözümünüzün zarafetini sevin! Herhangi bir şans eseri onu diğer yaklaşımlarla kıyasladınız mı?
rpyzh

1
Sonuç df.nearest_neighbors.apply(pd.Series)benim için çok şaşırtıcı;
Calum You

1
@rpyzh Evet, oldukça zarif, ama acınacak derecede yavaş.
cs95

16

Bence bu gerçekten iyi bir soru, Hive'da kullanacağınız EXPLODEbir durum var, Pandaların bu işlevi varsayılan olarak içermesi gerektiğini düşünüyorum. Muhtemelen liste sütununu şu şekilde iç içe geçmiş bir üreteç anlayışı ile patlatırdım:

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

Bu çözümün, her satır için liste öğelerinin sayısının farklı olmasına izin vermesini seviyorum.
user1718097

Orijinal dizini bu yöntemle korumanın bir yolu var mı?
SummerEla

2
@SummerEla lol bu gerçekten eski bir cevaptı, bunu şimdi nasıl yapacağımı göstermek için güncelledim
maxymoo

1
@maxymoo Yine de harika bir soru. Güncellediğiniz için teşekkürler!
SummerEla

Bunu yararlı buldum ve bir pakete
Oren

11

En hızlı buldum yöntem şimdiye kadar olan DataFrame uzatıyor .ilocve geri atama düzleştirilmiş hedef sütunu.

Her zamanki girdiye göre (biraz çoğaltılmış):

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

Aşağıdaki önerilen alternatifler göz önüne alındığında:

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

Bunun extend_iloc()en hızlı olduğunu buluyorum :

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

güzel değerlendirme
StephenBoesch

2
Bunun için teşekkürler, bana gerçekten yardımcı oldu. Ben extend_iloc çözümü kullanılmış ve tespit cols = [c for c in df.columns if c != col_target] olmalıdır: hatalarını sütun indeksi ile sunulan değilse. cols = [i for i,c in enumerate(df.columns) if c != col_target]df.iloc[ilocations, cols].copy()
jdungan

İloc öneriniz için tekrar teşekkürler. Burada nasıl çalıştığına dair ayrıntılı bir açıklama yazdım: medium.com/@johnadungan/… . Umarım benzer bir zorluk yaşayan herkese yardımcı olur.
jdungan

7

Apply (pd.Series) ile daha güzel alternatif çözüm:

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)

Bu satırları değil sütunları genişletir.
Oleg

@Oleg doğru, ancak DataFrame'i her zaman aktarabilir ve ardından diğer önerilerin çoğundan daha basit pd.Series uygulayabilirsiniz
Philipp Schwarz

7

Hive'ın EXPLODE işlevine benzer:

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
Bunu çalıştırdığımda şu hatayı alıyorum:NameError: global name 'copy' is not defined
frmsaul

4

Yani tüm bu cevaplar güzel ama ben ^ gerçekten basit bir şey istedim, işte katkım:

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

İşte bu .. listelerin 'patlatıldığı' yeni bir dizi istediğinizde bunu kullanın. İşte taco seçeneklerinde value_counts () yaptığımız bir örnek :)

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

İşte daha büyük veri çerçeveleri için potansiyel bir optimizasyon. Bu, "patlayan" alanda birkaç eşit değer olduğunda daha hızlı çalışır. (Veri çerçevesi, alandaki benzersiz değer sayısıyla karşılaştırıldığında ne kadar büyük olursa, bu kod o kadar iyi performans gösterir.)

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

Oleg'in .ilocyanıtını tüm liste sütunlarını otomatik olarak düzleştirecek şekilde genişletmek :

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

Bu, her liste sütununun eşit liste uzunluğuna sahip olduğunu varsayar.


1

Apply (pd.Series) kullanmak yerine sütunu düzleştirebilirsiniz. Bu, performansı artırır.

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)

IndexError: Çok fazla seviye: Endeksin 3 değil, sadece 2 seviyesi var, örneğimi denediğimde
vinsent paramanantham

1
Örneğinize göre reset_index'teki "düzey" i değiştirmeniz gerekiyor
suleep kumar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.