วิธีใช้ฟังก์ชันกับสองคอลัมน์ของ Pandas dataframe


368

สมมติว่าผมมีซึ่งมีคอลัมน์ของdf 'ID', 'col_1', 'col_2'และฉันกำหนดฟังก์ชั่น:

f = lambda x, y : my_function_expression.

ตอนนี้ผมต้องการที่จะใช้fในการdf's สองคอลัมน์'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 ยอมรับเฉพาะอินพุตแบบสเกลาร์ไม่ใช่อินพุตแบบเวกเตอร์ ตกลงคุณสามารถสมมติ f = lambda x, y: x + y (แน่นอน f ที่แท้จริงของฉันไม่ง่ายอย่างนั้นมิฉะนั้นฉันสามารถ df ['col_3'] = df ['col_1'] + df ['col_2'])
bigbug

1
ฉันพบคำถามและคำตอบที่เกี่ยวข้องที่ด้านล่าง URL แต่ปัญหาของฉันคือการคำนวณคอลัมน์ใหม่ด้วยคอลัมน์ที่มีอยู่สองคอลัมน์ไม่ใช่ 2 จาก 1 stackoverflow.com/questions/12356501/…
bigbug

ฉันคิดว่าการตอบสนองของฉันstackoverflow.com/a/52854800/5447172ตอบคำถามนี้ด้วยวิธี Pythonic / Pandanic ที่สุดโดยไม่มีวิธีแก้ปัญหาหรือการทำดัชนีตัวเลข มันสร้างผลลัพธ์ที่คุณต้องการในตัวอย่างของคุณ
ajrwhite

คำตอบ:


291

นี่คือตัวอย่างการใช้applyใน dataframe axis = 1ซึ่งผมโทรด้วย

โปรดสังเกตว่าความแตกต่างคือแทนที่จะพยายามส่งผ่านสองค่าไปยังฟังก์ชันfให้เขียนฟังก์ชันใหม่เพื่อรับวัตถุซีรี่ส์แพนด้าแล้วทำดัชนีซีรี่ส์เพื่อรับค่าที่ต้องการ

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

ขึ้นอยู่กับกรณีการใช้งานของคุณบางครั้งจะมีประโยชน์ในการสร้างgroupวัตถุแพนด้าและจากนั้นใช้applyกับกลุ่ม


ใช่ฉันพยายามใช้ใช้ แต่ไม่พบนิพจน์ไวยากรณ์ที่ถูกต้อง และถ้าแต่ละแถวของ df ไม่เหมือนใครยังคงใช้ groupby?
bigbug

เพิ่มตัวอย่างในคำตอบของฉันหวังว่านี่จะเป็นสิ่งที่คุณกำลังมองหา หากไม่ได้โปรดระบุฟังก์ชันตัวอย่างที่เฉพาะเจาะจงมากขึ้นเนื่องจากsumสามารถแก้ไขได้สำเร็จโดยวิธีการใด ๆ ที่แนะนำจนถึงขณะนี้
Aman

1
คุณช่วยกรุณาวางรหัสของคุณได้ไหม ฉันเขียนฟังก์ชันใหม่: def get_sublist (x): ส่งคืน mylist [x [1]: x [2] + 1] และ df ['col_3'] = df.apply (get_sublist, แกน = 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/ …
Davos

165

มีวิธีทำความสะอาดแบบบรรทัดเดียวใน Pandas:

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]

หากชื่อคอลัมน์ของคุณมีช่องว่างหรือแชร์ชื่อด้วยแอตทริบิวต์ dataframe ที่มีอยู่คุณสามารถสร้างดัชนีด้วยเครื่องหมายวงเล็บเหลี่ยมได้:

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

2
หมายเหตุถ้าใช้axis=1และคุณคอลัมน์เรียกว่าnameมันจะไม่จริงกลับข้อมูลคอลัมน์ของคุณ indexแต่ เช่นเดียวกับที่จะได้รับในname groupby()ฉันแก้ไขได้โดยเปลี่ยนชื่อคอลัมน์
Tom Hemmes

2
นี่ไง! ฉันไม่ทราบว่าคุณสามารถแทรกฟังก์ชั่นที่ผู้ใช้กำหนดด้วยอินพุตพารามิเตอร์หลายตัวลงใน lambdas เป็นสิ่งสำคัญที่จะต้องทราบ (ฉันคิดว่า) คุณกำลังใช้ DF.apply () มากกว่า Series.apply () สิ่งนี้ช่วยให้คุณจัดทำดัชนี df โดยใช้สองคอลัมน์ที่คุณต้องการและส่งผ่านทั้งคอลัมน์ไปยังฟังก์ชัน แต่เนื่องจากคุณกำลังใช้ Apply () มันจึงใช้ฟังก์ชันนี้ในลักษณะองค์ประกอบที่ชาญฉลาดตลอดทั้งคอลัมน์ ยอดเยี่ยม! ขอบคุณสำหรับการโพสต์!
Data-phile

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']]. ใช้ (f) เพียงเพื่อยืนยันวิธีการในคำถามไม่ทำงานเพราะ ผู้โพสต์ไม่ได้ระบุแกนนี้ = 1 เริ่มต้นคือแกน = 0?
Lost1

