แบ่งสตริงสตริง (explode) pandas dataframe เป็นแถวแยก


200

ฉันมีpandas dataframeสตริงข้อความหนึ่งคอลัมน์ที่มีค่าที่คั่นด้วยเครื่องหมายจุลภาค ฉันต้องการแยกแต่ละฟิลด์ CSV และสร้างแถวใหม่ต่อรายการ (สมมติว่า CSV สะอาดและต้องการแบ่งเพียง ',') ตัวอย่างเช่นaควรเป็น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

จนถึงตอนนี้ฉันได้ลองใช้ฟังก์ชั่นง่าย ๆ หลายอย่าง แต่.applyวิธีการดูเหมือนจะยอมรับเพียงหนึ่งแถวเป็นค่าตอบแทนเมื่อมันถูกใช้กับแกนและฉันไม่สามารถ.transformทำงานได้ ข้อเสนอแนะใด ๆ ที่จะได้รับการชื่นชมมาก!

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

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}])

ฉันรู้ว่าสิ่งนี้จะไม่ทำงานเพราะเราสูญเสีย meta-data ของ DataFrame โดยผ่านจำนวนมาก แต่ควรให้ความรู้สึกกับสิ่งที่ฉันพยายามทำ:

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
โซลูชันอื่น ๆ ในหน้านี้ใช้งานได้ แต่ฉันพบว่าการติดตามสั้น ๆ และมีประสิทธิภาพ stackoverflow.com/questions/27263805/…
desaiankitb

1
สำหรับคนอื่นที่มาที่หน้านี้และมองหาวิธีแก้ปัญหาที่เก็บหลายคอลัมน์ลองดูคำถามนี้: stackoverflow.com/questions/17116814/…
Sos

คำตอบ:


81

เกี่ยวกับบางสิ่งเช่นนี้:

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

จากนั้นคุณต้องเปลี่ยนชื่อคอลัมน์


1
ดูเหมือนว่าจะเป็นไปได้ ขอบคุณสำหรับความช่วยเหลือของคุณ! โดยทั่วไปแล้วมีวิธีที่นิยมใช้ Split-Apply-Combine ที่ Apply คืนค่าดาต้าเฟรมที่ขนาดใด ๆ (แต่สอดคล้องกับชิ้นส่วนทั้งหมด) และรวมเพียงแค่ vstacks DF ที่คืนมา?
Vincent

GroupBy.apply ควรใช้งานได้ (ฉันเพิ่งลองกับต้นแบบ) อย่างไรก็ตามในกรณีนี้คุณไม่จำเป็นต้องผ่านขั้นตอนเพิ่มเติมของการจัดกลุ่มเนื่องจากคุณสร้างข้อมูลตามแถวใช่ไหม
ช้างเธอ

1
ไงพวก. ขออภัยที่จะเข้ามาสายนี้ แต่สงสัยว่าถ้าไม่มีวิธีที่ดีกว่านี้ ฉันพยายามที่จะทดสอบกับ iterrow เป็นครั้งแรกเนื่องจากดูเหมือนว่าตั๋วสำหรับเรื่องนี้ ฉันสับสนด้วยวิธีแก้ปัญหาที่เสนอ เครื่องหมาย "_" หมายถึงอะไร คุณสามารถอธิบายวิธีแก้ปัญหาได้อย่างไร --Thank คุณ
horatio1701d

11
สามารถขยายโซลูชันไปยังมากกว่าสองคอลัมน์ได้หรือไม่?
horatio1701d


146

UPDATE2:ฟังก์ชั่นเวกเตอร์ทั่วไปมากขึ้นซึ่งจะทำงานสำหรับคอลัมน์normalหลายและหลายlist

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

การสาธิต:

หลายlistคอลัมน์ - คอลัมน์ทั้งหมดlistต้องมีองค์ประกอบ # รายการเหมือนกันในแต่ละแถว:

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

การเก็บรักษาค่าดัชนีดั้งเดิม:

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

ติดตั้ง:

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: []}
})

คอลัมน์ 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

ใช้เคล็ดลับเล็ก ๆ นี้เราสามารถแปลงคอลัมน์เหมือน CSV เป็นlistคอลัมน์:

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

