Pandalar sütun listelerinde, her liste öğesi için bir satır oluşturun


163

Bazı hücreler birden çok değer listelerini içeren bir veri çerçevesi var. Bir hücrede birden çok değer depolamak yerine, listedeki her öğenin kendi satırını (diğer tüm sütunlarda aynı değerlerle) alacak şekilde veri çerçevesini genişletmek istiyorum. Eğer varsa:

import pandas as pd
import numpy as np

df = pd.DataFrame(
    {'trial_num': [1, 2, 3, 1, 2, 3],
     'subject': [1, 1, 1, 2, 2, 2],
     'samples': [list(np.random.randn(3).round(2)) for i in range(6)]
    }
)

df
Out[10]: 
                 samples  subject  trial_num
0    [0.57, -0.83, 1.44]        1          1
1    [-0.01, 1.13, 0.36]        1          2
2   [1.18, -1.46, -0.94]        1          3
3  [-0.08, -4.22, -2.05]        2          1
4     [0.72, 0.79, 0.53]        2          2
5    [0.4, -0.32, -0.13]        2          3

Uzun biçime nasıl dönüştürebilirim, örneğin:

   subject  trial_num  sample  sample_num
0        1          1    0.57           0
1        1          1   -0.83           1
2        1          1    1.44           2
3        1          2   -0.01           0
4        1          2    1.13           1
5        1          2    0.36           2
6        1          3    1.18           0
# etc.

Dizin önemli değildir, mevcut sütunları dizin olarak ayarlamak uygundur ve son sıralama önemli değildir.


11
Pandalar 0.25'ten bunu df.explode('samples')çözmek için de kullanabilirsiniz . explodeşimdilik yalnızca bir sütunun patlamasını destekleyebilir.
cs95

Yanıtlar:


48
lst_col = 'samples'

r = pd.DataFrame({
      col:np.repeat(df[col].values, df[lst_col].str.len())
      for col in df.columns.drop(lst_col)}
    ).assign(**{lst_col:np.concatenate(df[lst_col].values)})[df.columns]

Sonuç:

In [103]: r
Out[103]:
    samples  subject  trial_num
0      0.10        1          1
1     -0.20        1          1
2      0.05        1          1
3      0.25        1          2
4      1.32        1          2
5     -0.17        1          2
6      0.64        1          3
7     -0.22        1          3
8     -0.71        1          3
9     -0.03        2          1
10    -0.65        2          1
11     0.76        2          1
12     1.77        2          2
13     0.89        2          2
14     0.65        2          2
15    -0.98        2          3
16     0.65        2          3
17    -0.30        2          3

PS burada biraz daha genel bir çözüm bulabilirsiniz


GÜNCELLEME: bazı açıklamalar: IMO bu kodu anlamanın en kolay yolu onu adım adım yürütmeye çalışmaktır:

aşağıdaki satırda, değerleri bir sütun Nkez tekrarlıyoruz; burada N- ilgili listenin uzunluğu:

In [10]: np.repeat(df['trial_num'].values, df[lst_col].str.len())
Out[10]: array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3], dtype=int64)

bu, skaler değerler içeren tüm sütunlar için genelleştirilebilir:

In [11]: pd.DataFrame({
    ...:           col:np.repeat(df[col].values, df[lst_col].str.len())
    ...:           for col in df.columns.drop(lst_col)}
    ...:         )
Out[11]:
    trial_num  subject
0           1        1
1           1        1
2           1        1
3           2        1
4           2        1
5           2        1
6           3        1
..        ...      ...
11          1        2
12          2        2
13          2        2
14          2        2
15          3        2
16          3        2
17          3        2

[18 rows x 2 columns]

kullanarak np.concatenate(), listsütundaki ( samples) tüm değerleri düzleştirebilir ve 1D vektörü alabiliriz:

In [12]: np.concatenate(df[lst_col].values)
Out[12]: array([-1.04, -0.58, -1.32,  0.82, -0.59, -0.34,  0.25,  2.09,  0.12,  0.83, -0.88,  0.68,  0.55, -0.56,  0.65, -0.04,  0.36, -0.31])

tüm bunları bir araya getirmek:

In [13]: pd.DataFrame({
    ...:           col:np.repeat(df[col].values, df[lst_col].str.len())
    ...:           for col in df.columns.drop(lst_col)}
    ...:         ).assign(**{lst_col:np.concatenate(df[lst_col].values)})
Out[13]:
    trial_num  subject  samples
0           1        1    -1.04
1           1        1    -0.58
2           1        1    -1.32
3           2        1     0.82
4           2        1    -0.59
5           2        1    -0.34
6           3        1     0.25
..        ...      ...      ...
11          1        2     0.68
12          2        2     0.55
13          2        2    -0.56
14          2        2     0.65
15          3        2    -0.04
16          3        2     0.36
17          3        2    -0.31

