pandalar: Bir sütundaki metni birden çok satıra nasıl bölerim?


135

Ben büyük bir csv dosyası ile çalışıyorum ve son sütunun yanındaki belirli bir sınırlayıcı tarafından bölmek istediğiniz bir metin dizesi vardır. Pandalar veya python kullanarak bunu yapmanın basit bir yolu olup olmadığını merak ediyordum?

CustNum  CustomerName     ItemQty  Item   Seatblocks                 ItemExt
32363    McCartney, Paul      3     F04    2:218:10:4,6                   60
31316    Lennon, John        25     F01    1:13:36:1,12 1:13:37:1,13     300

Ben boşluk bölmek istiyorum (' ')ve daha sonra kolon (':')içinde Seatblockskolona, ancak her hücre sütun farklı bir sayıyla sonuçlanacaktır. SeatblocksSütun, sayfanın sonunda olacak şekilde sütunları yeniden düzenlemek için bir işlevim var , ama oradan ne yapacağımdan emin değilim. Ben yerleşik text-to-columnsfonksiyonu ve hızlı bir makro ile excel yapabilirim , ama benim veri seti excel işlemek için çok fazla kayıt var.

Nihayetinde, John Lennon'un kayıtlarını almak ve her bir koltuk grubundan gelen bilgileri ayrı bir satırda olmak üzere birden fazla satır oluşturmak istiyorum.


Bu büyük soru şu anda mevcut olmayan pandalardaki FlatMap ile ilgilidir
cdarlint

Yanıtlar:


203

Bu, Seatblocks'u uzaya böler ve her birine kendi satırını verir.

In [43]: df
Out[43]: 
   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0    32363  McCartney, Paul        3  F04               2:218:10:4,6       60
1    31316     Lennon, John       25  F01  1:13:36:1,12 1:13:37:1,13      300

In [44]: s = df['Seatblocks'].str.split(' ').apply(Series, 1).stack()

In [45]: s.index = s.index.droplevel(-1) # to line up with df's index

In [46]: s.name = 'Seatblocks' # needs a name to join

In [47]: s
Out[47]: 
0    2:218:10:4,6
1    1:13:36:1,12
1    1:13:37:1,13
Name: Seatblocks, dtype: object

In [48]: del df['Seatblocks']

In [49]: df.join(s)
Out[49]: 
   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    32363  McCartney, Paul        3  F04       60  2:218:10:4,6
1    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13

Veya, iki nokta üstüste ayrılmış dizeyi kendi sütununda vermek için:

In [50]: df.join(s.apply(lambda x: Series(x.split(':'))))
Out[50]: 
   CustNum     CustomerName  ItemQty Item  ItemExt  0    1   2     3
0    32363  McCartney, Paul        3  F04       60  2  218  10   4,6
1    31316     Lennon, John       25  F01      300  1   13  36  1,12
1    31316     Lennon, John       25  F01      300  1   13  37  1,13

Bu biraz çirkin, ama belki birisi daha güzel bir çözümle girecek.


7
@DanAllan uyguladığınızda Serilere bir dizin verir; sütun adları olacaklar
Jeff

4
Bu soruya cevap verirken, (muhtemelen) split () öğesinin her satır için bir liste oluşturduğunu ve bu da DataFrameçok hızlı bir şekilde patladığını belirtmek gerekir . Benim durumumda, kodu ~ 200M bir tablo üzerinde çalıştırmak ~ 10G bellek (+ takas ...) kullanımı ile sonuçlandı.
David Nemeskey

1
Neden olduğundan emin değilim split(), çünkü sadece reduce()sütun boyunca bir cazibe gibi çalışır. O zaman sorun yatıyor olabilir stack()...
David Nemeskey

4
Bunun için bir hata alıyorum NameError: name 'Series' is not defined. nereden Seriesgelmeli? EDIT: nevermind, pandas.Seriesçünkü o öğeden söz ediyor olmalıdırpandas
user5359531

2
Evet, @ user5359531. from pandas import SeriesKolaylık / kısalık için ben .
Dan Allan

52

Dan'dan farklı olarak, cevabını oldukça zarif buluyorum ... ama maalesef çok da verimsiz. Bu nedenle, "büyük bir csv dosyası" ndan bahsettiğim sorudan , Dan'ın çözümünü bir kabukta denememe izin verin:

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df['col'].apply(lambda x : pd.Series(x.split(' '))).head()"