อัปเดต: วิธีการทั่วไปแบบเวกเตอร์ (จะใช้ได้กับหลายคอลัมน์ด้วย):

DF ดั้งเดิม:

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

สารละลาย:

ก่อนอื่นเรามาแปลงสตริง CSV เป็นรายการ:

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

ตอนนี้เราสามารถทำสิ่งนี้:

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

คำตอบเก่า:

แรงบันดาลใจจากโซลูชัน @AFinkelsteinฉันต้องการทำให้เป็นแบบทั่วไปมากขึ้นซึ่งสามารถนำไปใช้กับ DF ที่มีคอลัมน์มากกว่าสองคอลัมน์และเร็วเกือบจะเร็วเท่ากับโซลูชันของ 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
เพื่อนถ้าคุณสามารถเปิดการสนทนาใน Git pandas ฉันคิดว่าเราจำเป็นต้องมีฟังก์ชั่น build ในแบบนี้ !!! ฉันได้เห็นคำถามมากมายเกี่ยวกับ unlistify และ unsesting ในดังนั้นสำหรับ pandas
YOBEN_S

วิธีใช้สิ่งนี้สำหรับหลายคอลัมน์ เช่นถ้าฉันมีข้อมูลที่คั่นด้วยเครื่องหมายจุลภาคใน 2 คอลัมน์และต้องการทำตามลำดับหรือไม่
Jaskaran Singh Puri

@JaskaranSinghPuri คุณต้องการแปลงคอลัมน์ CSV ทั้งหมดเป็นรายการแรก
MaxU

1
ไม่เป็นผลมันไม่ทำงานหากองค์ประกอบรายการของคุณเป็นสิ่งอันดับ แต่หลังจากแปลง tuple ทั้งหมดเป็นสตริงแล้วมันก็ใช้งานได้เหมือนมีเสน่ห์!
กุยโด้

2
ดูเหมือนคำอ้อนวอนของ WenBen นั้นถูกได้ยินโดยเหล่าหมีแพนด้าพวกเขาได้ติดตั้ง.explode()วิธีการใน API (ดูคำตอบนี้ด้วย )
cs95

117

หลังจากการทดลองที่เจ็บปวดเพื่อค้นหาสิ่งที่เร็วกว่าคำตอบที่ยอมรับฉันได้รับการทำงาน มันวิ่งเร็วกว่า 100x ในชุดข้อมูลที่ฉันลอง

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

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
การแก้ปัญหานี้ทำงานได้เร็วขึ้นอย่างมีนัยสำคัญและดูเหมือนจะใช้หน่วยความจำน้อยลง
Cyril

1
นี่เป็นวิธีแก้ปัญหา vectorized pandas ที่ดีฉันกำลังมองหามันอยู่ ขอบคุณ!
Dennis Golomazov

เมื่อฉันลองชุดข้อมูลของตัวเองฉันจะได้รับTypeError: object of type 'float' has no len()ในขั้นตอนแรก ( DataFrame(df.var1.str.split(',').tolist()))
user5359531

@ user5359531 ชุดข้อมูลของคุณอาจมีบางส่วนNaNในคอลัมน์นั้นดังนั้นการแทนที่คือb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair

เพียงแค่ fyi นี่เป็นตัวอย่างที่ดีของการแก้ปัญหาด้วยตัวอย่างนี้
hhbilly

46

นี่คือฟังก์ชั่นที่ฉันเขียนสำหรับงานทั่วไปนี้ มันมีประสิทธิภาพมากกว่าSeries/ stackวิธี ลำดับของคอลัมน์และชื่อจะถูกเก็บไว้

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

ด้วยฟังก์ชั่นนี้คำถามเดิมนั้นง่ายเหมือน:

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

1
นี่มันเร็วมาก! ขอบคุณมากสำหรับสิ่งนี้
Anurag N. Sharma

42

นุ่น> = 0.25

วิธีการ Series และ DataFrame กำหนด.explode()วิธีการที่ระเบิดรายการลงในแถวที่แยกต่างหาก ดูที่ส่วนเอกสารเกี่ยวกับการระเบิดคอลัมน์เหมือนรายการรายการ