[18 rows x 3 columns]

kullanmak pd.DataFrame()[df.columns], sütunları orijinal sırayla seçtiğimizi garanti edecektir ...


3
Bu kabul edilen cevap olmalı. Şu anda kabul edilen cevap buna kıyasla çok, çok daha yavaş.
irene

1
Bunu nasıl düzeltebilirim: TypeError: 'güvenli' kuralına göre dtype ('float64') dtype ('int64') dizi verileri dökülemiyor
Greg

1
Yığınları aramada tam bir saat içinde bulunan 10+ kişiden, benim için çalışan tek cevap bu. Thanks MaxU 🙏
olisteadman

1
Bunun lst_coltamamen boş bir listeye sahip satırları bıraktığını unutmayın ; Bu satırları tutmak ve onların doldurmak için lst_colbirlikte np.nan, sadece yapabileceği df[lst_col] = df[lst_col].apply(lambda x: x if len(x) > 0 else [np.nan])bu yöntemi kullanmadan önce. Açıkçası .masklisteleri döndürmez, bu nedenle .apply.
Charles Davis

Bu, kabul edilen cevap olması gereken mükemmel bir cevaptır. Her ne kadar bu kara büyü düzeyinde bir cevap olsa da, birincisi, aslında bu adımların ne yaptığına dair bazı açıklamaları takdir ediyorum.
ifly6

129

Beklediğimden biraz daha uzun:

>>> df
                samples  subject  trial_num
0  [-0.07, -2.9, -2.44]        1          1
1   [-1.52, -0.35, 0.1]        1          2
2  [-0.17, 0.57, -0.65]        1          3
3  [-0.82, -1.06, 0.47]        2          1
4   [0.79, 1.35, -0.09]        2          2
5   [1.17, 1.14, -1.79]        2          3
>>>
>>> s = df.apply(lambda x: pd.Series(x['samples']),axis=1).stack().reset_index(level=1, drop=True)
>>> s.name = 'sample'
>>>
>>> df.drop('samples', axis=1).join(s)
   subject  trial_num  sample
0        1          1   -0.07
0        1          1   -2.90
0        1          1   -2.44
1        1          2   -1.52
1        1          2   -0.35
1        1          2    0.10
2        1          3   -0.17
2        1          3    0.57
2        1          3   -0.65
3        2          1   -0.82
3        2          1   -1.06
3        2          1    0.47
4        2          2    0.79
4        2          2    1.35
4        2          2   -0.09
5        2          3    1.17
5        2          3    1.14
5        2          3   -1.79

Sıralı dizin istiyorsanız reset_index(drop=True), sonuca başvurabilirsiniz .

güncelleme :

>>> res = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack()
>>> res = res.reset_index()
>>> res.columns = ['subject','trial_num','sample_num','sample']
>>> res
    subject  trial_num  sample_num  sample
0         1          1           0    1.89
1         1          1           1   -2.92
2         1          1           2    0.34
3         1          2           0    0.85
4         1          2           1    0.24
5         1          2           2    0.72
6         1          3           0   -0.96
7         1          3           1   -2.72
8         1          3           2   -0.11
9         2          1           0   -1.33
10        2          1           1    3.13
11        2          1           2   -0.65
12        2          2           0    0.10
13        2          2           1    0.65
14        2          2           2    0.15
15        2          3           0    0.64
16        2          3           1   -0.10
17        2          3           2   -0.76

Teşekkürler, her öğeyi kendi sütununda almak için başvurmanın ilk adımı bile çok yardımcıdır. Bunu yapmak için biraz farklı bir yol buldum, ancak hala birkaç adım var. Görünüşe göre bu Pandalar yapmak kolay değil!
Marius

1
Mükemmel cevap. Sen değiştirerek biraz kısaltın olabilir df.apply(lambda x: pd.Series(x['samples']),axis=1)ile df.samples.apply(pd.Series).
Dennis Golomazov

1
Okuyucular için not: Bu, performans sorunlarından korkunç bir şekilde muzdariptir. Numpy kullanarak çok daha performanslı bir çözüm için buraya bakın .
cs95

2
örnek sayısı tüm satırlar için aynı olmadığında çözüm nedir?
SarahData

@SarahData Buradadf.explode() gösterildiği gibi kullanın .
cs95

63

Pandalar> = 0.25

Seri ve DataFrame yöntemleri .explode()listeleri ayrı satırlara ayıran bir yöntem tanımlar . Liste benzeri bir sütunun patlaması ile ilgili dokümanlar bölümüne bakın .

df = pd.DataFrame({
    'var1': [['a', 'b', 'c'], ['d', 'e',], [], np.nan], 
    'var2': [1, 2, 3, 4]
})
df
        var1  var2
