วิธีการที่ผิดปกติ (ระเบิด) คอลัมน์ใน DataFrame แพนด้า


119

ฉันมี DataFrame ต่อไปนี้โดยที่หนึ่งในคอลัมน์เป็นวัตถุ (เซลล์ประเภทรายการ):

df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]})
df
Out[458]: 
   A       B
0  1  [1, 2]
1  2  [1, 2]

ผลลัพธ์ที่ฉันคาดหวังคือ:

   A  B
0  1  1
1  1  2
3  2  1
4  2  2

ฉันควรทำอย่างไรเพื่อให้บรรลุเป้าหมายนี้


คำถามที่เกี่ยวข้อง

แพนด้า: เมื่อเนื้อหาของเซลล์เป็นรายการให้สร้างแถวสำหรับแต่ละองค์ประกอบในรายการ

คำถามและคำตอบที่ดี แต่จัดการเพียงคอลัมน์เดียวที่มีรายการ (ในคำตอบของฉันฟังก์ชั่น self-def จะใช้งานได้กับหลายคอลัมน์นอกจากนี้คำตอบที่ยอมรับคือใช้เวลามากที่สุดapplyซึ่งไม่แนะนำให้ตรวจสอบข้อมูลเพิ่มเติมเมื่อฉันต้องการ การใช้แพนด้าใช้ () ในรหัสของฉัน? )


5
ที่เกี่ยวข้องไม่ทดสอบสตริง: stackoverflow.com/q/48197234/4909087
cs95

5
คู่ของบทความที่เกี่ยวข้อง: ที่นี่ , ที่นี่ , ที่นี่ , ที่นี่ ...
Cleb

คำตอบ:


184

ฉันรู้ว่าobjectคอลัมน์typeทำให้ข้อมูลแปลงด้วยpandasฟังก์ชันได้ยาก เมื่อฉันได้รับข้อมูลเช่นนี้สิ่งแรกที่ควรคำนึงถึงก็คือการ "แบน" หรือทำให้คอลัมน์ไม่สมบูรณ์

ฉันใช้pandasและpythonฟังก์ชั่นสำหรับคำถามประเภทนี้ หากคุณกังวลเกี่ยวกับความเร็วของโซลูชันข้างต้นโปรดตรวจสอบคำตอบของ user3483203 เนื่องจากใช้งานอยู่numpyและเวลาส่วนใหญ่numpyจะเร็วกว่า ฉันแนะนำCpythonและnumbaถ้าความเร็วมีความสำคัญ


วิธีที่ 0 [แพนด้า> = 0.25]
เริ่มจากแพนด้า 0.25หากคุณต้องการระเบิดเพียงคอลัมน์เดียวคุณสามารถใช้pandas.DataFrame.explodeฟังก์ชัน:

df.explode('B')

       A  B
    0  1  1
    1  1  2
    0  2  1
    1  2  2

กำหนดดาต้าเฟรมด้วยช่องว่างlistหรือNaNคอลัมน์ รายการที่ว่างเปล่าจะไม่ทำให้เกิดปัญหา แต่NaNจะต้องกรอกข้อมูลด้วยไฟล์list

df = pd.DataFrame({'A': [1, 2, 3, 4],'B': [[1, 2], [1, 2], [], np.nan]})
df.B = df.B.fillna({i: [] for i in df.index})  # replace NaN with []
df.explode('B')

   A    B
0  1    1
0  1    2
1  2    1
1  2    2
2  3  NaN
3  4  NaN

วิธีที่ 1
apply + pd.Series (เข้าใจง่าย แต่ไม่แนะนำในแง่ของประสิทธิภาพ)

df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0:'B'})
Out[463]: 
   A  B
0  1  1
1  1  2
0  2  1
1  2  2

วิธีที่ 2
ใช้repeatกับDataFrameตัวสร้างสร้างดาต้าเฟรมของคุณใหม่ (มีประสิทธิภาพดี แต่ไม่ดีในหลายคอลัมน์)

df=pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)})
df
Out[465]: 
   A  B
