web-dev-qa-db-ja.com

pandasデータフレームでタプルの列を分割する方法は?

pandasデータフレームがあります(これはほんの一部です)

>>> d1
   y norm test  y norm train  len(y_train)  len(y_test)  \
0    64.904368    116.151232          1645          549   
1    70.852681    112.639876          1645          549   

                                    SVR RBF  \
0   (35.652207342877873, 22.95533537448393)   
1  (39.563683797747622, 27.382483096332511)   

                                        LCV  \
0  (19.365430594452338, 13.880062435173587)   
1  (19.099614489458364, 14.018867136617146)   

                                   RIDGE CV  \
0  (4.2907610988480362, 12.416745648065584)   
1    (4.18864306788194, 12.980833914392477)   

                                         RF  \
0   (9.9484841581029428, 16.46902345373697)   
1  (10.139848213735391, 16.282141345406522)   

                                           GB  \
0  (0.012816232716538605, 15.950164822266007)   
1  (0.012814519804493328, 15.305745202851712)   

                                             ET DATA  
0  (0.00034337162272515505, 16.284800366214057)  j2m  
1  (0.00024811554516431878, 15.556506191784194)  j2m  
>>> 

タプルを含むすべての列を分割したい。たとえば、列LCVを列LCV-aおよびLCV-bに置き換えます。

どうやってやるの?

62
Donbeo

より大きなデータセットでは、.apply()pd.DataFrame(df['b'].values.tolist(), index=df.index)よりも数桁遅いことがわかりました。

この決定はGitHubで解決されましたが、この決定には同意しません。

https://github.com/pandas-dev/pandas/issues/11615

編集:この回答に基づいて: https://stackoverflow.com/a/44196843/2230844

23
denfromufa

私はこれが少し前からであることを知っていますが、2番目の解決策の警告:

pd.DataFrame(df['b'].values.tolist())

明示的にインデックスを破棄し、デフォルトのシーケンシャルインデックスを追加しますが、受け入れられた答えは

apply(pd.Series)

適用の結果は行インデックスを保持するため、そうはなりません。順序は元の配列から最初に保持されますが、pandasは2つのデータフレームのインデックスを一致させようとします。

これは、行を数値インデックス付き配列に設定しようとする場合に非常に重要になる可能性があり、pandasは新しい配列のインデックスを古い配列に自動的に一致させ、順序に歪みを生じさせます。

より良いハイブリッドソリューションは、元のデータフレームのインデックスを新しいデータセットに設定することです。

pd.DataFrame(df['b'].values.tolist(), index=df.index)

結果に対して順序とインデックスが保持されるようにしながら、2番目の方法を使用する速度を保持します。

8
Mike

私はもっ​​と簡単な方法だと思う:

>>> import pandas as pd
>>> df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]}) 
>>> df
   a       b
0  1  (1, 2)
1  2  (3, 4)
>>> df['b_a']=df['b'].str[0]
>>> df['b_b']=df['b'].str[1]
>>> df
   a       b  b_a  b_b
0  1  (1, 2)    1    2
1  2  (3, 4)    3    4
6
Jinhua Wang

pandas.Seriesdtype == objectオブジェクトで使用可能なstrアクセサーは、実際には反復可能です。

pandas.DataFramedfを想定:

df = pd.DataFrame(dict(col=[*Zip('abcdefghij', range(10, 101, 10))]))

df

        col
0   (a, 10)
1   (b, 20)
2   (c, 30)
3   (d, 40)
4   (e, 50)
5   (f, 60)
6   (g, 70)
7   (h, 80)
8   (i, 90)
9  (j, 100)

反復可能かどうかをテストできます

from collections import Iterable

isinstance(df.col.str, Iterable)

True

その後、他のイテラブルを行うように、そこから割り当てることができます。

var0, var1 = 'xy'
print(var0, var1)

x y

最も簡単なソリューション

したがって、1行で両方の列を割り当てることができます

df['a'], df['b'] = df.col.str

df

        col  a    b
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

より高速なソリューション

少しだけ複雑ですが、Zipを使用して同様の反復可能オブジェクトを作成できます

df['c'], df['d'] = Zip(*df.col)

df

        col  a    b  c    d
0   (a, 10)  a   10  a   10
1   (b, 20)  b   20  b   20
2   (c, 30)  c   30  c   30
3   (d, 40)  d   40  d   40
4   (e, 50)  e   50  e   50
5   (f, 60)  f   60  f   60
6   (g, 70)  g   70  g   70
7   (h, 80)  h   80  h   80
8   (i, 90)  i   90  i   90
9  (j, 100)  j  100  j  100

列をなして

つまり、既存のdfを変更しないでください
これは、assignがキーワード引数を取り、キーワードが新しい(または既存の)列名であり、値が新しい列の値になるため、機能します。辞書を使用し、**で展開して、キーワード引数として機能させることができます。したがって、これは'g' iterableの最初の項目であるdf.col.strという名前の新しい列と、'h' iterableの2番目の項目であるdf.col.strという名前の新しい列を割り当てる賢い方法です。

df.assign(**dict(Zip('gh', df.col.str)))

        col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

listアプローチの私のバージョン

最新のリストの理解と変数の展開。
注:joinを使用してインラインでも

df.join(pd.DataFrame([*df.col], df.index, [*'ef']))

        col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

変異バージョンは

df[['e', 'f']] = pd.DataFrame([*df.col], df.index)

素朴な時間テスト

上記で定義されたものを使用

%timeit df.assign(**dict(Zip('gh', df.col.str)))
%timeit df.assign(**dict(Zip('gh', Zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))

1.16 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
635 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
795 µs ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

10 ^ 3倍

df = pd.concat([df] * 1000, ignore_index=True)

%timeit df.assign(**dict(Zip('gh', df.col.str)))
%timeit df.assign(**dict(Zip('gh', Zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))

11.4 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.1 ms ± 41.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.33 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1
piRSquared