Tách (nổ) nhập chuỗi chuỗi dữ liệu gấu trúc để phân tách các hàng


200

Tôi có một pandas dataframecột trong đó một chuỗi các chuỗi văn bản chứa các giá trị được phân tách bằng dấu phẩy. Tôi muốn tách từng trường CSV và tạo một hàng mới cho mỗi mục nhập (giả sử rằng CSV sạch và chỉ cần được chia trên ','). Ví dụ: anên trở thành b:

In [7]: a
Out[7]: 
    var1  var2
0  a,b,c     1
1  d,e,f     2

In [8]: b
Out[8]: 
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Cho đến nay, tôi đã thử nhiều hàm đơn giản khác nhau, nhưng .applyphương thức dường như chỉ chấp nhận một hàng là giá trị trả về khi nó được sử dụng trên một trục và tôi không thể .transformlàm việc. Bất kỳ đề xuất sẽ được nhiều đánh giá cao!

Dữ liệu ví dụ:

from pandas import DataFrame
import numpy as np
a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
b = DataFrame([{'var1': 'a', 'var2': 1},
               {'var1': 'b', 'var2': 1},
               {'var1': 'c', 'var2': 1},
               {'var1': 'd', 'var2': 2},
               {'var1': 'e', 'var2': 2},
               {'var1': 'f', 'var2': 2}])

Tôi biết điều này sẽ không hoạt động vì chúng tôi mất dữ liệu meta DataFrame bằng cách vượt qua khó khăn, nhưng nó sẽ cho bạn cảm giác về những gì tôi đã cố gắng thực hiện:

def fun(row):
    letters = row['var1']
    letters = letters.split(',')
    out = np.array([row] * len(letters))
    out['var1'] = letters
a['idx'] = range(a.shape[0])
z = a.groupby('idx')
z.transform(fun)

2
các giải pháp khác trên trang này đang hoạt động nhưng tôi thấy sau một cách ngắn gọn và hiệu quả. stackoverflow.com/questions/27263805/
Mạnh

1
Đối với những người khác đến trang này và tìm kiếm một giải pháp giữ nhiều cột, hãy xem câu hỏi này: stackoverflow.com/questions/17116814/ trên
Sos

Câu trả lời:


81

Còn những thứ như thế này thì sao:

In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))              
                    for _, row in a.iterrows()]).reset_index()
Out[55]: 
  index  0
0     a  1
1     b  1
2     c  1
3     d  2
4     e  2
5     f  2

Sau đó, bạn chỉ cần đổi tên các cột


1
Có vẻ như điều này sẽ làm việc. Cảm ơn bạn đã giúp đỡ! Tuy nhiên, nói chung, có một cách tiếp cận ưa thích đối với Split-Apply-Combine trong đó Áp dụng trả về một khung dữ liệu có kích thước tùy ý (nhưng phù hợp với tất cả các khối) và Kết hợp chỉ làm thay đổi các DF được trả về?
Vincent

GroupBy.apply nên hoạt động (Tôi vừa thử nó với chủ). Tuy nhiên, trong trường hợp này, bạn không thực sự cần phải thực hiện thêm bước phân nhóm vì bạn đang tạo dữ liệu theo hàng phải không?
Chang She

1
Này các cậu. Xin lỗi để nhảy vào đây quá muộn nhưng tự hỏi nếu không có giải pháp tốt hơn cho việc này. Tôi đang cố gắng thử nghiệm với iterrow lần đầu tiên vì dường như đó là tấm vé cho việc này. Tôi cũng bối rối bởi giải pháp được đề xuất. "_" Đại diện cho cái gì? Bạn có thể giải thích làm thế nào các giải pháp hoạt động? - Cảm ơn bạn
horatio1701d

11
Giải pháp có thể được mở rộng đến hơn hai cột không?
horatio1701d

1
vui lòng kiểm tra phương pháp véc tơ này ...
MaxU

146

CẬP NHẬT2: hàm vectorized chung hơn, sẽ hoạt động cho nhiều normalvà nhiều listcột

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    # create "exploded" DF
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

Bản giới thiệu:

Nhiều listcột - tất cả listcác cột phải có cùng # phần tử trong mỗi hàng:

In [134]: df
Out[134]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2         []            []
2   12     3     [1, 2]      [cc, dd]
3   13     4         []            []

In [135]: explode(df, ['num','text'], fill_value='')
Out[135]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2
4   12     3   1   cc
5   12     3   2   dd
6   13     4