0  1  1
0  1  2
1  2  1
1  2  2

วิธีที่ 2.1
เช่นนอกเหนือจาก A เรามี A.1 ..... An ถ้าเรายังคงใช้วิธีการ ( วิธีที่ 2 ) ข้างต้นจะเป็นการยากที่เราจะสร้างคอลัมน์ใหม่ทีละคอลัมน์

วิธีแก้ไข: joinหรือmergeด้วยindexคอลัมน์เดียวที่ 'ไม่สำคัญที่สุด'

s=pd.DataFrame({'B':np.concatenate(df.B.values)},index=df.index.repeat(df.B.str.len()))
s.join(df.drop('B',1),how='left')
Out[477]: 
   B  A
0  1  1
0  2  1
1  1  2
1  2  2

หากคุณต้องการลำดับคอลัมน์เหมือนเดิมreindexทุกประการให้เพิ่มในตอนท้าย

s.join(df.drop('B',1),how='left').reindex(columns=df.columns)

วิธีที่ 3
สร้างไฟล์list

pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns)
Out[488]: 
   A  B
0  1  1
1  1  2
2  2  1
3  2  2

หากมีมากกว่าสองคอลัมน์ให้ใช้

s=pd.DataFrame([[x] + [z] for x, y in zip(df.index,df.B) for z in y])
s.merge(df,left_on=0,right_index=True)
Out[491]: 
   0  1  A       B
0  0  1  1  [1, 2]
1  0  2  1  [1, 2]
2  1  1  2  [1, 2]
3  1  2  2  [1, 2]

วิธีที่ 4
โดยใช้reindex หรือloc

df.reindex(df.index.repeat(df.B.str.len())).assign(B=np.concatenate(df.B.values))
Out[554]: 
   A  B
0  1  1
0  1  2
1  2  1
1  2  2

#df.loc[df.index.repeat(df.B.str.len())].assign(B=np.concatenate(df.B.values))

วิธีที่ 5
เมื่อรายการมีเฉพาะค่าที่ไม่ซ้ำกัน:

df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]]})
from collections import ChainMap
d = dict(ChainMap(*map(dict.fromkeys, df['B'], df['A'])))
pd.DataFrame(list(d.items()),columns=df.columns[::-1])
Out[574]: 
   B  A
0  1  1
1  2  1
2  3  2
3  4  2

วิธีที่ 6
ใช้numpyเพื่อประสิทธิภาพสูง:

newvalues=np.dstack((np.repeat(df.A.values,list(map(len,df.B.values))),np.concatenate(df.B.values)))
pd.DataFrame(data=newvalues[0],columns=df.columns)
   A  B
0  1  1
1  1  2
2  2  1
3  2  2

วิธีที่ 7
โดยใช้ฟังก์ชันพื้นฐานitertools cycleและchain: โซลูชัน Python บริสุทธิ์เพื่อความสนุกสนาน

from itertools import cycle,chain
l=df.values.tolist()
l1=[list(zip([x[0]], cycle(x[1])) if len([x[0]]) > len(x[1]) else list(zip(cycle([x[0]]), x[1]))) for x in l]
pd.DataFrame(list(chain.from_iterable(l1)),columns=df.columns)
   A  B
0  1  1
1  1  2
2  2  1
3  2  2

การสรุปเป็นหลายคอลัมน์

df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]],'C':[[1,2],[3,4]]})
df
Out[592]: 
   A       B       C
0  1  [1, 2]  [1, 2]
1  2  [3, 4]  [3, 4]

ฟังก์ชั่นป้องกันตัวเอง:

