Cách áp dụng một hàm cho hai cột của khung dữ liệu Pandas


368

Giả sử tôi có một dfcái có cột 'ID', 'col_1', 'col_2'. Và tôi định nghĩa một hàm:

f = lambda x, y : my_function_expression.

Bây giờ tôi muốn áp dụng fđể df's hai cột 'col_1', 'col_2'để tính toán yếu tố khôn ngoan một cột mới 'col_3', có phần như:

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

Làm thế nào để làm gì?

** Thêm mẫu chi tiết như dưới đây ***

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
bạn có thể áp dụng f trực tiếp vào các cột: df ['col_3'] = f (df ['col_1'], df ['col_2'])
btel

1
sẽ hữu ích để biết những gì fđang làm
tehmisvh

2
không, df ['col_3'] = f (df ['col_1'], df ['col_2']) không hoạt động. Đối với f chỉ chấp nhận đầu vào vô hướng, không phải đầu vào vector. OK, bạn có thể giả sử f = lambda x, y: x + y. (tất nhiên, e thật của tôi không phải là đơn giản, nếu không tôi có thể trực tiếp df [ 'col_3'] = df [ 'col_1'] + df [ 'col_2'])
bigbug

1
Tôi đã tìm thấy một câu hỏi và trả lời liên quan ở dưới url, nhưng vấn đề của tôi là tính toán một cột mới theo hai cột hiện có, không phải 2 từ 1. stackoverflow.com/questions/12356501/
con bọ hung

Tôi nghĩ rằng stackoverflow.com/a/52854800/5447172 của tôi trả lời câu hỏi này theo cách Pythonic / Pandanic nhất, không có cách giải quyết hoặc lập chỉ mục số. Nó tạo ra chính xác đầu ra bạn yêu cầu trong ví dụ của bạn.
ajrwhite

Câu trả lời:


291

Đây là một ví dụ sử dụng applytrên khung dữ liệu mà tôi đang gọi axis = 1.

Lưu ý sự khác biệt là thay vì cố gắng truyền hai giá trị cho hàm f, hãy viết lại hàm để chấp nhận đối tượng Sê-ri gấu trúc, sau đó lập chỉ mục Sê-ri để nhận các giá trị cần thiết.

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

Tùy thuộc vào trường hợp sử dụng của bạn, đôi khi rất hữu ích để tạo một groupđối tượng gấu trúc , và sau đó sử dụng applytrên nhóm.


Có, tôi đã thử sử dụng áp dụng, nhưng không thể tìm thấy biểu thức cú pháp hợp lệ. Và nếu mỗi hàng của df là duy nhất, vẫn sử dụng nhóm?
bigbird

Đã thêm một ví dụ cho câu trả lời của tôi, hy vọng đây là điều bạn đang tìm kiếm. Nếu không, vui lòng cung cấp một hàm ví dụ cụ thể hơn vì sumđược giải quyết thành công bằng bất kỳ phương thức nào được đề xuất cho đến nay.
Aman

1
Bạn vui lòng dán mã của bạn? Tôi viết lại hàm: def get_sublist (x): return mylist [x [1]: x [2] + 1] và df ['col_3'] = df.apply (get_sublist, angle = 1) cho 'ValueError: operands có thể không được phát cùng với hình dạng (2) (3) '
bigbird

3
@Aman: với Pandas phiên bản 0.14.1 (và có thể sớm hơn), sử dụng cũng có thể sử dụng biểu thức lambda. Đưa ra dfđối tượng bạn đã xác định, một cách tiếp cận khác (có kết quả tương đương) là df.apply(lambda x: x[0] + x[1], axis = 1).
Hân hoan

2
@CanCeylan bạn chỉ có thể sử dụng tên cột trong hàm thay vì chỉ mục sau đó bạn không cần lo lắng về việc thay đổi thứ tự hoặc lấy chỉ mục theo tên, ví dụ: xem stackoverflow.com/questions/13021654/iêu
Davos

165

Có một cách rõ ràng, một dòng để làm điều này trong Pandas:

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

Điều này cho phép flà một hàm do người dùng xác định có nhiều giá trị đầu vào và sử dụng các tên cột (an toàn) thay vì các chỉ số số (không an toàn) để truy cập các cột.

Ví dụ với dữ liệu (dựa trên câu hỏi ban đầu):

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)