... bu alternatifle karşılaştırıldığında:

time python -c "import pandas as pd;
from scipy import array, concatenate;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(concatenate(df['col'].apply( lambda x : [x.split(' ')]))).head()"

... ve bu:

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))).head()"

İkincisi sadece 100 000 Serisi ayırmaktan kaçınır ve bu yaklaşık 10 kat daha hızlı hale getirmek için yeterlidir. Ancak, str.split () 'e ironik bir şekilde çok fazla çağrı harcayan üçüncü çözüm (satır başına sütun başına bir kez çağrılır, bu nedenle diğer iki çözümden üç kat daha fazladır), yaklaşık 40 kattır. daha hızlıdır, çünkü 100.000 listeyi örnek almayı bile önler. Ve evet, kesinlikle biraz çirkin ...

EDIT: Bu cevap "to_list ()" nasıl kullanılacağını ve lambda ihtiyacı önlemek için önerir. Sonuç şuna benzer

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(df.col.str.split().tolist()).head()"

Üçüncü çözümden daha verimli ve kesinlikle çok daha zarif.

EDIT: daha da basit

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(list(df.col.str.split())).head()"

çalışır ve neredeyse aynı derecede verimlidir.

DÜZENLEME: daha da basit ! Ve NaN'leri işler (ancak daha az verimli):

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df.col.str.split(expand=True).head()"

Bu yöntemin kullandığı bellek miktarı ile ilgili biraz sorun yaşıyorum ve bana biraz tavsiyede bulunup bulunamayacağınızı merak ediyorum. Her biri 9216 alanı ayrılmış 8 bitlik tamsayılar içeren bir dize ile yaklaşık 8000 satır içeren bir DataFrame var. Bu yaklaşık 75 MB, ancak son çözümü kelimesi kelimesine uyguladığımda, Python belleğimin 2GB'ını yer. Bana bunun neden olduğunu ve etrafta dolaşmak için ne yapabileceğimi söyleyen bir kaynağın yönünü gösterebilir misiniz? Teşekkürler.
castle-bravo

1
Çok sayıda listeniz ve çok küçük dizeleriniz var, bu da python'da bellek kullanımı için aşağı yukarı en kötü durum (ve ara adım.. Muhtemelen senin yerinde ne yapacağım DataFrame bir dosyaya dökmek ve daha sonra read_csv (..., sep = '') ile csv olarak açmak olacaktır. Ancak konu üzerinde kalmak için: göreceli olarak az sayıda görece uzun sıraya sahip olduğunuz için ilk çözüm (ancak oldukça yavaş olması gereken üçüncü çözüm ile birlikte) 4 arasında en düşük bellek kullanımını sunan çözüm olabilir.
Pietro Battiston

Hey Pietro, bir dosyaya kaydetme ve yeniden yükleme önerisini denedim, oldukça iyi çalıştı. Bunu bir StringIO nesnesinde yapmaya çalıştığımda bir sorunla karşılaştım ve sorunum için güzel bir çözüm burada gönderildi .
castle-bravo

3
Son öneriniz tolist()mükemmel. Benim durumumda sadece listedeki veri parçalarından birini istedim ve .ix kullanarak mevcut df'ye doğrudan tek bir sütun ekleyebildim:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
fantabolous

Hakkında bir şey - Ahh, sorun ilk başta işe bu alma başlamıştı obect of type 'float' has no len()ben fark edene kadar, baffling edildiği bazı benim satırların vardı NaNaksine, onları str.
dwanderson

14
import pandas as pd
import numpy as np

df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
                   'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
                   'ItemExt': {0: 60, 1: 300}, 
                   'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
                   'CustNum': {0: 32363, 1: 31316}, 
                   'Item': {0: 'F04', 1: 'F01'}}, 
                    columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt'])

print (df)
   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0    32363  McCartney, Paul        3  F04               2:218:10:4,6       60
1    31316     Lennon, John       25  F01  1:13:36:1,12 1:13:37:1,13      300

Zincirleme ile benzer bir başka çözüm kullanım reset_indexve rename:

print (df.drop('Seatblocks', axis=1)
             .join
             (
             df.Seatblocks
             .str
             .split(expand=True)
             .stack()
             .reset_index(drop=True, level=1)
             .rename('Seatblocks')           
             ))

   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    32363  McCartney, Paul        3  F04       60  2:218:10:4,6
1    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13