0  [a, b, c]     1
1     [d, e]     2
2         []     3
3        NaN     4

df.explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
2  NaN     3  # empty list converted to NaN
3  NaN     4  # NaN entry preserved as-is

# to reset the index to be monotonically increasing...
df.explode('var1').reset_index(drop=True)

  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5  NaN     3
6  NaN     4

Bunun ayrıca liste ve skalerlerin karışık sütunlarının yanı sıra boş listeler ve NaN'leri de uygun şekilde işlediğini unutmayın (bu, repeattemelli çözümlerin bir dezavantajıdır ).

Ancak, explodeyalnızca tek bir sütunda (şimdilik) çalıştığını unutmayın .

Not: bir dizgi sütununu patlatmak istiyorsanız, önce bir ayırıcıya bölmeniz, sonra kullanmanız gerekir explode. Bu (çok) ilgili yanıtı görüyorum.


8
Sonunda, Pandalar için bir patlama ()!
Kai

2
en sonunda! Zihin karmaşası! Yukarıdaki @MaxU'dan harika bir cevap ama bu işleri çok daha basit hale getiriyor.
bağımlısı

12

Ayrıca kullanabilir pd.concatve pd.meltbunun için:

>>> objs = [df, pd.DataFrame(df['samples'].tolist())]
>>> pd.concat(objs, axis=1).drop('samples', axis=1)
   subject  trial_num     0     1     2
0        1          1 -0.49 -1.00  0.44
1        1          2 -0.28  1.48  2.01
2        1          3 -0.52 -1.84  0.02
3        2          1  1.23 -1.36 -1.06
4        2          2  0.54  0.18  0.51
5        2          3 -2.18 -0.13 -1.35
>>> pd.melt(_, var_name='sample_num', value_name='sample', 
...         value_vars=[0, 1, 2], id_vars=['subject', 'trial_num'])
    subject  trial_num sample_num  sample
0         1          1          0   -0.49
1         1          2          0   -0.28
2         1          3          0   -0.52
3         2          1          0    1.23
4         2          2          0    0.54
5         2          3          0   -2.18
6         1          1          1   -1.00
7         1          2          1    1.48
8         1          3          1   -1.84
9         2          1          1   -1.36
10        2          2          1    0.18
11        2          3          1   -0.13
12        1          1          2    0.44
13        1          2          2    2.01
14        1          3          2    0.02
15        2          1          2   -1.06
16        2          2          2    0.51
17        2          3          2   -1.35

son olarak, gerekirse ilk üç sütuna göre sıralama yapabilirsiniz.


1
Bu sadece listelerin uzunluğunun ne olacağını önceden biliyorsanız ve / veya hepsi aynı uzunlukta olacaksa çalışır?
Chill2Macht

9

Roman Pekar'ın çözümünü daha iyi anlamak için adım adım çalışmaya çalışarak melt, kafa karıştırıcı istifleme ve dizin sıfırlama işlemlerinden kaçınmak için kullanılan kendi çözümümü buldum . Yine de daha açık bir çözüm olduğunu söyleyemem:

items_as_cols = df.apply(lambda x: pd.Series(x['samples']), axis=1)
# Keep original df index as a column so it's retained after melt
items_as_cols['orig_index'] = items_as_cols.index

melted_items = pd.melt(items_as_cols, id_vars='orig_index', 
                       var_name='sample_num', value_name='sample')
melted_items.set_index('orig_index', inplace=True)

df.merge(melted_items, left_index=True, right_index=True)

Çıktı (Açıkçası orijinal örnekler sütununu şimdi bırakabiliriz):

                 samples  subject  trial_num sample_num  sample
0    [1.84, 1.05, -0.66]        1          1          0    1.84
0    [1.84, 1.05, -0.66]        1          1          1    1.05
0    [1.84, 1.05, -0.66]        1          1          2   -0.66
1    [-0.24, -0.9, 0.65]        1          2          0   -0.24
1    [-0.24, -0.9, 0.65]        1          2          1   -0.90
1    [-0.24, -0.9, 0.65]        1          2          2    0.65
2    [1.15, -0.87, -1.1]        1          3          0    1.15
2    [1.15, -0.87, -1.1]        1          3          1   -0.87
2    [1.15, -0.87, -1.1]        1          3          2   -1.10
3   [-0.8, -0.62, -0.68]        2          1          0   -0.80
3   [-0.8, -0.62, -0.68]        2          1          1   -0.62
3   [-0.8, -0.62, -0.68]        2          1          2   -0.68
4    [0.91, -0.47, 1.43]        2          2          0    0.91
4    [0.91, -0.47, 1.43]        2          2          1   -0.47
4    [0.91, -0.47, 1.43]        2          2          2    1.43
5  [-1.14, -0.24, -0.91]        2          3          0   -1.14
5  [-1.14, -0.24, -0.91]        2          3          1   -0.24
5  [-1.14, -0.24, -0.91]        2          3          2   -0.91