1
คำตอบนี้เทียบได้กับคำตอบของ @ Anman แต่มีการสั่นไหวเล็กน้อย เขากำลังสร้างฟังก์ชั่นนิรนามซึ่งรับฟังก์ชั่น iterable และ unpack ก่อนที่มันจะส่งไปยังฟังก์ชัน 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 ดังนั้นคอลัมน์จึงแสดงในลำดับที่ถูกต้อง

อีกหนึ่งรุ่นสั้น ๆ :

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

วิธีที่คุณเขียนมันต้องการสองอินพุต หากคุณดูข้อความแสดงข้อผิดพลาดข้อความแจ้งว่าคุณไม่ได้ให้อินพุตสองอินพุตให้กับ f เพียงอันเดียว ข้อความแสดงข้อผิดพลาดถูกต้อง
การจับคู่ไม่ตรงกันเนื่องจาก df [['col1', 'col2']] ส่งคืนดาต้าเฟรมเดียวที่มีสองคอลัมน์ไม่ใช่สองคอลัมน์แยกกัน

คุณจำเป็นต้องเปลี่ยน f ของคุณเพื่อให้รับอินพุตเดี่ยวเก็บเฟรมข้อมูลข้างต้นเป็นอินพุตแล้วแบ่งมันเป็น x, y ภายในส่วนของฟังก์ชั่น จากนั้นทำสิ่งที่คุณต้องการและส่งคืนค่าเดียว

คุณต้องใช้ลายเซ็นของฟังก์ชั่นนี้เพราะไวยากรณ์คือ. ประยุกต์ (f) ดังนั้น f ต้องใช้สิ่งเดียว = dataframe และไม่ใช่สองสิ่งซึ่งเป็นสิ่งที่ f ปัจจุบันของคุณคาดหวัง

เนื่องจากคุณไม่ได้ให้เนื้อหาของ f ฉันไม่สามารถช่วยในรายละเอียดอีกต่อไป - แต่ควรให้ทางออกโดยไม่เปลี่ยนรหัสของคุณพื้นฐานหรือใช้วิธีอื่นแทนที่จะใช้


12

ฉันจะลงคะแนนให้กับ np.vectorize มันช่วยให้คุณถ่ายภาพได้มากกว่าจำนวนคอลัมน์ x และไม่จัดการกับ dataframe ในฟังก์ชั่นดังนั้นมันยอดเยี่ยมสำหรับฟังก์ชั่นที่คุณไม่ควบคุมหรือทำอะไรบางอย่างเช่นการส่ง 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 dataframe" ไม่ใช่ "วิธีการใช้ฟังก์ชั่นกับสองคอลัมน์ของ Pandas dataframe โดยใช้วิธีการแบบ Pandas เท่านั้น" และ 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

มีสามผลลัพธ์ที่เป็นไปได้โดยส่งคืนรายการจาก 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)หากความยาวของรายการที่ส่งคืนเท่ากับจำนวนคอลัมน์สำหรับแถวแรก แต่มีอย่างน้อยหนึ่งแถวที่รายการมีจำนวนองค์ประกอบที่แตกต่างจากจำนวนคอลัมน์ที่เพิ่มขึ้น 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)

ตอบปัญหาโดยไม่ต้องสมัคร

การใช้applyกับ axis = 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)

@ คำตอบโทมัส

%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 แต่ถ้าคุณไม่ต้องการเขียนฟังก์ชันใหม่คุณสามารถใช้แผนที่ได้ ใช้ข้อมูลตัวอย่างต้นฉบับ -

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
นี่เป็นคำตอบที่เร็วขึ้นกว่านั้นมากที่ใช้applyกับaxis=1
Ted Petrou


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ฟังก์ชั่นและเพียงต้องการใช้applyวิธีการของ DataFrame ในการทำงาน เพื่อให้ได้ผลที่คุณต้องการผมเคยเขียนสองฟังก์ชั่นความช่วยเหลือและget_sublist_list unlistตามที่ชื่อฟังก์ชั่นแนะนำอันดับแรกให้รับรายการของรายการย่อยที่สองแยกรายการย่อยนั้นจากรายการนั้น ในที่สุดเราจำเป็นต้องเรียกapplyใช้ฟังก์ชันเพื่อใช้ทั้งสองฟังก์ชั่นกับ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_sublistฟังก์ชั่นนั้นget_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.