Đầu ra của 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]

Nếu tên cột của bạn chứa khoảng trắng hoặc chia sẻ tên với thuộc tính khung dữ liệu hiện có, bạn có thể lập chỉ mục bằng dấu ngoặc vuông:

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

2
Lưu ý, nếu sử dụng axis=1và cột bạn được gọi, namenó sẽ không thực sự trả về dữ liệu cột của bạn mà là index. Tương tự như nhận được nametrong a groupby(). Tôi đã giải quyết điều này bằng cách đổi tên cột của tôi.
Tom Hemmes

2
ĐÂY LÀ NÓ! Tôi chỉ không nhận ra rằng bạn có thể chèn các hàm do người dùng xác định với nhiều tham số đầu vào vào lambdas. Điều quan trọng cần lưu ý (tôi nghĩ) rằng bạn đang sử dụng DF.apply () thay vì Series.apply (). Điều này cho phép bạn lập chỉ mục df bằng cách sử dụng hai cột bạn muốn và chuyển toàn bộ cột vào hàm, nhưng vì bạn đang sử dụng áp dụng (), nên nó áp dụng hàm theo kiểu phần tử theo toàn bộ cột. Xuất sắc! Cảm ơn đã đăng bài viết!
Dữ liệu-phile

1
CUỐI CÙNG! Bạn đã cứu ngày của tôi!
Mysterio

Tôi tin rằng cách được đề xuất để làm điều này là df.loc [:, 'new col'] = df.apply .....
valearner

@valearner Tôi không nghĩ có bất kỳ lý do nào để thích .loctrong ví dụ này. Có thể cần thiết nếu bạn thích ứng điều này với cài đặt vấn đề khác (ví dụ: làm việc với các lát).
ajrwhite

86

Một giải pháp đơn giản là:

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

1
Câu trả lời này khác với cách tiếp cận trong câu hỏi như thế nào: df ['col_3'] = df [['col_1', 'col_2']]. áp dụng (f) chỉ để xác nhận, cách tiếp cận trong câu hỏi không hiệu quả vì người đăng không chỉ định trục này = 1, mặc định là trục = 0?
Mất1

1
Câu trả lời này tương đương với câu trả lời của @ Anman nhưng hơi lắt léo. Anh ta đang xây dựng một hàm ẩn danh cần một lần lặp và giải nén nó trước khi chuyển nó sang hàm f.
tiao

39

Một câu hỏi thú vị! câu trả lời của tôi như dưới đây:

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

Đầu ra:

  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]

Tôi đã thay đổi tên cột thành ID, J1, J2, J3 để đảm bảo ID <J1 <J2 <J3, để cột hiển thị theo đúng trình tự.

Một phiên bản ngắn gọn hơn:

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

Phương pháp bạn đang tìm kiếm là Series.combine. Tuy nhiên, có vẻ như một số chăm sóc phải được thực hiện xung quanh các kiểu dữ liệu. Trong ví dụ của bạn, bạn sẽ (như tôi đã làm khi kiểm tra câu trả lời) gọi một cách ngây thơ

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

Tuy nhiên, điều này ném lỗi:

ValueError: setting an array element with a sequence.

Dự đoán tốt nhất của tôi là dường như hy vọng kết quả sẽ cùng loại với chuỗi gọi phương thức (df.col_1 ở đây). Tuy nhiên, các công việc sau đây:

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

Cách bạn đã viết f nó cần hai đầu vào. Nếu bạn nhìn vào thông báo lỗi, nó báo rằng bạn không cung cấp hai đầu vào cho f, chỉ một. Thông báo lỗi là chính xác.
Sự không phù hợp là do df [['col1', 'col2']] trả về một khung dữ liệu duy nhất có hai cột, không phải hai cột riêng biệt.

Bạn cần thay đổi f của mình để nó có một đầu vào duy nhất, giữ khung dữ liệu trên làm đầu vào, sau đó chia nó thành x, y bên trong thân hàm. Sau đó làm bất cứ điều gì bạn cần và trả lại một giá trị duy nhất.

Bạn cần chữ ký hàm này vì cú pháp là .apply (f) Vì vậy, f cần lấy điều duy nhất = dataframe chứ không phải hai điều đó là điều mà f hiện tại của bạn mong đợi.