def unnesting(df, explode):
    idx = df.index.repeat(df[explode[0]].str.len())
    df1 = pd.concat([
        pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1)
    df1.index = idx

    return df1.join(df.drop(explode, 1), how='left')

        
unnesting(df,['B','C'])
Out[609]: 
   B  C  A
0  1  1  1
0  2  2  1
1  3  3  2
1  4  4  2

คอลัมน์ฉลาด Unnesting

วิธีการทั้งหมดข้างต้นกำลังพูดถึงการระเบิดและระเบิดในแนวตั้งหากคุณต้องการใช้รายการแนวนอนตรวจสอบกับตัวpd.DataFrameสร้าง

df.join(pd.DataFrame(df.B.tolist(),index=df.index).add_prefix('B_'))
Out[33]: 
   A       B       C  B_0  B_1
0  1  [1, 2]  [1, 2]    1    2
1  2  [3, 4]  [3, 4]    3    4

อัปเดตฟังก์ชัน

def unnesting(df, explode, axis):
    if axis==1:
        idx = df.index.repeat(df[explode[0]].str.len())
        df1 = pd.concat([
            pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1)
        df1.index = idx

        return df1.join(df.drop(explode, 1), how='left')
    else :
        df1 = pd.concat([
                         pd.DataFrame(df[x].tolist(), index=df.index).add_prefix(x) for x in explode], axis=1)
        return df1.join(df.drop(explode, 1), how='left')

ผลการทดสอบ

unnesting(df, ['B','C'], axis=0)
Out[36]: 
   B0  B1  C0  C1  A
0   1   2   1   2  1
1   3   4   3   4  2

43

ตัวเลือกที่ 1

หากรายการย่อยทั้งหมดในคอลัมน์อื่นมีความยาวเท่ากันnumpyสามารถเป็นตัวเลือกที่มีประสิทธิภาพได้ที่นี่:

vals = np.array(df.B.values.tolist())    
a = np.repeat(df.A, vals.shape[1])

pd.DataFrame(np.column_stack((a, vals.ravel())), columns=df.columns)

   A  B
0  1  1
1  1  2
2  2  1
3  2  2

ทางเลือกที่ 2

หากรายการย่อยมีความยาวต่างกันคุณต้องมีขั้นตอนเพิ่มเติม:

vals = df.B.values.tolist()
rs = [len(r) for r in vals]    
a = np.repeat(df.A, rs)

pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns)

   A  B
0  1  1
1  1  2
2  2  1
3  2  2

ตัวเลือก 3

ฉันได้ลองใช้การสรุปสิ่งนี้เพื่อทำให้Nคอลัมน์แบนราบและคอลัมน์ไทล์Mฉันจะทำงานในภายหลังเพื่อทำให้มีประสิทธิภาพมากขึ้น:

df = pd.DataFrame({'A': [1,2,3], 'B': [[1,2], [1,2,3], [1]],
                   'C': [[1,2,3], [1,2], [1,2]], 'D': ['A', 'B', 'C']})

   A          B          C  D
0  1     [1, 2]  [1, 2, 3]  A
1  2  [1, 2, 3]     [1, 2]  B
2  3        [1]     [1, 2]  C

def unnest(df, tile, explode):
    vals = df[explode].sum(1)
    rs = [len(r) for r in vals]
    a = np.repeat(df[tile].values, rs, axis=0)
    b = np.concatenate(vals.values)
    d = np.column_stack((a, b))
    return pd.DataFrame(d, columns = tile +  ['_'.join(explode)])

unnest(df, ['A', 'D'], ['B', 'C'])

    A  D B_C
0   1  A   1
1   1  A   2
2   1  A   1
3   1  A   2
4   1  A   3
5   2  B   1
6   2  B   2
7   2  B   3
8   2  B   1
9   2  B   2
10  3  C   1
11  3  C   1
12  3  C   2

ฟังก์ชั่น

def wen1(df):
    return df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0: 'B'})

def wen2(df):
    return pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)})

def wen3(df):
    s = pd.DataFrame({'B': np.concatenate(df.B.values)}, index=df.index.repeat(df.B.str.len()))
    return s.join(df.drop('B', 1), how='left')

def wen4(df):
    return pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns)

def chris1(df):
    vals = np.array(df.B.values.tolist())
    a = np.repeat(df.A, vals.shape[1])
    return pd.DataFrame(np.column_stack((a, vals.ravel())), columns=df.columns)

