パンダ:既存の列から計算された値を使用して、データフレームに2つの新しい列を作成します


100

私はパンダライブラリを使用しておりdf、n列(n> 0)のデータフレームに2つの新しい列を追加したいと思います。
これらの新しい列は、データフレームの列の1つに関数を適用した結果です。

適用する関数は次のようになります。

def calculate(x):
    ...operate...
    return z, y

値のみを返す関数の新しい列を作成する1つの方法は次のとおりです。

df['new_col']) = df['column_A'].map(a_function)

だから、私が欲しいもの、そして失敗したことを試みたもの(*)は、次のようなものです:

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)

これを達成するための最良の方法は何でしょうか?手がかりなしでドキュメントをスキャンしました。

** df['column_A'].map(calculate)パンダシリーズを返します。各アイテムはタプルz、yで構成されています。これを2つのデータフレーム列に割り当てようとすると、ValueErrorが発生します。*

回答:


119

私は使用するだけzipです:

In [1]: from pandas import *

In [2]: def calculate(x):
   ...:     return x*2, x*3
   ...: 

In [3]: df = DataFrame({'a': [1,2,3], 'b': [2,3,4]})

In [4]: df
Out[4]: 
   a  b
0  1  2
1  2  3
2  3  4

In [5]: df["A1"], df["A2"] = zip(*df["a"].map(calculate))

In [6]: df
Out[6]: 
   a  b  A1  A2
0  1  2   2   3
1  2  3   4   6
2  3  4   6   9

ありがとう、すばらしい。私は0.8.1のドキュメントでこのようなものを見つけませんでした...私は常にシリーズでタプルのリストとして考える必要があると思います...
joaquin

代わりにこれを行うことでパフォーマンスに違いはありますか?zip(* df ["a"]。map(calculate))の代わりにzip(* map(calculate、df ["a"]))を使用すると、(上記のように)[(2、4、6)、( 3、6、9)]?
ekta 2014年

1
「SettingWithCopyWarning:値がDataFrameからのスライスのコピーに設定されようとしています。代わりに.loc [row_indexer、col_indexer] = valueを使用してみてください」というような警告が表示されます。心配する必要がありますか?パンダv.0.15
taras

46

私の意見では、トップの答えに欠陥があります。うまくいけば、誰もがすべてのパンダを名前空間に大量にインポートしていないfrom pandas import *。また、mapメソッドは、ディクショナリまたはシリーズを渡すときのために予約する必要があります。関数をとることができますが、これapplyが使用されます。

したがって、上記のアプローチを使用する必要がある場合は、次のように記述します

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))

ここでzipを使用する理由は実際にはありません。あなたは単にこれを行うことができます:

df["A1"], df["A2"] = calculate(df['a'])

この2番目の方法は、より大きなDataFrameでもはるかに高速です

df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})

300,000行で作成されたDataFrame

%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

zipより60倍高速


一般に、applyの使用は避けてください

通常、適用はPythonリストの繰り返しよりもはるかに高速ではありません。上記と同じことを行うためにforループのパフォーマンスをテストしてみましょう

%%timeit
A1, A2 = [], []
for val in df['a']:
    A1.append(val**2)
    A2.append(val**3)

df['A1'] = A1
df['A2'] = A2

298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

したがって、これは2倍遅く、これはひどいパフォーマンスの低下ではありませんが、上記をCythonizeすると、パフォーマンスが大幅に向上します。仮定すると、あなたはipythonを使用しています:

%load_ext cython

%%cython
cpdef power(vals):
    A1, A2 = [], []
    cdef double val
    for val in vals:
        A1.append(val**2)
        A2.append(val**3)

    return A1, A2

%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

適用せずに直接割り当てる

直接ベクトル化された演算を使用すると、速度がさらに向上します。

%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

これは、ループの代わりにNumPyの非常に高速なベクトル化された演算を利用します。オリジナルよりも30倍高速化されました。


で最も簡単な速度テスト apply

上記の例はapply、速度がどれほど遅いかを明確に示しているはずですが、そのために最も明確な例として、最も基本的な例を見てみましょう。適用ありとなしの1000万個のシリーズを2乗しましょう

s = pd.Series(np.random.rand(10000000))

%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

適用しない場合は50倍速くなります

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
これは本当に素晴らしい答えです。質問したいのapplymapですが、データフレームの各要素に特定の関数を実装する必要がある場合、どう思いますか?
デビッド

3
この回答にはいくつかの良いアドバイスがありますが、func(series)代わりに使用する主なアドバイスseries.apply(func)は、個々の値とシリーズの両方で同様に動作する操作を使用してfuncが完全に定義されている場合にのみ適用できると思います。それは最初の回答の例の場合ですが、列への関数の適用についてより一般的に尋ねているOPの質問の場合はそうではありません。1/2
グラハムリー

1
例として、dfが:DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})およびcalcis:のdef calc(x): return x[0], len(x)場合tdf.a.apply(calc))calc(tdf.a)非常に異なるものを返します。
グラハムリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.