Pandasデータフレームの2つの列に関数を適用する方法


368

df列を持つがあるとします'ID', 'col_1', 'col_2'。そして私は関数を定義します:

f = lambda x, y : my_function_expression

次に、の2つの列を要素ごとに適用しfて、新しい列を計算します。df'col_1', 'col_2''col_3'

df['col_3'] = df[['col_1','col_2']].apply(f)  
# Pandas gives : TypeError: ('<lambda>() takes exactly 2 arguments (1 given)'

実行する方法 ?

** 以下のように詳細サンプルを追加します ***

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

  ID  col_1  col_2            col_3
0  1      0      1       ['a', 'b']
1  2      2      4  ['c', 'd', 'e']
2  3      3      5  ['d', 'e', 'f']

4
fを列に直接適用できますか:df ['col_3'] = f(df ['col_1']、df ['col_2'])
btel

1
fが行われているのかを知るのに役立ちます
tehmisvh

2
いいえ、df ['col_3'] = f(df ['col_1']、df ['col_2'])は機能しません。fは、ベクトル入力ではなく、スカラー入力のみを受け入れます。OK、f = lambda x、y:x + yと仮定できます。(もちろん、私の実際のfはそれほど単純ではありません。それ以外の場合は、直接df ['col_3'] = df ['col_1'] + df ['col_2'])
bigbug

1
以下のURLで関連するQ&Aを見つけましたが、私の問題は、2から1ではなく、2つの既存の列によって新しい列を計算しています。 stackoverflow.com/questions/12356501/...
bigbug

私の応答stackoverflow.com/a/52854800/5447172は、回避策や数値のインデックス付けなしで、最もPythonic / Pandanicの方法でこれに答えると思います。例で必要な出力を正確に生成します。
ajrwhite 2018年

回答:


291

applyこれは、で呼び出しているデータフレームでの使用例ですaxis = 1

違いは、関数に2つの値を渡そうとする代わりにf、pandas Seriesオブジェクトを受け入れるように関数を書き直してから、Seriesにインデックスを付けて、必要な値を取得することです。

In [49]: df
Out[49]: 
          0         1
0  1.000000  0.000000
1 -0.494375  0.570994
2  1.000000  0.000000
3  1.876360 -0.229738
4  1.000000  0.000000

In [50]: def f(x):    
   ....:  return x[0] + x[1]  
   ....:  

In [51]: df.apply(f, axis=1) #passes a Series object, row-wise
Out[51]: 
0    1.000000
1    0.076619
2    1.000000
3    1.646622
4    1.000000

ユースケースによっては、pandas groupオブジェクトを作成applyしてグループで使用すると役立つ場合があります。


はい、適用を使用しようとしましたが、有効な構文式が見つかりません。そして、dfの各行が一意である場合でも、groupbyを使用しますか?
bigbug

私の回答に例を追加しました。これがあなたが探していることを期待しています。そうでない場合は、sumこれまでに提案された方法のいずれかで問題が解決されるため、より具体的な関数例を提供してください。
アマン

1
コードを貼り付けますか?関数を書き換えます:def get_sublist(x):return mylist [x [1]:x [2] + 1] and df ['col_3'] = df.apply(get_sublist、axis = 1)は 'ValueError:オペランドがない形状(2)(3)」と一緒に放送される
bigbug

3
@Aman:Pandasバージョン0.14.1(およびおそらくそれ以前)では、ラムダ式も使用できます。dfあなたが定義したオブジェクトを与える、(同等の結果を持つ)別のアプローチはdf.apply(lambda x: x[0] + x[1], axis = 1)です。
Jubbles

2
あなたはちょうどその代わりに、インデックスの機能で列名を使用することができます@CanCeylanあなたが変えるために心配する必要はあり、または例えば参照名でインデックスを得ることはありませんstackoverflow.com/questions/13021654/...
ダボス

165

パンダでこれを行うためのクリーンな一行の方法があります:

df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)

これによりf、複数の入力値を持つユーザー定義関数になることができ、(安全でない)数値インデックスではなく(安全な)列名を使用して列にアクセスします。

データの例(元の質問に基づく):

import pandas as pd