เนื่องจากคุณมีรายการของสตริงที่คั่นด้วยเครื่องหมายจุลภาคให้แยกสตริงบนเครื่องหมายจุลภาคเพื่อรับรายการองค์ประกอบจากนั้นเรียกexplodeใช้คอลัมน์นั้น

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

โปรดทราบว่าexplodeใช้งานได้ในคอลัมน์เดียวเท่านั้น (สำหรับตอนนี้)


NaNs และรายการที่ว่างเปล่าได้รับการรักษาที่พวกเขาสมควรได้รับโดยที่คุณไม่ต้องกระโดดผ่านห่วงเพื่อทำให้ถูกต้อง

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

นี่เป็นข้อได้เปรียบที่ร้ายแรงเหนือโซลูชันที่ใช้ravel+repeat (ซึ่งไม่สนใจรายการที่ว่างเปล่าอย่างสมบูรณ์และทำให้หายใจไม่ออกบน NaNs)


4
อันนี้ง่ายที่สุดและพอดีที่สุดในกรณีของฉัน! ขอบคุณ!
Isaac Sim

14

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

คุณสามารถทำได้:

>> a=pd.DataFrame({"var1":"a,b,c d,e,f".split(),"var2":[1,2]})
>> s = a.var1.str.split(",").apply(pd.Series, 1).stack()
>> s.index = s.index.droplevel(-1)
>> del a['var1']
>> a.join(s)
   var2 var1
0     1    a
0     1    b
0     1    c
1     2    d
1     2    e
1     2    f

2
ใช้งานได้หลังจากเพิ่มรหัสเปลี่ยนชื่ออีกหนึ่งรายการ s.name = 'var1'
Jesse

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)})

สาธิต

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

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

มาสร้าง dataframe ใหม่dที่มีรายการกัน

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

ความเห็นทั่วไป

ฉันจะใช้np.arangeกับrepeatเพื่อสร้างตำแหน่งดัชนี dataframe ที่ฉันสามารถใช้กับilocที่ฉันสามารถใช้กับ

คำถามที่พบบ่อย

ทำไมฉันไม่ใช้ loc ?

เพราะดัชนีอาจไม่ซ้ำกันและใช้ locจะส่งคืนทุกแถวที่ตรงกับดัชนีที่สอบถาม

ทำไมคุณไม่ใช้ valuesคุณสมบัติและฝานนั้น

เมื่อเรียกใช้valuesถ้าหากดาต้าเฟรมทั้งหมดอยู่ใน "บล็อก" หนึ่งอันหนึ่ง Pandas จะส่งคืนมุมมองของอาร์เรย์ที่เป็น "บล็อก" มิฉะนั้นนุ่นจะต้องเรียงลำดับใหม่ด้วยกัน เมื่อการปูด้วยหินอาเรย์นั้นจะต้องมีรูปแบบเหมือนกัน บ่อยครั้งที่หมายถึงกลับอาร์เรย์กับ dtype objectว่าเป็น โดยใช้ilocแทนการหั่นvaluesคุณลักษณะฉันบรรเทาตัวเองจากการจัดการกับสิ่งนั้น

ทำไมคุณถึงใช้ assign ?

เมื่อฉันใช้ assignโดยใช้ชื่อคอลัมน์เดียวกับที่ฉันกำลังระเบิดฉันจะเขียนทับคอลัมน์ที่มีอยู่และรักษาตำแหน่งไว้ใน dataframe

เหตุใดค่าดัชนีจึงเกิดซ้ำ

โดยอาศัยการใช้งานilocในตำแหน่งซ้ำดัชนีผลลัพธ์แสดงรูปแบบซ้ำ ๆ กัน หนึ่งรายการซ้ำสำหรับแต่ละรายการหรือสตริง
สามารถรีเซ็ตได้ด้วยreset_index(drop=True)


สำหรับสตริง

ฉันไม่ต้องการแยกสตริงก่อนเวลาอันควร ดังนั้นฉันจึงนับการเกิดขึ้นของการsepโต้แย้งโดยสมมติว่าถ้าฉันแยกความยาวของรายการผลลัพธ์จะมากกว่าจำนวนตัวคั่น

จากนั้นผมก็ใช้ว่าsepจะสายแล้วjoinsplit

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)})

สำหรับรายการ

