gấu trúc: Làm cách nào để chia văn bản trong một cột thành nhiều hàng?


135

Tôi đang làm việc với một tệp csv lớn và cột tiếp theo cuối cùng có một chuỗi văn bản mà tôi muốn phân tách bằng một dấu phân cách cụ thể. Tôi đã tự hỏi nếu có một cách đơn giản để làm điều này bằng cách sử dụng gấu trúc hoặc trăn?

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

Tôi muốn phân chia theo khoảng trắng (' ')và sau đó dấu hai chấm (':')trong Seatblockscột, nhưng mỗi ô sẽ dẫn đến một số cột khác nhau. Tôi có một chức năng sắp xếp lại các cột để Seatblockscột ở cuối trang tính, nhưng tôi không chắc phải làm gì từ đó. Tôi có thể làm điều đó trong excel với text-to-columnschức năng tích hợp và macro nhanh, nhưng tập dữ liệu của tôi có quá nhiều bản ghi để excel xử lý.

Cuối cùng, tôi muốn ghi lại những bản ghi của John Lennon và tạo ra nhiều dòng, với thông tin từ mỗi bộ ghế trên một dòng riêng biệt.


câu hỏi tuyệt vời này liên quan đến FlatMap trong gấu trúc, hiện không tồn tại
cdarlint

Câu trả lời:


203

Điều này phân chia các Dây an toàn theo không gian và cung cấp cho mỗi hàng riêng của nó.

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

Hoặc, để cung cấp cho mỗi chuỗi được phân tách bằng dấu hai chấm trong cột riêng của nó:

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

Điều này là một chút xấu xí, nhưng có lẽ ai đó sẽ kêu gọi với một giải pháp đẹp hơn.


7
@DanAllan đưa ra một chỉ mục cho Sê-ri khi bạn đăng ký; họ sẽ trở thành tên cột
Jeff

4
Trong khi điều này trả lời câu hỏi, điều đáng nói là (có lẽ) split () tạo ra một danh sách cho mỗi hàng, làm tăng kích thước của DataFramerất nhanh. Trong trường hợp của tôi, việc chạy mã trên bảng ~ 200M dẫn đến việc sử dụng bộ nhớ ~ 10G (+ trao đổi ...).
David Nemekey

1
Mặc dù tôi không chắc chắn đó là vì split(), bởi vì chỉ đơn giản là reduce()thông qua cột hoạt động như một lá bùa. Vấn đề sau đó có thể nằm ở stack()...
David Nemekey

4
Tôi nhận được lỗi NameError: name 'Series' is not definedcho điều này. nơi nào được Seriescho là đến từ đâu? EDIT: không có gì, nó phải là pandas.Seriesvì nó đang đề cập đến mục từpandas
user5359531

2
Đúng, @ user5359531. Tôi from pandas import Seriescho thuận tiện / ngắn gọn.
Dan Allan

52

Khác với Dan, tôi cho rằng câu trả lời của anh ta khá tao nhã ... nhưng thật không may, nó cũng rất rất kém hiệu quả. Vì vậy, vì câu hỏi đề cập đến "một tệp csv lớn" , hãy để tôi đề nghị thử dùng giải pháp của Dan shell:

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

... so với giải pháp thay thế này:

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

... và điều này:

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

Thứ hai chỉ đơn giản là kiềm chế phân bổ 100 000 Series, và điều này là đủ để làm cho nó nhanh hơn khoảng 10 lần. Nhưng giải pháp thứ ba, phần nào làm lãng phí rất nhiều cuộc gọi đến str.split () (nó được gọi một lần trên mỗi cột trên mỗi hàng, gấp ba lần so với hai giải pháp khác), nhanh hơn khoảng 40 lần so với lần đầu tiên, bởi vì nó thậm chí còn tránh được các danh sách 100 000. Và vâng, nó chắc chắn là một chút xấu xí ...

EDIT: câu trả lời này gợi ý cách sử dụng "to_list ()" và để tránh sự cần thiết của lambda. Kết quả là một cái gì đó như

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

mà thậm chí còn hiệu quả hơn giải pháp thứ ba, và chắc chắn thanh lịch hơn nhiều.

EDIT: đơn giản hơn nữa

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

hoạt động quá, và gần như là hiệu quả.

EDIT: thậm chí đơn giản hơn ! Và xử lý NaN (nhưng kém hiệu quả hơn):

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

