แพนด้า: ฉันจะแยกข้อความในคอลัมน์ออกเป็นหลายแถวได้อย่างไร


137

ฉันกำลังทำงานกับไฟล์ csv ขนาดใหญ่และถัดจากคอลัมน์สุดท้ายมีสตริงข้อความที่ฉันต้องการแยกด้วยตัวคั่นเฉพาะ ฉันสงสัยว่ามีวิธีง่ายๆในการทำเช่นนี้โดยใช้แพนด้าหรืองูหลามหรือไม่?

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

ฉันต้องการแบ่งตามช่องว่าง(' ')และเครื่องหมายทวิภาค(':')ในSeatblocksคอลัมน์ แต่แต่ละเซลล์จะทำให้จำนวนคอลัมน์ต่างกัน ฉันมีฟังก์ชั่นจัดเรียงคอลัมน์ใหม่ให้Seatblocksคอลัมน์อยู่ท้ายแผ่นงาน แต่ฉันไม่แน่ใจว่าต้องทำอะไรจากตรงนั้น ฉันสามารถทำได้ใน excel ด้วยtext-to-columnsฟังก์ชันในตัวและมาโครด่วน แต่ชุดข้อมูลของฉันมีระเบียนมากเกินไปสำหรับ excel ที่จะจัดการ

ในที่สุดฉันต้องการบันทึกเช่นของ John Lennon และสร้างหลายบรรทัดโดยมีข้อมูลจากชุดที่นั่งแต่ละชุดแยกกัน


คำถามที่ดีนี้เกี่ยวข้องกับ FlatMap ในแพนด้าซึ่งปัจจุบันไม่มีอยู่
cdarlint

คำตอบ:


211

สิ่งนี้จะแบ่ง Seatblocks ตามช่องว่างและให้แต่ละแถวเป็นของตัวเอง

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

หรือเพื่อให้แต่ละสตริงคั่นด้วยโคลอนในคอลัมน์ของตัวเอง:

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

นี่เป็นสิ่งที่น่าเกลียดเล็กน้อย แต่อาจมีใครบางคนเข้ามาพร้อมกับวิธีแก้ปัญหาที่สวยกว่า


7
@DanAllan ให้ดัชนีซีรีส์เมื่อคุณสมัคร; พวกเขาจะกลายเป็นชื่อคอลัมน์
Jeff

4
แม้ว่าสิ่งนี้จะตอบคำถาม แต่ก็ควรค่าแก่การกล่าวถึงว่า (อาจ) แยก () สร้างรายการสำหรับแต่ละแถวซึ่งจะทำให้ขนาดของขนาดใหญ่ขึ้นDataFrameอย่างรวดเร็ว ในกรณีของฉันการเรียกใช้รหัสบนตาราง ~ 200M ทำให้เกิดการใช้งานหน่วยความจำ ~ 10G (+ swap ... )
David Nemeskey

1
แม้ว่าฉันไม่แน่ใจว่าเป็นเพราะอะไรsplit()เพราะเพียงแค่reduce()อ่านคอลัมน์ก็เหมือนมีเสน่ห์ ปัญหาอาจอยู่ในstack()...
David Nemeskey

5
ฉันได้รับข้อผิดพลาดNameError: name 'Series' is not definedนี้ ที่ถูกSeriesควรจะมาจากไหน? แก้ไข: ไม่เป็นไรน่าจะเป็นpandas.Seriesเพราะอ้างอิงจากรายการจากpandas
user5359531

2
ใช่ @ user5359531 ฉันfrom pandas import Seriesเพื่อความสะดวก / สั้น
Dan Allan

54

ต่างจากแดนฉันคิดว่าคำตอบของเขาค่อนข้างสวยหรู ... แต่น่าเสียดายที่มันไร้ประสิทธิภาพมากเช่นกัน ดังนั้นเนื่องจากคำถามกล่าวถึง"ไฟล์ csv ขนาดใหญ่"ฉันขอแนะนำให้ลองใช้โซลูชันของ shell Dan:

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

... เทียบกับทางเลือกนี้:

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

... และนี่:

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

อย่างที่สองเพียงแค่ละเว้นจากการจัดสรร 100 000 Series และนี่ก็เพียงพอแล้วที่จะทำให้เร็วขึ้นประมาณ 10 เท่า แต่วิธีที่สามซึ่งค่อนข้างสิ้นเปลืองการโทรไปยัง str.split () อย่างมาก (เรียกว่าหนึ่งครั้งต่อคอลัมน์ต่อแถวดังนั้นมากกว่าสามเท่าสำหรับโซลูชันอื่นสองวิธี) เร็วกว่าครั้งแรกประมาณ40 เท่าเพราะแม้จะหลีกเลี่ยงการรวมรายการ 100,000 รายการ และใช่มันน่าเกลียดเล็กน้อยอย่างแน่นอน ...

แก้ไข: คำตอบนี้แนะนำวิธีใช้ "to_list ()" และเพื่อหลีกเลี่ยงความจำเป็นในการใช้แลมด้า ผลลัพธ์ที่ได้คือ

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

ซึ่งมีประสิทธิภาพมากกว่าโซลูชันที่สามและดูหรูหรากว่ามาก

แก้ไข:ง่ายยิ่งขึ้น

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

ทำงานเกินไปและเกือบมีประสิทธิภาพ

แก้ไข: ง่ายยิ่งขึ้น ! และจัดการกับ NaN (แต่มีประสิทธิภาพน้อยกว่า):

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