คล้ายกับสตริงยกเว้นว่าฉันไม่จำเป็นต้องนับการเกิดขึ้นsepเพราะมันแบ่งแล้ว

ฉันใช้ Numpy concatenateเพื่อทำรายการติดกัน

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)})


ฉันชอบอันนี้ รัดกุมมากและประสิทธิภาพก็น่าจะดีเช่นกัน แม้ว่าคำถามหนึ่งข้อ: df.iloc [i] เหมือนกับการทำซ้ำแถวของ dataframe หรือมีประสิทธิภาพมากกว่านั้นหรือไม่? ขอบคุณ!
ทิม

7

มีความเป็นไปได้ที่จะแยกและกระจายดาต้าเฟรมโดยไม่ต้องเปลี่ยนโครงสร้างของดาต้าเฟรม

แยกและขยายข้อมูลของคอลัมน์ที่เฉพาะเจาะจง

การป้อนข้อมูล:

    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)

ออก:

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

แก้ไข-1

แยกและขยายแถวสำหรับหลายคอลัมน์

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]

จัดทำดัชนีใหม่ตามคอลัมน์อ้างอิงและจัดเรียงข้อมูลค่าคอลัมน์ด้วยสแต็ก

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

ออก:

                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

ฉันคิดวิธีแก้ปัญหาสำหรับ dataframes ด้วยจำนวนคอลัมน์โดยพลการ (ในขณะที่ยังคงแยกรายการของคอลัมน์ได้ทีละรายการเท่านั้น)

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
ดี แต่ช้าช้าเนื่องจากการแปลง () การเปลี่ยนแปลง: (
MAQ

4

นี่เป็นข้อความที่ค่อนข้างตรงไปตรงมาซึ่งใช้splitวิธีการจาก pandas straccessor แล้วใช้ NumPy เพื่อทำให้แต่ละแถวแบนเป็นอาร์เรย์เดียว

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
นั่นอาจเป็นคำตอบที่สวยงามมาก น่าเสียดายที่มันไม่ได้ปรับขนาดสำหรับคอลัมน์จำนวนมากใช่ไหม
Michael Dorner

3

ฉันดิ้นรนกับประสบการณ์ที่ไม่ได้อยู่ในความทรงจำโดยใช้วิธีการต่าง ๆ ในการระเบิดรายการของฉันดังนั้นฉันจึงจัดทำเกณฑ์มาตรฐานบางอย่างเพื่อช่วยฉันตัดสินใจว่าคำตอบใดสำหรับการอัปโหลด ฉันทดสอบห้าสถานการณ์ด้วยสัดส่วนที่แตกต่างกันของความยาวรายการกับจำนวนรายการ การแบ่งปันผลลัพธ์ด้านล่าง:

เวลา: (น้อยกว่าดีกว่าคลิกเพื่อดูรุ่นใหญ่)

ความเร็ว

การใช้หน่วยความจำสูงสุด: (น้อยกว่าดีกว่า)

การใช้งานหน่วยความจำสูงสุด

บทสรุป :

  • @ คำตอบของ MaxU (อัพเดต 2) ชื่อรหัสต่อกันมีความเร็วที่ดีที่สุดในเกือบทุกกรณีขณะที่ยังคงใช้หน่วยความจำแบบแอบดูอยู่ในระดับต่ำ
  • ดูที่คำตอบของ @ DMulligan (ชื่อรหัสสแต็ค ) หากคุณต้องการประมวลผลแถวจำนวนมากด้วยรายการที่ค่อนข้างเล็กและสามารถเพิ่มหน่วยความจำสูงสุดได้
  • คำตอบของ @ ช้างที่ยอมรับนั้นทำงานได้ดีสำหรับเฟรมข้อมูลที่มีบางแถว แต่มีรายการมาก

รายละเอียดทั้งหมด (ฟังก์ชั่นและรหัสการเปรียบเทียบ) อยู่ในส่วนสำคัญของ GitHubนี้ โปรดทราบว่าปัญหามาตรฐานได้ง่ายขึ้นและไม่รวมถึงการแยกสตริงลงในรายการ - ซึ่งโซลูชั่นส่วนใหญ่ดำเนินการในลักษณะที่คล้ายกัน


เป็นการเปรียบเทียบที่ดี! คุณคิดที่จะโพสต์รหัสที่คุณใช้สำหรับการวางแผนมาตรฐานหรือไม่
MaxU

1
โปรดดูที่ลิงก์นี้: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (รวมอยู่ในคำตอบแล้ว) - IMO อาจจะยาวเกินกว่าที่จะวางทั้งหมดที่นี่
krassowski

2

ขึ้นอยู่กับวิธีแก้ปัญหาที่ยอดเยี่ยมของ @ DMulligan นี่คือฟังก์ชั่นเวกเตอร์ทั่วไป (ไม่มีลูป) ซึ่งแบ่งคอลัมน์ของดาต้าเฟรมออกเป็นหลายแถวและผสานกลับไปเป็นดาต้าเฟรมดั้งเดิม นอกจากนี้ยังใช้change_column_orderฟังก์ชันทั่วไปที่ยอดเยี่ยมจากคำตอบนี้

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)

