2011-06-23 7 views
7

thể trùng lặp:
Is R's apply family more than syntactic sugarƯu điểm của chức năng "áp dụng" là gì? Khi nào thì tốt hơn nên sử dụng hơn là "cho" vòng lặp, và khi nào thì không?

Chỉ cần những gì tiêu đề nói. Câu hỏi ngu ngốc, có lẽ, nhưng sự hiểu biết của tôi là khi sử dụng một hàm "áp dụng", việc lặp lại được thực hiện trong mã được biên dịch hơn là trong trình phân tích cú pháp R. Điều này dường như ngụ ý rằng lapply, ví dụ, chỉ nhanh hơn một "cho" vòng lặp nếu có rất nhiều lặp đi lặp lại và mỗi hoạt động là tương đối đơn giản. Ví dụ, nếu một cuộc gọi duy nhất đến một hàm được bao bọc trong lapply mất 10 giây và chỉ có 12 lần lặp lại, tôi sẽ tưởng tượng rằng hầu như không có sự khác biệt nào giữa việc sử dụng "for" và "lapply". Bây giờ tôi nghĩ về nó, nếu chức năng bên trong "lapply" phải được phân tích cú pháp anyway, tại sao nên có bất kỳ lợi ích hiệu suất từ ​​việc sử dụng "lapply" thay vì "cho", trừ khi bạn đang làm một cái gì đó mà được biên dịch chức năng cho (như tổng hợp hoặc nhân, vv)?

Cảm ơn trước!

Josh

+1

Xem chủ đề này: http://stackoverflow.com/q/2275896/429846 –

+1

Và điều này một: http://stackoverflow.com/q/5533246/210673 – Aaron

+1

Ngoài ra còn có một lớn R bài viết Help Desk về điều này từ tháng 5 năm 2008: http://promberger.info/files/rnews-vectorvsloops2008.pdf – DrewConway

Trả lời

12

Có nhiều lý do tại sao người ta có thể thích một chức năng apply gia đình qua một vòng lặp for, hoặc ngược lại.

Thứ nhất, for()apply(), sapply() thường sẽ nhanh như nhau nếu được thi hành chính xác. lapply() hoạt động nhiều hơn trong mã được biên dịch trong nội bộ R so với các mã khác, vì vậy có thể nhanh hơn các hàm đó. Nó xuất hiện lợi thế tốc độ là lớn nhất khi hành động "looping" trên dữ liệu là một phần quan trọng của thời gian tính toán; trong nhiều lần sử dụng hàng ngày nói chung, bạn không có khả năng thu được nhiều từ số tiền lapply() vốn nhanh hơn. Cuối cùng, tất cả những điều này sẽ được gọi là R chức năng để họ cần phải được giải thích và sau đó chạy.

for() vòng lặp thường có thể dễ thực hiện hơn, đặc biệt nếu bạn đến từ nền tảng lập trình nơi vòng lặp phổ biến. Làm việc trong một vòng lặp có thể tự nhiên hơn là buộc tính toán lặp lại thành một trong các hàm gia đình apply. Tuy nhiên, để sử dụng các vòngđúng cách, bạn cần thực hiện thêm một số công việc để thiết lập bộ nhớ và quản lý việc cắm đầu ra của vòng lặp lại với nhau. Các chức năng apply làm điều này cho bạn một cách tự động. Ví dụ .:

IN <- runif(10) 
OUT <- logical(length = length(IN)) 
for(i in IN) { 
    OUT[i] <- IN > 0.5 
} 

đó là một ngớ ngẩn dụ như > là một nhà điều hành vectorised nhưng tôi muốn một cái gì đó để làm cho một điểm, cụ thể là bạn phải quản lý đầu ra. Điều chính là với các vòng for(), bạn luôn luôn phân bổ đủ bộ nhớ để giữ đầu ra trước khi bạn bắt đầu vòng lặp. Nếu bạn không biết bạn sẽ cần bao nhiêu dung lượng lưu trữ, hãy phân bổ một lượng lưu trữ hợp lý và sau đó kiểm tra vòng lặp nếu bạn đã cạn kiệt bộ nhớ đó và sử dụng bộ nhớ lưu trữ lớn khác.