def chris2(df):
    vals = df.B.values.tolist()
    rs = [len(r) for r in vals]
    a = np.repeat(df.A.values, rs)
    return pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns)

การกำหนดเวลา

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from timeit import timeit

res = pd.DataFrame(
       index=['wen1', 'wen2', 'wen3', 'wen4', 'chris1', 'chris2'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000],
       dtype=float
)

for f in res.index:
    for c in res.columns:
        df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]})
        df = pd.concat([df]*c)
        stmt = '{}(df)'.format(f)
        setp = 'from __main__ import df, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=50)

ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N")
ax.set_ylabel("time (relative)")

ประสิทธิภาพ

ใส่คำอธิบายภาพที่นี่


3
น่าสนใจดีจะได้ทราบการเปรียบเทียบกับdf.explodeวิธีการใหม่
Paul Rougieux


9

ทางเลือกหนึ่งคือการใช้สูตร meshgridเหนือแถวของคอลัมน์เพื่อไม่ให้เกิดปัญหา:

import numpy as np
import pandas as pd


def unnest(frame, explode):
    def mesh(values):
        return np.array(np.meshgrid(*values)).T.reshape(-1, len(values))

    data = np.vstack(mesh(row) for row in frame[explode].values)
    return pd.DataFrame(data=data, columns=explode)


df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]})
print(unnest(df, ['A', 'B']))  # base
print()

df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [3, 4]], 'C': [[1, 2], [3, 4]]})
print(unnest(df, ['A', 'B', 'C']))  # multiple columns
print()

df = pd.DataFrame({'A': [1, 2, 3], 'B': [[1, 2], [1, 2, 3], [1]],
                   'C': [[1, 2, 3], [1, 2], [1, 2]], 'D': ['A', 'B', 'C']})

print(unnest(df, ['A', 'B']))  # uneven length lists
print()
print(unnest(df, ['D', 'B']))  # different types
print()

เอาต์พุต

   A  B
0  1  1
1  1  2
2  2  1
3  2  2

   A  B  C
0  1  1  1
1  1  2  1
2  1  1  2
3  1  2  2
4  2  3  3
5  2  4  3
6  2  3  4
7  2  4  4

   A  B
0  1  1
1  1  2
2  2  1
3  2  2
4  2  3
5  3  1

   D  B
0  A  1
1  A  2
2  B  1
3  B  2
4  B  3
5  C  1

3

5 เซ็นต์ของฉัน:

df[['B', 'B2']] = pd.DataFrame(df['B'].values.tolist())

df[['A', 'B']].append(df[['A', 'B2']].rename(columns={'B2': 'B'}),
                      ignore_index=True)

และอีก 5

df[['B1', 'B2']] = pd.DataFrame([*df['B']]) # if values.tolist() is too boring

(pd.wide_to_long(df.drop('B', 1), 'B', 'A', '')
 .reset_index(level=1, drop=True)
 .reset_index())

ทั้งสองส่งผลให้เหมือนกัน

   A  B
0  1  1
1  2  1
2  1  2
3  2  2

3

การตั้งค่าปัญหา

สมมติว่ามีหลายคอลัมน์ที่มีวัตถุที่มีความยาวต่างกันอยู่ภายใน

df = pd.DataFrame({
    'A': [1, 2],
    'B': [[1, 2], [3, 4]],
    'C': [[1, 2], [3, 4, 5]]
})

df

   A       B          C
0  1  [1, 2]     [1, 2]
1  2  [3, 4]  [3, 4, 5]

เมื่อความยาวเท่ากันจึงเป็นเรื่องง่ายที่เราจะสมมติว่าองค์ประกอบที่แตกต่างกันเกิดขึ้นพร้อมกันและควรจะ "ซิป" เข้าด้วยกัน

   A       B          C
0  1  [1, 2]     [1, 2]  # Typical to assume these should be zipped [(1, 1), (2, 2)]
1  2  [3, 4]  [3, 4, 5]