bảo toàn các giá trị chỉ mục gốc:

In [136]: explode(df, ['num','text'], fill_value='', preserve_index=True)
Out[136]:
   aaa  myid num text
0   10     1   1   aa
0   10     1   2   bb
0   10     1   3   cc
1   11     2
2   12     3   1   cc
2   12     3   2   dd
3   13     4

Thiết lập:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [], 2: [1, 2], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: [], 2: ['cc', 'dd'], 3: []}
})

Cột CSV:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

sử dụng mẹo nhỏ này, chúng tôi có thể chuyển đổi cột giống như CSV sang listcột:

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

CẬP NHẬT: cách tiếp cận véc tơ chung (cũng sẽ hoạt động cho nhiều cột):

DF gốc:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

Giải pháp:

trước tiên hãy chuyển đổi chuỗi CSV thành danh sách:

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

Bây giờ chúng ta có thể làm điều này:

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

Câu trả lời của OLD:

Lấy cảm hứng từ giải pháp @AFinkelstein , tôi muốn làm cho nó khái quát hơn một chút có thể áp dụng cho DF với hơn hai cột và nhanh, gần như nhanh như giải pháp của AFinkelstein):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

7
anh bạn, nếu bạn có thể mở một cuộc thảo luận trong gấu trúc Git, tôi nghĩ chúng ta cần một chức năng xây dựng như thế này !!! Tôi đã thấy rất nhiều câu hỏi về việc không niêm yết và hủy bỏ trong SO cho gấu trúc
YOBEN_S

Làm thế nào để sử dụng điều này cho nhiều cột. Giống như nếu tôi có dữ liệu được phân tách bằng dấu phẩy trong 2 cột và muốn thực hiện theo trình tự?
Jaskaran Singh Puri

@JaskaranSinghPuri, trước tiên bạn muốn chuyển đổi tất cả các cột CSV thành danh sách.
MaxU

1
Không thường xuyên, nó không hoạt động nếu các thành phần danh sách của bạn là bộ dữ liệu. Nhưng sau khi chuyển đổi toàn bộ tuple thành chuỗi, nó hoạt động như một bùa mê!
Guido

2
Có vẻ như lời cầu xin của WenBen đã được các vị thần gấu trúc nghe thấy, họ đã cài đặt một .explode()phương thức vào API (cũng xem câu trả lời này ).
cs95

117

Sau khi thử nghiệm đau đớn để tìm ra một cái gì đó nhanh hơn câu trả lời được chấp nhận, tôi đã làm điều này để làm việc. Nó chạy nhanh hơn khoảng 100 lần trên tập dữ liệu mà tôi đã thử.

Nếu ai đó biết một cách để làm cho điều này thanh lịch hơn, bằng mọi cách xin vui lòng sửa đổi mã của tôi. Tôi không thể tìm thấy cách hoạt động mà không đặt các cột khác mà bạn muốn giữ làm chỉ mục và sau đó đặt lại chỉ mục và đặt lại tên cho các cột, nhưng tôi tưởng tượng có một cái gì đó khác hoạt động.

b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack()
b = b.reset_index()[[0, 'var2']] # var1 variable is currently labeled 0
b.columns = ['var1', 'var2'] # renaming var1

2
Giải pháp này hoạt động nhanh hơn đáng kể và dường như sử dụng ít bộ nhớ hơn
cyril

1
Đây là một giải pháp gấu trúc vector đẹp, tôi đã tìm kiếm điều đó. Cảm ơn!
Dennis Golomazov

Khi tôi thử điều này trên tập dữ liệu của riêng mình, tôi tiếp tục nhận được TypeError: object of type 'float' has no len()ở bước đầu tiên ( DataFrame(df.var1.str.split(',').tolist()))
user5359531

@ user5359531 tập dữ liệu của bạn có thể có một số NaNtrong cột đó, vì vậy thay thế làb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair

Chỉ cần FYI đây là một tốt đẹp viết lên của giải pháp này với ví dụ.
hhbilly

46

Đây là một chức năng tôi đã viết cho nhiệm vụ chung này. Nó hiệu quả hơn so với Series/ stackphương thức. Thứ tự cột và tên được giữ lại.

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column's values
    keep : bool
        whether to retain the presplit value as it's own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df

Với chức năng này, câu hỏi ban đầu đơn giản như:

tidy_split(a, 'var1', sep=',')