df = pd.DataFrame({'ID':['1', '2', '3'], 'col_1': [0, 2, 3], 'col_2':[1, 4, 5]})
mylist = ['a', 'b', 'c', 'd', 'e', 'f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = df.apply(lambda x: get_sublist(x.col_1, x.col_2), axis=1)

の出力print(df)

  ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

列名にスペースが含まれている場合、または既存のデータフレーム属性と名前を共有している場合は、角括弧でインデックスを作成できます。

df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)

2
使用axis=1していて列が呼び出されnameた場合、実際には列データではなくが返されますindex。取得と同様namegroupby()。私は私の列の名前を変更することでこれを解決しました。
トムヘムズ

2
これだよ!複数の入力パラメーターを持つユーザー定義関数をラムダに挿入できることに気づかなかっただけです。Series.apply()ではなくDF.apply()を使用していることに注意してください(私はそう思います)。これにより、必要な2つの列を使用してdfにインデックスを付け、列全体を関数に渡すことができますが、apply()を使用しているため、列全体に要素単位で関数が適用されます。鮮やかさ!投稿ありがとうございます!
データファン

1
最終的に!あなたは私の日を救った!
Mysterio、

これを行うための推奨方法は、df.loc [:, 'new col'] = df.apply .....
valearner

@valearner .loc例で好む理由はないと思います。これを別の問題設定(スライスの操作など)に適応させる場合に必要になることがあります。
ajrwhite

86

簡単な解決策は次のとおりです。

df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)

1
この答えは、質問のアプローチとどのように異なりますか:df ['col_3'] = df [['col_1'、 'col_2']]。apply(f)確認のために、質問のアプローチは機能しませんでした。ポスターはこの軸= 1を指定しませんでした、デフォルトは軸= 0です?
Lost1

1
この回答は@Anmanの回答に匹敵しますが、少しちらつきがあります。彼はイテラブルを取り、関数fに渡す前にアンパックする無名関数を作成しています。
tiao

39

興味深い質問です!以下の私の答え:

import pandas as pd

def sublst(row):
    return lst[row['J1']:row['J2']]

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(sublst,axis=1)
print df

出力:

  ID  J1  J2
0  1   0   1
1  2   2   4
2  3   3   5
  ID  J1  J2      J3
0  1   0   1     [a]
1  2   2   4  [c, d]
2  3   3   5  [d, e]

列名をID、J1、J2、J3に変更して、ID <J1 <J2 <J3になるようにし、列が正しい順序で表示されるようにしました。

もう1つの簡単なバージョン:

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(lambda row:lst[row['J1']:row['J2']],axis=1)
print df

23

あなたが探しているメソッドはSeries.combineです。ただし、データ型には注意が必要です。あなたの例では、あなたは(答えをテストするときに私がしたように)素朴に呼び出すでしょう

df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)

ただし、これはエラーをスローします。

ValueError: setting an array element with a sequence.

私の推測では、結果はメソッドを呼び出すシリーズと同じタイプであると予想されているようです(ここではdf.col_1)。ただし、以下は機能します。

df['col_3'] = df.col_1.astype(object).combine(df.col_2, func=get_sublist)

df

   ID   col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

12

あなたが書いた方法は2つの入力を必要とします。エラーメッセージを見ると、fへの入力が2つではなく、1つだけであることがわかります。エラーメッセージは正しいです。
df [['col1'、 'col2']]は、2つの別々の列ではなく、2つの列を持つ単一のデータフレームを返すため、この不一致が生じます。

単一の入力を取得するようにfを変更し、上記のデータフレームを入力として保持し、関数本体内で x、yに分割する必要があります。次に、必要なことをすべて行い、単一の値を返します。

構文が.apply(f)であるため、この関数シグネチャが必要です。したがって、fは、現在のfが期待する2つのものではなく、1つのもの=データフレームを取得する必要があります。

あなたはfの本文を提供していないので、これ以上詳細にはヘルプできません。


12

np.vectorizeに投票します。これにより、x列だけを撮影し、関数のデータフレームを処理しないことができるため、制御できない関数や、2つの列と定数を関数に送信するような関数(col_1、col_2、 「foo」)。

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

df.loc[:,'col_3'] = np.vectorize(get_sublist, otypes=["O"]) (df['col_1'], df['col_2'])


df

ID  col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

1
これはパンダを使用した質問には実際には答えません。
mnky9800n

18
問題は、「Pandasデータフレームの2列に関数を適用する方法」ではなく、「Pandasメソッドのみを使用してPandasデータフレームの2列に関数を適用する方法」ではなく、numpyはPandasの依存関係であるため、インストールする必要があります。これは奇妙な異論のようです。
Trae Wallace