Vì bạn chưa cung cấp phần thân của tôi nên tôi không thể giúp chi tiết nữa - nhưng điều này sẽ cung cấp lối thoát mà không thay đổi cơ bản mã của bạn hoặc sử dụng một số phương pháp khác thay vì áp dụng


12

Tôi sẽ bỏ phiếu cho np.vectorize. Nó cho phép bạn chỉ bắn qua x số cột và không xử lý khung dữ liệu trong hàm, vì vậy thật tuyệt vời cho các chức năng bạn không kiểm soát hoặc thực hiện một số việc như gửi 2 cột và hằng số vào một hàm (ví dụ: 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
Điều này không thực sự trả lời câu hỏi bằng cách sử dụng gấu trúc.
mnky9800n

18
Câu hỏi đặt ra là "Cách áp dụng một hàm cho hai cột của khung dữ liệu Pandas" chứ không phải "Cách áp dụng một hàm cho hai cột của khung dữ liệu Pandas chỉ sử dụng các phương thức Pandas" và numpy là một phụ thuộc của Pandas vì vậy dù sao bạn cũng phải cài đặt nó vì vậy điều này có vẻ như một sự phản đối kỳ lạ.
Trae Wallace

12

Trả về danh sách từ applylà một hoạt động nguy hiểm vì đối tượng kết quả không được đảm bảo là Sê-ri hoặc Khung dữ liệu. Và ngoại lệ có thể được nêu ra trong một số trường hợp nhất định. Hãy đi qua một ví dụ đơn giản:

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

Có ba kết quả có thể xảy ra với việc trả về một danh sách từ apply

1) Nếu độ dài của danh sách được trả về không bằng số lượng cột, thì một loạt danh sách được trả về.

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) Khi độ dài của danh sách được trả về bằng số lượng cột thì DataFrame được trả về và mỗi cột nhận giá trị tương ứng trong danh sách.

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) Nếu độ dài của danh sách được trả về bằng số lượng cột cho hàng đầu tiên nhưng có ít nhất một hàng trong đó danh sách có số phần tử khác với số cột mà ValueError được nâng lên.

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)

Trả lời vấn đề mà không áp dụng

Sử dụng applyvới trục = 1 là rất chậm. Có thể có được hiệu suất tốt hơn nhiều (đặc biệt là trên các bộ dữ liệu lớn hơn) với các phương pháp lặp cơ bản.

Tạo khung dữ liệu lớn hơn

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

Thời gian

# 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 trả lời

%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
Thật tuyệt khi thấy câu trả lời rất chi tiết từ nơi có thể học hỏi.
Andrea Moro

7

Tôi chắc chắn rằng điều này không nhanh bằng các giải pháp sử dụng các thao tác Pandas hoặc Numpy, nhưng nếu bạn không muốn viết lại chức năng của mình, bạn có thể sử dụng bản đồ. Sử dụng dữ liệu mẫu ban đầu -

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

Chúng ta có thể chuyển nhiều đối số như chúng ta muốn vào hàm theo cách này. Đầu ra là những gì chúng tôi muốn

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
Điều này thực sự nhanh hơn nhiều những câu trả lời được sử dụng applyvớiaxis=1
Ted Petrou

2

Ví dụ của tôi cho câu hỏi của bạn:

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

Nếu bạn có một tập dữ liệu khổng lồ, thì bạn có thể sử dụng một cách dễ dàng nhưng nhanh hơn (thời gian thực hiện) để thực hiện việc này bằng cách sử dụng 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

Tôi cho rằng bạn không muốn thay đổi get_sublistchức năng và chỉ muốn sử dụng applyphương thức của DataFrame để thực hiện công việc. Để có được kết quả bạn muốn, tôi đã viết hai hàm trợ giúp: get_sublist_listunlist. Như tên hàm gợi ý, đầu tiên hãy lấy danh sách danh sách con, trích xuất danh sách phụ đó từ danh sách đó. Cuối cùng, chúng ta cần gọi applyhàm để áp dụng hai hàm đó chodf[['col_1','col_2']] DataFrame sau đó.

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

Nếu bạn không sử dụng []để đóng get_sublisthàm, thì get_sublist_listhàm sẽ trả về một danh sách đơn giản, nó sẽ tăng lên ValueError: could not broadcast input array from shape (3) into shape (2), như @Ted Petrou đã đề cập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.