1
Đây là nhanh chóng phồng rộp! Cảm ơn rất nhiều vì chuyện này.
Anurag N. Sharma

42

Gấu trúc> = 0,25

Các phương thức sê-ri và DataFrame xác định một .explode()phương thức khám phá danh sách thành các hàng riêng biệt. Xem phần tài liệu về Phát nổ cột giống như danh sách .

Vì bạn có một danh sách các chuỗi được phân tách bằng dấu phẩy, hãy tách chuỗi trên dấu phẩy để lấy danh sách các phần tử, sau đó gọi explodevào cột đó.

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

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Lưu ý rằng explodechỉ hoạt động trên một cột duy nhất (bây giờ).


NaN và danh sách trống nhận được điều trị mà họ xứng đáng mà không cần bạn phải nhảy qua vòng để có được điều đó đúng.

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

df['var1'].str.split(',')

0    [d, e, f]
1           []
2          NaN

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    d     1
0    e     1
0    f     1
1          2  # empty list entry becomes empty string after exploding 
2  NaN     3  # NaN left un-touched

Đây là một lợi thế nghiêm trọng so với các giải pháp dựa trên ravel+repeat (bỏ qua hoàn toàn danh sách trống và bị sặc trên NaN).


4
Đây là dễ nhất và phù hợp nhất trong trường hợp của tôi! cảm ơn!
Isaac Sim


14

TL; DR

import pandas as pd
import numpy as np

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

Trình diễn

explode_str(a, 'var1', ',')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Hãy tạo một khung dữ liệu mới dcó danh sách

d = a.assign(var1=lambda d: d.var1.str.split(','))

explode_list(d, 'var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Nhận xét chung

Tôi sẽ sử dụng np.arangevới repeatsản xuất dataframe vị trí chỉ số mà tôi có thể sử dụng với iloc.

Câu hỏi thường gặp

Tại sao tôi không sử dụng loc?

Bởi vì chỉ mục có thể không phải là duy nhất và sử dụng loc sẽ trả về mọi hàng khớp với chỉ mục được truy vấn.

Tại sao bạn không sử dụng valuesthuộc tính và cắt nó?

Khi gọi values, nếu toàn bộ khung dữ liệu nằm trong một "khối" gắn kết, Pandas sẽ trả về một khung nhìn của mảng đó là "khối". Nếu không, Pandas sẽ phải lắp ghép một mảng mới. Khi cobbled, mảng đó phải là một dtype thống nhất. Thông thường điều đó có nghĩa là trả về một mảng với dtype object. Bằng cách sử dụng ilocthay vì cắt valuesthuộc tính, tôi làm giảm bớt việc phải đối phó với điều đó.

Tại sao bạn sử dụng assign?

Khi tôi sử dụng assign dụng cùng tên cột mà tôi đang phát nổ, tôi ghi đè lên cột hiện có và duy trì vị trí của nó trong khung dữ liệu.

Tại sao các giá trị chỉ số lặp lại?

Nhờ sử dụng iloctrên các vị trí lặp lại, chỉ số kết quả hiển thị cùng một mẫu lặp lại. Một lần lặp lại cho mỗi phần tử danh sách hoặc chuỗi.
Điều này có thể được thiết lập lại vớireset_index(drop=True)


Đối với chuỗi

Tôi không muốn phải phân chia chuỗi sớm. Vì vậy, thay vào đó tôi đếm các lần xuất hiện của sepđối số giả sử rằng nếu tôi được chia, độ dài của danh sách kết quả sẽ nhiều hơn một số lần phân tách.

Sau đó tôi sử dụng nó sepđể joinchuỗi split.

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

Đối với danh sách

Tương tự như đối với các chuỗi ngoại trừ tôi không cần phải đếm số lần xuất hiện của sep vì nó đã bị tách.

Tôi sử dụng Numpy concatenateđể kết hợp các danh sách lại với nhau.

import pandas as pd
import numpy as np

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})


Tôi thích cái này. Thực sự súc tích và hiệu suất nên cũng thực sự tốt. Một câu hỏi mặc dù: df.iloc [i] có giống như lặp lại các hàng của khung dữ liệu hay nó hiệu quả hơn thế? Cảm ơn!
Tim

7

Có khả năng phân tách và phát nổ khung dữ liệu mà không thay đổi cấu trúc của khung dữ liệu

Tách và mở rộng dữ liệu của các cột cụ thể