อย่างไรก็ตามสมมติฐานนี้ถูกท้าทายเมื่อเราเห็นวัตถุที่มีความยาวแตกต่างกันเราควร "ซิป" หรือไม่ถ้าเป็นเช่นนั้นเราจะจัดการกับส่วนเกินในวัตถุอย่างใดอย่างหนึ่งได้อย่างไร หรือบางทีเราอาจต้องการผลคูณจากวัตถุทั้งหมด สิ่งนี้จะได้รับอย่างรวดเร็ว แต่อาจเป็นสิ่งที่ต้องการ

   A       B          C
0  1  [1, 2]     [1, 2]
1  2  [3, 4]  [3, 4, 5]  # is this [(3, 3), (4, 4), (None, 5)]?

หรือ

   A       B          C
0  1  [1, 2]     [1, 2]
1  2  [3, 4]  [3, 4, 5]  # is this [(3, 3), (3, 4), (3, 5), (4, 3), (4, 4), (4, 5)]

ฟังก์ชั่น

ฟังก์ชันนี้จัดการzipหรือproductยึดตามพารามิเตอร์อย่างสง่างามและถือว่าเป็นไปzipตามความยาวของวัตถุที่ยาวที่สุดด้วยzip_longest

from itertools import zip_longest, product

def xplode(df, explode, zipped=True):
    method = zip_longest if zipped else product

    rest = {*df} - {*explode}

    zipped = zip(zip(*map(df.get, rest)), zip(*map(df.get, explode)))
    tups = [tup + exploded
     for tup, pre in zipped
     for exploded in method(*pre)]

    return pd.DataFrame(tups, columns=[*rest, *explode])[[*df]]

ซิป

xplode(df, ['B', 'C'])

   A    B  C
0  1  1.0  1
1  1  2.0  2
2  2  3.0  3
3  2  4.0  4
4  2  NaN  5

สินค้า

xplode(df, ['B', 'C'], zipped=False)

   A  B  C
0  1  1  1
1  1  1  2
2  1  2  1
3  1  2  2
4  2  3  3
5  2  3  4
6  2  3  5
7  2  4  3
8  2  4  4
9  2  4  5

การตั้งค่าใหม่

เปลี่ยนแปลงตัวอย่างเล็กน้อย

df = pd.DataFrame({
    'A': [1, 2],
    'B': [[1, 2], [3, 4]],
    'C': 'C',
    'D': [[1, 2], [3, 4, 5]],
    'E': [('X', 'Y', 'Z'), ('W',)]
})

df

   A       B  C          D          E
0  1  [1, 2]  C     [1, 2]  (X, Y, Z)
1  2  [3, 4]  C  [3, 4, 5]       (W,)

ซิป

xplode(df, ['B', 'D', 'E'])

   A    B  C    D     E
0  1  1.0  C  1.0     X
1  1  2.0  C  2.0     Y
2  1  NaN  C  NaN     Z
3  2  3.0  C  3.0     W
4  2  4.0  C  4.0  None
5  2  NaN  C  5.0  None

สินค้า

xplode(df, ['B', 'D', 'E'], zipped=False)

    A  B  C  D  E
0   1  1  C  1  X
1   1  1  C  1  Y
2   1  1  C  1  Z
3   1  1  C  2  X
4   1  1  C  2  Y
5   1  1  C  2  Z
6   1  2  C  1  X
7   1  2  C  1  Y
8   1  2  C  1  Z
9   1  2  C  2  X
10  1  2  C  2  Y
11  1  2  C  2  Z
12  2  3  C  3  W
13  2  3  C  4  W
14  2  3  C  5  W
15  2  4  C  3  W
16  2  4  C  4  W
17  2  4  C  5  W

2

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

MultiIndex ควรเป็นวิธีที่ง่ายกว่าในการเขียนและมีการแสดงที่ใกล้เคียงกับวิธี numpy

น่าแปลกที่วิธีการทำความเข้าใจในการนำไปใช้งานของฉันมีประสิทธิภาพที่ดีที่สุด

def stack(df):
    return df.set_index(['A', 'C']).B.apply(pd.Series).stack()