6

Manuel sütun adlandırmalarını önleyen Roman Pekar'ın cevabını arayanlar için:

column_to_explode = 'samples'
res = (df
       .set_index([x for x in df.columns if x != column_to_explode])[column_to_explode]
       .apply(pd.Series)
       .stack()
       .reset_index())
res = res.rename(columns={
          res.columns[-2]:'exploded_{}_index'.format(column_to_explode),
          res.columns[-1]: '{}_exploded'.format(column_to_explode)})

4

En kolay yolun:

  1. samplesSütunu bir DataFrame'e dönüştürme
  2. Orijinal df ile birleştirme
  3. Erime

Burada gösterilmektedir:

    df.samples.apply(lambda x: pd.Series(x)).join(df).\
melt(['subject','trial_num'],[0,1,2],var_name='sample')

        subject  trial_num sample  value
    0         1          1      0  -0.24
    1         1          2      0   0.14
    2         1          3      0  -0.67
    3         2          1      0  -1.52
    4         2          2      0  -0.00
    5         2          3      0  -1.73
    6         1          1      1  -0.70
    7         1          2      1  -0.70
    8         1          3      1  -0.29
    9         2          1      1  -0.70
    10        2          2      1  -0.72
    11        2          3      1   1.30
    12        1          1      2  -0.55
    13        1          2      2   0.10
    14        1          3      2  -0.44
    15        2          1      2   0.13
    16        2          2      2  -1.44
    17        2          3      2   0.73

Bunun sadece işe yaramış olabileceğini belirtmek gerekir, çünkü her deneme aynı sayıda örneğe sahiptir (3). Farklı numune boyutlarındaki denemeler için daha akıllı bir şey gerekebilir.


2

Çok geç cevap ama bunu eklemek istiyorum:

sample_numOP örneğindeki sütunla da ilgilenen vanilya Python kullanan hızlı bir çözüm . 10 milyondan fazla satır ve 28 milyon satırdan oluşan kendi büyük veri setimde bu yalnızca 38 saniye sürüyor. Kabul edilen çözüm, bu miktarda veriyle tamamen bozulur ve memory errorsistemimde 128GB RAM'e sahip bir açıklığa yol açar .

df = df.reset_index(drop=True)
lstcol = df.lstcol.values
lstcollist = []
indexlist = []
countlist = []
for ii in range(len(lstcol)):
    lstcollist.extend(lstcol[ii])
    indexlist.extend([ii]*len(lstcol[ii]))
    countlist.extend([jj for jj in range(len(lstcol[ii]))])
df = pd.merge(df.drop("lstcol",axis=1),pd.DataFrame({"lstcol":lstcollist,"lstcol_num":countlist},
index=indexlist),left_index=True,right_index=True).reset_index(drop=True)

2

Ayrıca çok geç, ama burada Pandalar yoksa benim için iyi çalıştı Karvy1 bir cevap> = 0.25 sürümü: https://stackoverflow.com/a/52511166/10740287

Yukarıdaki örnek için şunu yazabilirsiniz:

data = [(row.subject, row.trial_num, sample) for row in df.itertuples() for sample in row.samples]
data = pd.DataFrame(data, columns=['subject', 'trial_num', 'samples'])

Hız testi:

%timeit data = pd.DataFrame([(row.subject, row.trial_num, sample) for row in df.itertuples() for sample in row.samples], columns=['subject', 'trial_num', 'samples'])

1.33 ms döngü başına ± 74.8 µs (ortalama 7 adım, ortalama 1000 döngü)

%timeit data = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack().reset_index()

Her döngü için 4.9 ms ± 189 µs (ortalama 7 std ± ortalama, her biri 100 döngü)

%timeit data = pd.DataFrame({col:np.repeat(df[col].values, df['samples'].str.len())for col in df.columns.drop('samples')}).assign(**{'samples':np.concatenate(df['samples'].values)})

1.38 ms döngü başına ± 25 µs (ortalama 7 adım, ortalama 7 döngü, her biri 1000 döngü)


1
import pandas as pd
df = pd.DataFrame([{'Product': 'Coke', 'Prices': [100,123,101,105,99,94,98]},{'Product': 'Pepsi', 'Prices': [101,104,104,101,99,99,99]}])
print(df)
df = df.assign(Prices=df.Prices.str.split(',')).explode('Prices')
print(df)

Pandalar> = 0.25 sürümünde bunu deneyin


1
Gerek yok .str.split(',')çünkü Priceszaten bir liste.
Oren
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.