ฉันมีปัญหาเล็กน้อยเกี่ยวกับจำนวนหน่วยความจำที่ใช้วิธีนี้และฉันสงสัยว่าคุณสามารถให้คำแนะนำเล็กน้อยได้หรือไม่ ฉันมี DataFrame ที่มีแถว 8000 แถวแต่ละแถวมี 9216 ช่องว่างที่คั่นด้วยจำนวนเต็ม 8 บิต นี่คือประมาณ 75MB แต่เมื่อฉันใช้วิธีแก้ปัญหาสุดท้ายแบบคำต่อคำ Python จะกินหน่วยความจำของฉัน 2GB คุณช่วยชี้ทิศทางของแหล่งข้อมูลที่จะบอกฉันได้ไหมว่าทำไมจึงเป็นเช่นนั้นและฉันจะทำอย่างไรเพื่อหลีกเลี่ยง ขอบคุณ.
castle-bravo

1
คุณมีรายการจำนวนมากและสตริงขนาดเล็กมากซึ่งเป็นกรณีที่เลวร้ายที่สุดสำหรับการใช้หน่วยความจำใน python (และขั้นตอนกลาง ".split (). tolist ()" จะสร้างวัตถุ python ที่บริสุทธิ์) สิ่งที่ฉันอาจจะทำแทนคุณคือการถ่ายโอนข้อมูล DataFrame ไปยังไฟล์จากนั้นเปิดเป็น csv ด้วย read_csv (... , sep = '') แต่เพื่อให้อยู่ในหัวข้อ: วิธีแก้ปัญหาแรก (ร่วมกับวิธีที่สามซึ่งควรจะช้ามาก) อาจเป็นวิธีที่ให้การใช้หน่วยความจำต่ำที่สุดในบรรดา 4 เนื่องจากคุณมีแถวที่ค่อนข้างยาวค่อนข้างน้อย
Pietro Battiston

เฮ้ Pietro ฉันลองใช้คำแนะนำของคุณในการบันทึกเป็นไฟล์และโหลดใหม่แล้วมันก็ใช้ได้ดีทีเดียว ฉันวิ่งเข้าไปในปัญหาบางอย่างเมื่อผมพยายามที่จะทำเช่นนี้ในวัตถุ StringIO, และเป็นทางออกที่ดีในการแก้ไขปัญหาของฉันได้รับการโพสต์ที่นี่
castle-bravo

3
คำแนะนำสุดท้ายของคุณtolist()สมบูรณ์แบบ ในกรณีของฉันฉันต้องการข้อมูลเพียงชิ้นเดียวในรายการและสามารถเพิ่มคอลัมน์เดียวลงใน df ที่มีอยู่ของฉันได้โดยตรงโดยใช้. ix:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
fantabolous

อ่าผมมีปัญหาในการรับการทำงานในตอนแรก - บางสิ่งบางอย่างเกี่ยวกับobect of type 'float' has no len()ซึ่งยุ่งเหยิงจนฉันตระหนักบางแถวของฉันมีในพวกเขาเมื่อเทียบกับNaN 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

อีกวิธีหนึ่งที่คล้ายกันกับการผูกมัดคือการใช้reset_indexและ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

หากในคอลัมน์ไม่ใช่ NaNค่าวิธีแก้ปัญหาที่เร็วที่สุดคือใช้listความเข้าใจร่วมกับตัว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

แต่ถ้าคอลัมน์มีNaNเฉพาะstr.splitกับพารามิเตอร์expand=Trueที่ส่งคืนDataFrame( เอกสารประกอบ ) และอธิบายว่าเหตุใดจึงช้ากว่า:

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

อาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าคุณจำเป็นต้องมีexpand=Trueตัวเลือกpandas.DataFramesที่ใช้งานได้ในขณะที่ใช้.str.split()เช่น
holzkohlengrill

@holzkohlengrill - ขอบคุณสำหรับความคิดเห็นฉันเพิ่มคำตอบ
jezrael

@jezrael ฉันใช้เวลานานมากในการรันโค้ดนี้เป็นที่คาดหวัง ฉันจะทำให้เร็วขึ้นได้อย่างไร ถ้าฉันใส่ไว้ในลูปเช่น: สำหรับ x ใน df [Seablocks] [: 100] เพื่อทำเฉพาะในส่วนย่อยแล้วเชื่อมต่อกับชุดย่อยเหล่านี้จะได้ผลหรือไม่
bernando_vialli

2

อีกวิธีหนึ่งจะเป็นเช่นนี้:

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

1

ยังสามารถใช้ groupby () โดยไม่จำเป็นต้องเข้าร่วมและกองซ้อน ()

ใช้ข้อมูลตัวอย่างข้างต้น:

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

ขอบคุณล่วงหน้า. ฉันจะใช้โค้ดด้านบนได้อย่างไรโดยแบ่งสองคอลัมน์ให้ตรงกัน ตัวอย่าง: 0 31316 เลนนอน, จอห์น 25 F01 300 1: 13: 36: 1,12 1: 13: 37: 1,13 A, B .. ผลลัพธ์ควรเป็น: 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 Aและบรรทัดถัดไป 0 31316 Lennon, John 25 F01 300 1:13:37:1,13 B
Krithi.S

@ Krithi.S ฉันพยายามที่จะเข้าใจคำถาม คุณหมายความว่าคอลัมน์ทั้งสองต้องมีจำนวนสมาชิกเท่ากันหลังจากแยกหรือไม่? ผลลัพธ์ที่คุณคาดหวังสำหรับ 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1,12 1: 13: 37: 1,13 A, B, C คืออะไร?
Ben2018

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.