Tôi gặp một chút rắc rối với dung lượng bộ nhớ mà phương pháp này tiêu thụ và tôi tự hỏi liệu bạn có thể cho tôi một lời khuyên không. Tôi có một DataFrame chứa khoảng 8000 hàng, mỗi hàng có một chuỗi chứa 9216 số nguyên 8 bit được phân tách bằng dấu cách. Đây là khoảng 75 MB, nhưng khi tôi áp dụng nguyên văn giải pháp cuối cùng, Python ăn hết 2GB bộ nhớ của tôi. Bạn có thể chỉ cho tôi theo hướng của một số nguồn sẽ cho tôi biết lý do tại sao, và tôi có thể làm gì để khắc phục nó? Cảm ơn.
lâu đài-bravo

1
Bạn có rất nhiều danh sách và các chuỗi rất nhỏ, ít nhiều là trường hợp xấu nhất đối với việc sử dụng bộ nhớ trong python (và bước trung gian ".split (). Tolist ()" tạo ra các đối tượng python thuần). Những gì tôi có thể làm ở vị trí của bạn sẽ là kết xuất DataFrame vào một tệp và sau đó mở nó dưới dạng csv với read_csv (..., sep = ''). Nhưng để duy trì chủ đề: giải pháp đầu tiên (cùng với giải pháp thứ ba, tuy nhiên sẽ rất chậm) có thể là giải pháp cung cấp cho bạn mức sử dụng bộ nhớ thấp nhất trong số 4, vì bạn có số lượng hàng tương đối dài.
Pietro Battiston

Này Pietro, tôi đã thử đề xuất của bạn về việc lưu vào một tệp và tải lại, một tệp hoạt động khá tốt. Tôi gặp một số rắc rối khi tôi cố gắng làm điều này trong một đối tượng StringIO và một giải pháp tốt cho vấn đề của tôi đã được đăng ở đây .
lâu đài-bravo

3
Đề nghị cuối cùng của bạn tolist()là hoàn hảo. Trong trường hợp của tôi, tôi chỉ muốn một trong những phần dữ liệu trong danh sách và có thể trực tiếp thêm một cột vào df hiện tại của mình bằng cách sử dụng .ix:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
fantabolous

Ahh, lúc đầu tôi gặp khó khăn khi làm việc này - một cái gì obect of type 'float' has no len()đó gây trở ngại, cho đến khi tôi nhận ra một số hàng của tôi có NaNtrong đó, trái ngược với str.
lùn

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

Một giải pháp tương tự khác với chuỗi là sử dụng reset_indexrename:

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

Nếu trong cột KHÔNG có NaN giá trị, giải pháp nhanh nhất là sử dụng listhiểu với hàm DataFrametạo:

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

Nhưng nếu cột NaNchỉ chứa hoạt động str.splitvới tham số expand=Truetrả về DataFrame( tài liệu ) và nó giải thích tại sao nó chậm hơn:

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

Có lẽ điều đáng nói là bạn nhất thiết cần expand=Truetùy chọn làm việc pandas.DataFramestrong khi sử dụng .str.split()chẳng hạn.
holzkohlengrill

@holzkohlengrill - cảm ơn bạn đã bình luận, tôi thêm nó để trả lời.
jezrael

@jezrael, tôi mất rất nhiều thời gian để thực thi mã này, đó là điều được mong đợi. Làm thế nào chính xác để tôi làm cho nó nhanh hơn? NẾU tôi đặt nó trong một vòng lặp for như: for x in df [Seablocks] [: 100] để chỉ thực hiện nó trên một tập hợp con và sau đó nối vào các tập hợp con này, nó có hoạt động không?
bernando_vialli

2

Một cách tiếp cận khác sẽ như thế này:

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

1

Cũng có thể sử dụng groupby () mà không cần tham gia và stack ().

Sử dụng dữ liệu ví dụ trê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

Cảm ơn trước. Làm thế nào tôi có thể sử dụng mã trên bằng cách chia hai cột một cách rõ ràng. Ví dụ: 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1.12 1: 13: 37: 1.13 A, B .. Kết quả sẽ là: 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 Avà dòng tiếp theo 0 31316 Lennon, John 25 F01 300 1:13:37:1,13 B
Krithi.S

@ Krithi.S, tôi cố gắng hiểu câu hỏi. Bạn có nghĩa là hai cột phải có cùng số lượng thành viên sau khi chia tách? Kết quả mong đợi của bạn cho 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1.12 1: 13: 37: 1.13 A, B, C là gì?
Ben2018

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.