def comprehension(df):
    return pd.DataFrame([x + [z] for x, y in zip(df[['A', 'C']].values.tolist(), df.B) for z in y])


def multiindex(df):
    return pd.DataFrame(np.concatenate(df.B.values), index=df.set_index(['A', 'C']).index.repeat(df.B.str.len()))


def array(df):
    return pd.DataFrame(
        np.column_stack((
            np.repeat(df[['A', 'C']].values, df.B.str.len(), axis=0),
            np.concatenate(df.B.values)
        ))
    )


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from timeit import timeit

res = pd.DataFrame(
    index=[
        'stack',
        'comprehension',
        'multiindex',
        'array',
    ],
    columns=[1000, 2000, 5000, 10000, 20000, 50000],
    dtype=float
)

for f in res.index:
    for c in res.columns:
        df = pd.DataFrame({'A': list('abc'), 'C': list('def'), 'B': [['g', 'h', 'i'], ['j', 'k'], ['l']]})
        df = pd.concat([df] * c)
        stmt = '{}(df)'.format(f)
        setp = 'from __main__ import df, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=20)

ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N")
ax.set_ylabel("time (relative)")

ประสิทธิภาพ

เวลาสัมพัทธ์ของแต่ละวิธี


2

ฉันสรุปปัญหาเล็กน้อยเพื่อให้ใช้ได้กับคอลัมน์เพิ่มเติม

สรุปวิธีแก้ปัญหาของฉัน:

In[74]: df
Out[74]: 
    A   B             C             columnD
0  A1  B1  [C1.1, C1.2]                D1
1  A2  B2  [C2.1, C2.2]  [D2.1, D2.2, D2.3]
2  A3  B3            C3        [D3.1, D3.2]

In[75]: dfListExplode(df,['C','columnD'])
Out[75]: 
    A   B     C columnD
0  A1  B1  C1.1    D1
1  A1  B1  C1.2    D1
2  A2  B2  C2.1    D2.1
3  A2  B2  C2.1    D2.2
4  A2  B2  C2.1    D2.3
5  A2  B2  C2.2    D2.1
6  A2  B2  C2.2    D2.2
7  A2  B2  C2.2    D2.3
8  A3  B3    C3    D3.1
9  A3  B3    C3    D3.2

ตัวอย่างที่สมบูรณ์:

การระเบิดจริงจะดำเนินการใน 3 บรรทัด ส่วนที่เหลือคือเครื่องสำอาง (การระเบิดหลายคอลัมน์การจัดการสตริงแทนที่จะเป็นรายการในคอลัมน์การระเบิด ... )

import pandas as pd
import numpy as np

df=pd.DataFrame( {'A': ['A1','A2','A3'],
                  'B': ['B1','B2','B3'],
                  'C': [ ['C1.1','C1.2'],['C2.1','C2.2'],'C3'],
                  'columnD': [ 'D1',['D2.1','D2.2', 'D2.3'],['D3.1','D3.2']],
                  })
print('df',df, sep='\n')

def dfListExplode(df, explodeKeys):
    if not isinstance(explodeKeys, list):
        explodeKeys=[explodeKeys]
    # recursive handling of explodeKeys
    if len(explodeKeys)==0:
        return df
    elif len(explodeKeys)==1:
        explodeKey=explodeKeys[0]
    else:
        return dfListExplode( dfListExplode(df, explodeKeys[:1]), explodeKeys[1:])
    # perform explosion/unnesting for key: explodeKey
    dfPrep=df[explodeKey].apply(lambda x: x if isinstance(x,list) else [x]) #casts all elements to a list
    dfIndExpl=pd.DataFrame([[x] + [z] for x, y in zip(dfPrep.index,dfPrep.values) for z in y ], columns=['explodedIndex',explodeKey])
    dfMerged=dfIndExpl.merge(df.drop(explodeKey, axis=1), left_on='explodedIndex', right_index=True)
    dfReind=dfMerged.reindex(columns=list(df))
    return dfReind