12

apply結果のオブジェクトがSeriesまたはDataFrameであることが保証されていないため、リストを返すことは危険な操作です。また、場合によっては例外が発生することもあります。簡単な例を見てみましょう:

df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)),
                  columns=['a', 'b', 'c'])
df
   a  b  c
0  4  0  0
1  2  0  1
2  2  2  2
3  1  2  2
4  3  0  0

からリストを返すと、3つの結果が考えられます。 apply

1)返されたリストの長さが列の数と等しくない場合、一連のリストが返されます。

df.apply(lambda x: list(range(2)), axis=1)  # returns a Series
0    [0, 1]
1    [0, 1]
2    [0, 1]
3    [0, 1]
4    [0, 1]
dtype: object

2)返されたリストの長さが列の数と等しい場合、DataFrameが返され、各列はリスト内の対応する値を取得します。

df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame
   a  b  c
0  0  1  2
1  0  1  2
2  0  1  2
3  0  1  2
4  0  1  2

3)返されたリストの長さが最初の行の列数と等しいが、リストの要素数が列数と異なる行が少なくとも1行ある場合、ValueErrorが発生します。

i = 0
def f(x):
    global i
    if i == 0:
        i += 1
        return list(range(3))
    return list(range(4))

df.apply(f, axis=1) 
ValueError: Shape of passed values is (5, 4), indices imply (5, 3)

適用せずに問題に答える

applyaxis = 1での使用は非常に低速です。基本的な反復法を使用すると、パフォーマンスが大幅に向上する可能性があります(特に大きなデータセットの場合)。

より大きなデータフレームを作成する

df1 = df.sample(100000, replace=True).reset_index(drop=True)

タイミング

# apply is slow with axis=1
%timeit df1.apply(lambda x: mylist[x['col_1']: x['col_2']+1], axis=1)
2.59 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# zip - similar to @Thomas
%timeit [mylist[v1:v2+1] for v1, v2 in zip(df1.col_1, df1.col_2)]  
29.5 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

@Thomas回答

%timeit list(map(get_sublist, df1['col_1'],df1['col_2']))
34 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
学ぶことができるところからとても詳細な答えを見るのは素晴らしいことです。
Andrea Moro

7

これはPandasまたはNumpyの操作を使用したソリューションほど高速ではないと思いますが、関数を書き直したくない場合は、mapを使用できます。元のサンプルデータの使用-

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = list(map(get_sublist,df['col_1'],df['col_2']))
#In Python 2 don't convert above to list

このようにして、関数に必要なだけ引数を渡すことができます。出力は私たちが欲しかったものです

ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

1
これは、使用することを実際にはるかに高速それらの答えであるapplyaxis=1
テッドPetrou

2

あなたの質問への私の例:

def get_sublist(row, col1, col2):
    return mylist[row[col1]:row[col2]+1]
df.apply(get_sublist, axis=1, col1='col_1', col2='col_2')

2

あなたが巨大なデータセットを持っているなら、あなたはswifterを使ってこれを行うための簡単だがより速い(実行時間)方法を使うことができます:

import pandas as pd
import swifter

def fnc(m,x,c):
    return m*x+c

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})
df["y"] = df.swifter.apply(lambda x: fnc(x.m, x.x, x.c), axis=1)

1

get_sublist関数を変更するのではなく、DataFrameのapplyメソッドを使用してジョブを実行したいと思います。あなたが望む結果を得るために、私は2つのヘルプ関数を書きました:get_sublist_listunlist。関数名が示すように、最初にサブリストのリストを取得し、次にそのリストからそのサブリストを抽出します。最後に、applyこれら2つの関数をdf[['col_1','col_2']]DataFrame に適用するために関数を呼び出す必要があります。

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

def get_sublist_list(cols):
    return [get_sublist(cols[0],cols[1])]

def unlist(list_of_lists):
    return list_of_lists[0]

df['col_3'] = df[['col_1','col_2']].apply(get_sublist_list,axis=1).apply(unlist)

df

関数[]を囲むために使用しない場合get_sublistget_sublist_list関数はプレーンリストを返しますValueError: could not broadcast input array from shape (3) into shape (2)。@ Ted Petrouが述べたように、この関数はを発生させます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.