Sütunda DEĞİL NaN değerler varsa, en hızlı çözüm yapıcı listile birlikte anlamadır DataFrame:

df = pd.DataFrame(['a b c']*100000, columns=['col'])

In [141]: %timeit (pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))))
1 loop, best of 3: 211 ms per loop

In [142]: %timeit (pd.DataFrame(df.col.str.split().tolist()))
10 loops, best of 3: 87.8 ms per loop

In [143]: %timeit (pd.DataFrame(list(df.col.str.split())))
10 loops, best of 3: 86.1 ms per loop

In [144]: %timeit (df.col.str.split(expand=True))
10 loops, best of 3: 156 ms per loop

In [145]: %timeit (pd.DataFrame([ x.split() for x in df['col'].tolist()]))
10 loops, best of 3: 54.1 ms per loop

Ancak sütun NaNyalnızca döndüren ( dokümantasyon ) str.splitparametresiyle çalışıyorsa ve neden daha yavaş olduğunu açıklar:expand=TrueDataFrame

df = pd.DataFrame(['a b c']*10, columns=['col'])
df.loc[0] = np.nan
print (df.head())
     col
0    NaN
1  a b c
2  a b c
3  a b c
4  a b c

print (df.col.str.split(expand=True))
     0     1     2
0  NaN  None  None
1    a     b     c
2    a     b     c
3    a     b     c
4    a     b     c
5    a     b     c
6    a     b     c
7    a     b     c
8    a     b     c
9    a     b     c

Belki de , örneğin kullanırken expand=Truebirlikte çalışma seçeneğine ihtiyacınız olduğunu belirtmek gerekir . pandas.DataFrames.str.split()
holzkohlengrill

@holzkohlengrill - yorumunuz için teşekkür ederim, cevaplamak için ekliyorum.
jezrael

@jezrael, bu kodun yürütülmesi çok uzun sürüyor, beklenen bir şey. Tam olarak nasıl daha hızlı yaparım? Ben bir for döngüsü gibi: for df [Seablocks] [: 100] için sadece bir alt kümede yapmak ve daha sonra bu alt kümeleri birleştirmek için koyarsam, bu işe yarayacak mı?
bernando_vialli

2

Başka bir yaklaşım şöyle olacaktır:

temp = df['Seatblocks'].str.split(' ')
data = data.reindex(data.index.repeat(temp.apply(len)))
data['new_Seatblocks'] = np.hstack(temp)

1

Birleştirme ve istifleme () gerektirmeyen groupby () işlevini de kullanabilir.

Yukarıdaki örnek verileri kullanın:

import pandas as pd
import numpy as np


df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
                   'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
                   'ItemExt': {0: 60, 1: 300}, 
                   'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
                   'CustNum': {0: 32363, 1: 31316}, 
                   'Item': {0: 'F04', 1: 'F01'}}, 
                    columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt']) 
print(df)

   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0  32363    McCartney, Paul  3        F04  2:218:10:4,6               60     
1  31316    Lennon, John     25       F01  1:13:36:1,12 1:13:37:1,13  300  


#first define a function: given a Series of string, split each element into a new series
def split_series(ser,sep):
    return pd.Series(ser.str.cat(sep=sep).split(sep=sep)) 
#test the function, 
split_series(pd.Series(['a b','c']),sep=' ')
0    a
1    b
2    c
dtype: object

df2=(df.groupby(df.columns.drop('Seatblocks').tolist()) #group by all but one column
          ['Seatblocks'] #select the column to be split
          .apply(split_series,sep=' ') # split 'Seatblocks' in each group
         .reset_index(drop=True,level=-1).reset_index()) #remove extra index created

print(df2)
   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13
2    32363  McCartney, Paul        3  F04       60  2:218:10:4,6

Şimdiden teşekkürler. Nasıl iki sütunu birbirinden ayırarak yukarıdaki kodu kullanabilirsiniz. Örneğin: 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1,12 1: 13: 37: 1,13 A, B. .. Sonuç şu olmalıdır: 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 Ave sonraki satır 0 31316 Lennon, John 25 F01 300 1:13:37:1,13 B
Krithi.S

@ Krithi.S, soruyu anlamaya çalışıyorum. Bölme işleminden sonra iki sütunun aynı sayıda üyeye sahip olması gerektiği anlamına mı geliyor? 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1,12 1: 13: 37: 1,13 A, B, C için beklenen sonuçlar nedir?
Ben2018

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.