Đầu vào:

    var1    var2
0   a,b,c   1
1   d,e,f   2



#Get the indexes which are repetative with the split 
temp = df['var1'].str.split(',')
df = df.reindex(df.index.repeat(temp.apply(len)))


df['var1'] = np.hstack(temp)

Ngoài:

    var1    var2
0   a   1
0   b   1
0   c   1
1   d   2
1   e   2
1   f   2

Chỉnh sửa-1

Tách và mở rộng các hàng cho nhiều cột

Filename    RGB                                             RGB_type
0   A   [[0, 1650, 6, 39], [0, 1691, 1, 59], [50, 1402...   [r, g, b]
1   B   [[0, 1423, 16, 38], [0, 1445, 16, 46], [0, 141...   [r, g, b]

Lập chỉ mục lại dựa trên cột tham chiếu và căn chỉnh thông tin giá trị cột với ngăn xếp

df = df.reindex(df.index.repeat(df['RGB_type'].apply(len)))
df = df.groupby('Filename').apply(lambda x:x.apply(lambda y: pd.Series(y.iloc[0])))
df.reset_index(drop=True).ffill()

Ngoài:

                Filename    RGB_type    Top 1 colour    Top 1 frequency Top 2 colour    Top 2 frequency
    Filename                            
 A  0       A   r   0   1650    6   39
    1       A   g   0   1691    1   59
    2       A   b   50  1402    49  187
 B  0       B   r   0   1423    16  38
    1       B   g   0   1445    16  46
    2       B   b   0   1419    16  39

5

Tôi đã đưa ra một giải pháp cho các datafram với số lượng cột tùy ý (trong khi vẫn chỉ tách riêng một mục của một cột).

def splitDataFrameList(df,target_column,separator):
    ''' df = dataframe to split,
    target_column = the column containing the values to split
    separator = the symbol used to perform the split

    returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
    The values in the other columns are duplicated across the newly divided rows.
    '''
    def splitListToRows(row,row_accumulator,target_column,separator):
        split_row = row[target_column].split(separator)
        for s in split_row:
            new_row = row.to_dict()
            new_row[target_column] = s
            row_accumulator.append(new_row)
    new_rows = []
    df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
    new_df = pandas.DataFrame(new_rows)
    return new_df

2
tốt đẹp nhưng đáng buồn là chậm vì chuyển đổi todict () này :(
MAQ

4

Đây là một thông điệp khá đơn giản sử dụng splitphương pháp từ gấu trúcstr và sau đó sử dụng NumPy để làm phẳng mỗi hàng thành một mảng duy nhất.

Các giá trị tương ứng được lấy bằng cách lặp lại cột không phân chia số lần chính xác với np.repeat.

var1 = df.var1.str.split(',', expand=True).values.ravel()
var2 = np.repeat(df.var2.values, len(var1) / len(df))

pd.DataFrame({'var1': var1,
              'var2': var2})

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

1
Đó có thể là một câu trả lời rất hay. Thật không may, nó không mở rộng cho nhiều cột, phải không?
Michael D Corner

3

Tôi đã phải vật lộn với trải nghiệm hết bộ nhớ bằng nhiều cách khác nhau để làm nổ danh sách của mình vì vậy tôi đã chuẩn bị một số điểm chuẩn để giúp tôi quyết định câu trả lời nào cần nâng cấp. Tôi đã thử nghiệm năm kịch bản với các tỷ lệ khác nhau của độ dài danh sách với số lượng danh sách. Chia sẻ kết quả dưới đây:

Thời gian: (ít hơn là tốt hơn, nhấp để xem phiên bản lớn)

Tốc độ

Sử dụng bộ nhớ tối đa: (ít hơn là tốt hơn)

Sử dụng bộ nhớ cao điểm

Kết luận :

  • Câu trả lời của @ MaxU (bản cập nhật 2), tên mã concatenate cung cấp tốc độ tốt nhất trong hầu hết mọi trường hợp, trong khi vẫn giữ mức sử dụng bộ nhớ lén thấp,
  • xem câu trả lời của @ DMulligan ( ngăn xếp tên mã ) nếu bạn cần xử lý nhiều hàng với danh sách tương đối nhỏ và có thể tăng bộ nhớ tối đa,
  • câu trả lời được chấp nhận của @ Chang hoạt động tốt cho các khung dữ liệu có một vài hàng nhưng danh sách rất lớn.

Chi tiết đầy đủ (chức năng và mã điểm chuẩn) có trong ý chính GitHub này . Xin lưu ý rằng vấn đề điểm chuẩn đã được đơn giản hóa và không bao gồm việc chia chuỗi vào danh sách - điều mà hầu hết các giải pháp thực hiện theo cách tương tự.


Đẹp so sánh! Bạn có phiền khi đăng một mã, mà bạn đã sử dụng để vẽ các điểm chuẩn không?
MaxU

1
Vui lòng xem liên kết này: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (đã có trong câu trả lời) - IMO sẽ hơi lâu để dán tất cả vào đây.
krassowski

2

Dựa trên giải pháp tuyệt vời của @ DMulligan , đây là hàm vectơ chung (không có vòng lặp) phân tách một cột của khung dữ liệu thành nhiều hàng và hợp nhất nó trở lại khung dữ liệu gốc. Nó cũng sử dụng một change_column_orderchức năng chung tuyệt vời từ câu trả lời này .

def change_column_order(df, col_name, index):
    cols = df.columns.tolist()
    cols.remove(col_name)
    cols.insert(index, col_name)
    return df[cols]

def split_df(dataframe, col_name, sep):
    orig_col_index = dataframe.columns.tolist().index(col_name)
    orig_index_name = dataframe.index.name
    orig_columns = dataframe.columns
    dataframe = dataframe.reset_index()  # we need a natural 0-based index for proper merge
    index_col_name = (set(dataframe.columns) - set(orig_columns)).pop()
    df_split = pd.DataFrame(
        pd.DataFrame(dataframe[col_name].str.split(sep).tolist())
        .stack().reset_index(level=1, drop=1), columns=[col_name])
    df = dataframe.drop(col_name, axis=1)
    df = pd.merge(df, df_split, left_index=True, right_index=True, how='inner')
    df = df.set_index(index_col_name)
    df.index.name = orig_index_name
    # merge adds the column to the last place, so we need to move it back
    return change_column_order(df, col_name, orig_col_index)

Thí dụ:

df = pd.DataFrame([['a:b', 1, 4], ['c:d', 2, 5], ['e:f:g:h', 3, 6]], 
                  columns=['Name', 'A', 'B'], index=[10, 12, 13])
df
        Name    A   B
    10   a:b     1   4
    12   c:d     2   5
    13   e:f:g:h 3   6

split_df(df, 'Name', ':')
    Name    A   B
10   a       1   4
10   b       1   4
12   c       2   5
12   d       2   5
13   e       3   6
13   f       3   6    
13   g       3   6    
13   h       3   6    

Lưu ý rằng nó giữ nguyên chỉ mục và thứ tự ban đầu của các cột. Nó cũng hoạt động với các dataframes có chỉ mục không tuần tự.


2
cái này đã bẻ khóa cái này cho tôi, công việc tuyệt vời: stackoverflow.com/a/48554655/6672746
Evan

2

Việc phân chia hàm chuỗi có thể lấy một đối số boolean tùy chọn 'mở rộng'.

Đây là một giải pháp sử dụng đối số này:

(a.var1
  .str.split(",",expand=True)
  .set_index(a.var2)
  .stack()
  .reset_index(level=1, drop=True)
  .reset_index()
  .rename(columns={0:"var1"}))

1

Chỉ cần sử dụng câu trả lời tuyệt vời của jiln từ trên, nhưng cần mở rộng để chia nhiều cột. Nghĩ tôi sẽ chia sẻ.

def splitDataFrameList(df,target_column,separator):
''' df = dataframe to split,
target_column = the column containing the values to split
separator = the symbol used to perform the split

returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
The values in the other columns are duplicated across the newly divided rows.
'''
def splitListToRows(row, row_accumulator, target_columns, separator):
    split_rows = []
    for target_column in target_columns:
        split_rows.append(row[target_column].split(separator))
    # Seperate for multiple columns
    for i in range(len(split_rows[0])):
        new_row = row.to_dict()
        for j in range(len(split_rows)):
            new_row[target_columns[j]] = split_rows[j][i]
        row_accumulator.append(new_row)
new_rows = []
df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
new_df = pd.DataFrame(new_rows)
return new_df

1

nâng cấp câu trả lời của MaxU với sự hỗ trợ Multi Index

def explode(df, lst_cols, fill_value='', preserve_index=False):
    """
    usage:
        In [134]: df
        Out[134]:
           aaa  myid        num          text
        0   10     1  [1, 2, 3]  [aa, bb, cc]
        1   11     2         []            []
        2   12     3     [1, 2]      [cc, dd]
        3   13     4         []            []

        In [135]: explode(df, ['num','text'], fill_value='')
        Out[135]:
           aaa  myid num text
        0   10     1   1   aa
        1   10     1   2   bb
        2   10     1   3   cc
        3   11     2
        4   12     3   1   cc
        5   12     3   2   dd
        6   13     4
    """
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)

    # if original index is MultiIndex build the dataframe from the multiindex
    # create "exploded" DF
    if isinstance(df.index, pd.MultiIndex):
        res = res.reindex(
            index=pd.MultiIndex.from_tuples(
                res.index,
                names=['number', 'color']
            )
    )
    return res

1

One-liner sử dụng split(___, expand=True)levelnameđối số reset_index():

>>> b = a.var1.str.split(',', expand=True).set_index(a.var2).stack().reset_index(level=0, name='var1')
>>> b
   var2 var1
0     1    a
1     1    b
2     1    c
0     2    d
1     2    e
2     2    f

Nếu bạn cần btrông giống hệt như trong câu hỏi, bạn cũng có thể làm:

>>> b = b.reset_index(drop=True)[['var1', 'var2']]
>>> b
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

0

Tôi đã đưa ra giải pháp sau đây cho vấn đề này:

def iter_var1(d):
    for _, row in d.iterrows():
        for v in row["var1"].split(","):
            yield (v, row["var2"])

new_a = DataFrame.from_records([i for i in iter_var1(a)],
        columns=["var1", "var2"])

0

Một giải pháp khác sử dụng gói sao chép python

import copy
new_observations = list()
def pandas_explode(df, column_to_explode):
    new_observations = list()
    for row in df.to_dict(orient='records'):
        explode_values = row[column_to_explode]
        del row[column_to_explode]
        if type(explode_values) is list or type(explode_values) is tuple:
            for explode_value in explode_values:
                new_observation = copy.deepcopy(row)
                new_observation[column_to_explode] = explode_value
                new_observations.append(new_observation) 
        else:
            new_observation = copy.deepcopy(row)
            new_observation[column_to_explode] = explode_values
            new_observations.append(new_observation) 
    return_df = pd.DataFrame(new_observations)
    return return_df

df = pandas_explode(df, column_name)

0

Có rất nhiều câu trả lời ở đây nhưng tôi ngạc nhiên không ai đề cập đến chức năng nổ trong gấu trúc. Kiểm tra các liên kết dưới đây: https://pandas.pydata.org/pandas-docs/urdy/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Vì một số lý do tôi không thể truy cập chức năng đó, vì vậy tôi đã sử dụng đoạn mã dưới đây:

import pandas_explode
pandas_explode.patch()
df_zlp_people_cnt3 = df_zlp_people_cnt2.explode('people')

nhập mô tả hình ảnh ở đây

Trên đây là một mẫu dữ liệu của tôi. Như bạn có thể thấy cột người có hàng loạt người, và tôi đang cố gắng làm nổ tung nó. Mã tôi đã đưa ra hoạt động cho dữ liệu loại danh sách. Vì vậy, hãy cố gắng để có được dữ liệu văn bản được phân tách bằng dấu phẩy của bạn thành định dạng danh sách. Ngoài ra, do mã của tôi sử dụng các hàm dựng sẵn, nó nhanh hơn nhiều so với các hàm tùy chỉnh / áp dụng.

Lưu ý: Bạn có thể cần cài đặt pandas_explode với pip.


0

Tôi gặp vấn đề tương tự, giải pháp của tôi là chuyển đổi khung dữ liệu thành danh sách từ điển trước, sau đó thực hiện chuyển đổi. Đây là chức năng:

import copy
import re

def separate_row(df, column_name):
    ls = []
    for row_dict in df.to_dict('records'):
        for word in re.split(',', row_dict[column_name]):
            row = copy.deepcopy(row_dict)
            row[column_name]=word
            ls(row)
    return pd.DataFrame(ls)

Thí dụ:

>>> from pandas import DataFrame
>>> import numpy as np
>>> a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
>>> a
    var1  var2
0  a,b,c     1
1  d,e,f     2
>>> separate_row(a, "var1")
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Bạn cũng có thể thay đổi chức năng một chút để hỗ trợ tách các loại loại danh sách.

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.