Lý do chính, trong suy nghĩ của tôi, khi sử dụng một trong các dòng chức năng apply là để có mã dễ đọc hơn, thanh lịch hơn. Thay vì quản lý lưu trữ đầu ra và thiết lập vòng lặp (như được hiển thị ở trên), chúng ta có thể để R xử lý và yêu cầu R chạy một hàm trên các tập con dữ liệu của chúng ta.Tốc độ thường là không đưa vào quyết định, ít nhất là đối với tôi. Tôi sử dụng chức năng phù hợp với tình huống tốt nhất và sẽ dẫn đến mã đơn giản, dễ hiểu, vì tôi có nhiều khả năng lãng phí nhiều thời gian hơn tôi tiết kiệm bằng cách luôn chọn chức năng nhanh nhất nếu tôi không nhớ mã đó là gì làm một ngày hoặc một tuần hoặc nhiều hơn sau đó!

apply gia đình tự vay cho hoạt động vô hướng hoặc vectơ. Vòng lặp for() thường sẽ tự vay để thực hiện nhiều thao tác lặp lại sử dụng cùng một chỉ mục i. Ví dụ: tôi đã viết mã sử dụng các vòng for() để làm k - gấp hoặc xác thực chéo khởi động trên các đối tượng. Tôi có lẽ sẽ không bao giờ giải trí với một trong các gia đình apply vì mỗi lần lặp CV cần nhiều thao tác, truy cập vào rất nhiều đối tượng trong khung hiện tại và điền vào một số đối tượng đầu ra giữ đầu ra của các lần lặp.

Đối với điểm cuối cùng, về việc tại sao lapply() có thể có thể nhanh hơn mà for() hoặc apply(), bạn cần phải nhận ra rằng "loop" có thể được thực hiện trong mã R giải thích hoặc trong mã được biên dịch. Có, cả hai vẫn sẽ gọi các hàm R cần được giải thích, nhưng nếu bạn đang thực hiện lặp và gọi trực tiếp từ mã C đã biên dịch (ví dụ: lapply()) thì đó là nơi hiệu suất có thể đến từ hơn apply(). một vòng lặp for() trong mã R thực tế. Xem nguồn cho apply() để thấy rằng nó là một wrapper xung quanh một vòng for(), và sau đó nhìn vào mã cho lapply(), đó là:

> lapply 
function (X, FUN, ...) 
{ 
    FUN <- match.fun(FUN) 
    if (!is.vector(X) || is.object(X)) 
     X <- as.list(X) 
    .Internal(lapply(X, FUN)) 
} 
<environment: namespace:base> 

và bạn sẽ thấy lý do tại sao có thể có một sự khác biệt về tốc độ giữa lapply()for() và các chức năng gia đình khác apply. .Internal() là một trong những cách gọi R được biên dịch mã C được sử dụng bởi chính R. Ngoài thao tác, và kiểm tra độ chính xác trên FUN, toàn bộ tính toán được thực hiện bằng C, gọi hàm R FUN. So sánh điều đó với nguồn cho apply().

+0

Đẹp ! Bạn cũng đã quản lý để thêm thông tin mới ngoài các câu hỏi có liên quan. Cảm ơn, tôi đã học được một vài điều mới. – Aaron

+0

Tuyệt vời, cảm ơn. Tôi muốn làm rõ một điều về lợi ích hiệu suất tiềm năng. Mã đang được thực hiện bên trong lời gọi đến lapply() vẫn sẽ mất nhiều thời gian lặp lại, phải không?Vì vậy, tôi sẽ được chính xác trong nói rằng trong một tình huống mà mỗi iteration chính nó đang thực hiện trên thứ tự của một vài giây hoặc nhiều hơn mà đạt được hiệu suất trên áp dụng() hoặc cho() sẽ là không đáng kể? – Josh

3

Từ Burns' R Inferno (pdf), p25:

Sử dụng một rõ ràng for vòng lặp khi mỗi lặp là một nhiệm vụ không tầm thường. Nhưng vòng lặp đơn giản có thể rõ ràng hơn và được thể hiện rõ ràng bằng cách sử dụng chức năng apply . Có ít nhất một ngoại lệ đối với quy tắc này ... nếu kết quả sẽ là là danh sách và một số thành phần có thể là NULL, thì vòng lặp for là rắc rối (rắc rối lớn) và lapply cho câu trả lời mong đợi.

+0

Không đồng ý hoàn toàn. Nếu việc lặp lại là một nhiệm vụ không tầm thường, bạn nên chuyển khối mã đó thành một hàm thích hợp và sau đó gọi nó bằng * apply(). – geoffjentry