ตัวอย่าง:

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    

โปรดทราบว่ามันเก็บรักษาดัชนีเดิมและคำสั่งของคอลัมน์ นอกจากนี้ยังทำงานร่วมกับ dataframes ที่มีดัชนีไม่ต่อเนื่อง


2
สิ่งนี้แตกสำหรับฉันงานที่ดี: stackoverflow.com/a/48554655/6672746
Evan

2

การแยกฟังก์ชั่นสตริงสามารถใช้อาร์กิวเมนต์บูลีนตัวเลือก 'ขยาย'

นี่คือวิธีแก้ปัญหาโดยใช้อาร์กิวเมนต์นี้:

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

1

เพิ่งใช้คำตอบที่ดีของ jiln จากด้านบน แต่จำเป็นต้องขยายเพื่อแยกคอลัมน์หลายคอลัมน์ คิดว่าฉันจะแบ่งปัน

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

อัพเกรดคำตอบของ MaxU ด้วยการรองรับ MultiIndex

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

ใช้หนึ่งซับsplit(___, expand=True)และlevelและและnameอาร์กิวเมนต์เพื่อ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

หากคุณต้องการbให้ดูเหมือนกับคำถามคุณสามารถทำได้เพิ่มเติม:

>>> 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

ฉันได้คิดวิธีแก้ไขปัญหาต่อไปนี้แล้ว:

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

โซลูชันอื่นที่ใช้แพ็คเกจการคัดลอกงูหลาม

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

มีคำตอบมากมายที่นี่ แต่ฉันแปลกใจที่ไม่มีใครพูดถึงฟังก์ชั่นการระเบิดในแพนด้า ตรวจสอบลิงค์ด้านล่าง: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

ด้วยเหตุผลบางอย่างฉันไม่สามารถเข้าถึงฟังก์ชั่นนั้นได้ดังนั้นฉันจึงใช้รหัสด้านล่าง:

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

ป้อนคำอธิบายรูปภาพที่นี่

ด้านบนเป็นตัวอย่างข้อมูลของฉัน อย่างที่คุณเห็นผู้คนคอลัมน์มีหลายคนและฉันพยายามจะระเบิดมัน รหัสที่ฉันมอบให้ใช้กับข้อมูลประเภทรายการ ดังนั้นพยายามทำให้ข้อมูลข้อความคั่นด้วยเครื่องหมายจุลภาคของคุณในรูปแบบรายการ นอกจากนี้เนื่องจากรหัสของฉันใช้ฟังก์ชันในตัวจึงเร็วกว่าฟังก์ชั่นกำหนดเอง / นำไปใช้

หมายเหตุ: คุณอาจต้องติดตั้ง pandas_explode ด้วย pip


0

ฉันมีปัญหาที่คล้ายกันวิธีแก้ปัญหาของฉันคือการแปลงชื่อไฟล์ไปเป็นรายการพจนานุกรมก่อนจากนั้นทำการเปลี่ยนแปลง นี่คือฟังก์ชั่น:

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)

ตัวอย่าง:

>>> 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

นอกจากนี้คุณยังสามารถเปลี่ยนฟังก์ชันเล็กน้อยเพื่อรองรับการแยกแถวประเภทรายการ

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