1. ホーム
  2. python

[解決済み] pandas DataFrameのカラムを複数行にアンネスト(分解)する方法

2022-03-02 11:40:45

質問

以下のようなDataFrameがあり、列の1つがオブジェクト(リスト型セル)になっています。

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です。セルの内容がリストの場合、リスト内の各要素に対して行を作成する

良い質問と回答ですが、リストで1列しか扱えません(私の回答では、自己定義関数は複数の列に対して機能します。 apply これは推奨されませんので、詳細を確認してください。 自分のコードでpandas apply()を使いたいのはどんなとき? )

どのように解決するのですか?

知っている object d型カラムの場合、pandasの関数でデータを変換するのが難しくなります。このようなデータを受け取ったとき、最初に思いついたのは "flatten" または列の非正規化でした。

このような質問にはpandasとPythonの関数を使用しています。もし、上記のソリューションの速度が心配なら、以下をチェックしてみてください。 user3483203さんの回答 これはnumpyを使用しており、ほとんどの場合numpyの方が速いからです。私のお勧めは Cython または ヌンバ 速度が重要な場合は


メソッド0 [pandas >= 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 使用方法 repeatDataFrame コンストラクタで、データフレームを再作成します。

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 ......A.n がある場合、このメソッド( 方法2 )上の列を1つずつ再作成するのは大変です。

解決策: 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

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 cyclechain : 純粋な 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]

Self-def機能。

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

2021-02-17更新 オリジナルexplode機能搭載

def unnesting(df, explode, axis):
    if axis==1:
        df1 = pd.concat([df[x].explode() for x in explode], axis=1)
        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')