[python] Pandas DataFrame에서 열의 중첩을 해제 (폭파)하는 방법은 무엇입니까?

열 중 하나가 개체 (목록 유형 셀) 인 다음 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

이를 달성하려면 어떻게해야합니까?


관련 질문

pandas : 셀 내용이 목록 인 경우 목록의 각 요소에 대한 행을 만듭니다.

좋은 질문과 답변이지만 목록으로 하나의 열만 처리합니다 (내 대답에서 self-def 함수는 여러 열에 대해 작동하며 허용되는 대답은 가장 많은 시간이 소요 apply되는 사용이며 권장되지 않습니다. 더 많은 정보를 확인하십시오 . 내 코드에서 pandas apply ()를 사용하려면? )



답변

R및 을 모두 사용하는 사용자로서 python이러한 유형의 질문을 두 번 보았습니다.

R에서는 tidyr라는 패키지의 내장 함수가 있습니다 unnest. 그러나 Python( pandas)에는 이러한 유형의 질문에 대한 내장 함수가 없습니다.

object열은 type항상 pandas‘함수 로 데이터를 변환하기 어렵게 만든다는 것을 알고 있습니다 . 이와 같은 데이터를 받았을 때 가장 먼저 떠오른 것은 열을 ‘평탄화’하거나 중첩 해제하는 것입니다.

이 유형의 질문에 대해 pandaspython기능을 사용 하고 있습니다. 위 솔루션의 속도가 걱정된다면 user3483203의 답변을 확인하세요. 그가 사용 numpy하고 있고 대부분의 시간 numpy이 더 빠르기 때문입니다. 나는 추천 Cpythonnumba속도가 귀하의 경우 중요합니다.


방법 0 [pandas> = 0.25] pandas 0.25
에서 시작하여 하나의 열만 분해해야하는 경우 다음 함수를 사용할 수 있습니다 .explode

df.explode('B')

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

방법 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생성자는 다시 작성 (여러 열에서 좋지 않은 성능 좋은) 당신의 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후 ‘unnest’단일 컬럼

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]

또는
사용하는 방법 4reindexloc

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

고성능
numpy위해 사용 하는 방법 6 :

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


기본 기능을 사용하는 방법 7itertools cyclechain: 재미를 위해 순수한 파이썬 솔루션

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

컬럼 방식의 중첩 해제

위의 모든 방법은 수직 중첩 해제 및 폭발 에 대해 이야기 하고 있습니다. 목록을 수평으로 확장 해야하는 경우 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


답변

옵션 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)")

공연

여기에 이미지 설명 입력


답변

pandas 0.25 에서는 다음 explode()메서드를 추가 하여 목록과 같은 열을 분해하는 작업이 크게 단순화 되었습니다 .

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

밖:

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


답변

한 가지 대안은 열의 행에 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


답변

내 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


답변

일반적으로 하위 목록 길이가 다르고 결합 / 병합이 계산 비용이 훨씬 더 많이 들기 때문입니다. 다른 길이의 하위 목록과 더 일반적인 열에 대한 방법을 다시 테스트했습니다.

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

공연

각 방법의 상대 시간


답변

더 많은 열에 적용 할 수 있도록 문제를 약간 일반화했습니다.

내 솔루션의 기능 요약 :

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의 답변에 대한 크레딧