dfExpl=dfListExplode(df,['C','columnD'])
print('dfExpl',dfExpl, sep='\n')

เครดิตสำหรับคำตอบของ WeNYoBen


1

ไม่แนะนำบางอย่าง (อย่างน้อยก็ใช้ได้ผลในกรณีนี้):

df=pd.concat([df]*2).sort_index()
it=iter(df['B'].tolist()[0]+df['B'].tolist()[0])
df['B']=df['B'].apply(lambda x:next(it))

concat+ sort_index+ iter+ +applynext

ตอนนี้:

print(df)

คือ:

   A  B
0  1  1
0  1  2
1  2  1
1  2  2

หากสนใจดัชนี:

df=df.reset_index(drop=True)

ตอนนี้:

print(df)

คือ:

   A  B
0  1  1
1  1  2
2  2  1
3  2  2

1
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]})

pd.concat([df['A'], pd.DataFrame(df['B'].values.tolist())], axis = 1)\
  .melt(id_vars = 'A', value_name = 'B')\
  .dropna()\
  .drop('variable', axis = 1)

    A   B
0   1   1
1   2   1
2   1   2
3   2   2

มีความคิดเห็นเกี่ยวกับวิธีนี้หรือไม่? หรือทำทั้ง concat และละลายถือว่า "แพง" เกินไป?


1

ฉันมีอีกวิธีที่ดีในการแก้ปัญหานี้เมื่อคุณมีคอลัมน์มากกว่าหนึ่งคอลัมน์ที่จะระเบิด

df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]], 'C':[[1,2,3],[1,2,3]]})

print(df)
   A       B          C
0  1  [1, 2]  [1, 2, 3]
1  2  [1, 2]  [1, 2, 3]

ฉันต้องการระเบิดคอลัมน์ B และ C ก่อนอื่นฉันระเบิด B ครั้งที่สอง C. กว่าที่ฉันจะวาง B และ C จาก df เดิม หลังจากนั้นฉันจะทำการรวมดัชนีกับ 3 dfs

explode_b = df.explode('B')['B']
explode_c = df.explode('C')['C']
df = df.drop(['B', 'C'], axis=1)
df = df.join([explode_b, explode_c])

0
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]})

out = pd.concat([df.loc[:,'A'],(df.B.apply(pd.Series))], axis=1, sort=False)

out = out.set_index('A').stack().droplevel(level=1).reset_index().rename(columns={0:"B"})

       A    B
   0    1   1
   1    1   2
   2    2   1
   3    2   2
  • คุณสามารถใช้สิ่งนี้เป็นซับเดียวได้หากคุณไม่ต้องการสร้างวัตถุกลาง

0
# Here's the answer to the related question in:
# https://stackoverflow.com/q/56708671/11426125

# initial dataframe
df12=pd.DataFrame({'Date':['2007-12-03','2008-09-07'],'names':
[['Peter','Alex'],['Donald','Stan']]})

# convert dataframe to array for indexing list values (names)
a = np.array(df12.values)  

# create a new, dataframe with dimensions for unnested
b = np.ndarray(shape = (4,2))
df2 = pd.DataFrame(b, columns = ["Date", "names"], dtype = str)

# implement loops to assign date/name values as required
i = range(len(a[0]))
j = range(len(a[0]))
for x in i:
    for y in j:
        df2.iat[2*x+y, 0] = a[x][0]
        df2.iat[2*x+y, 1] = a[x][1][y]

# set Date column as Index
df2.Date=pd.to_datetime(df2.Date)
df2.index=df2.Date
df2.drop('Date',axis=1,inplace =True)

0

ในกรณีของฉันมีคอลัมน์มากกว่าหนึ่งคอลัมน์ที่จะระเบิดและมีความยาวตัวแปรสำหรับอาร์เรย์ที่ต้องไม่ทดสอบ

ฉันลงเอยด้วยการใช้explodeฟังก์ชันแพนด้าใหม่ 0.25 สองครั้งจากนั้นลบรายการที่ซ้ำกันที่สร้างขึ้นและมันก็ทำงาน!

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