Tại sao `vapply` lại an toàn hơn` sapply`?


84

Tài liệu cho biết

vapplytương tự như sapply, nhưng có loại giá trị trả về được chỉ định trước, vì vậy có thể [...] an toàn hơn khi sử dụng.

Bạn có thể vui lòng giải thích tại sao nó nói chung là an toàn hơn, có thể cung cấp các ví dụ?


Tái bút: Tôi biết câu trả lời và tôi đã có xu hướng tránh sapply. Tôi chỉ ước có một câu trả lời hay ở đây trên SO để tôi có thể chỉ cho đồng nghiệp của mình về nó. Xin vui lòng, không có câu trả lời "đọc hướng dẫn sử dụng".


1
Nó dễ dự đoán hơn, làm cho mã ít mơ hồ hơn và mạnh mẽ hơn. Đặc biệt trong các dự án lớn hơn, nói là một gói lớn, điều này có liên quan.
Paul Hiemstra

1
Các ví dụ hướng dẫn sử dụng vapply cho FUN.VALUE rất phức tạp và đáng sợ đối với người dùng sapply.
jsta

Câu trả lời:


73

Như đã được lưu ý, vapplyhai điều:

  • Cải thiện tốc độ nhẹ
  • Cải thiện tính nhất quán bằng cách cung cấp kiểm tra loại trả lại hạn chế.

Điểm thứ hai là lợi thế lớn hơn, vì nó giúp bắt lỗi trước khi chúng xảy ra và dẫn đến mã mạnh hơn. Việc kiểm tra giá trị trả về này có thể được thực hiện riêng biệt bằng cách sử dụng sapplytheo sau stopifnotđể đảm bảo rằng các giá trị trả về phù hợp với những gì bạn mong đợi, nhưng vapplydễ dàng hơn một chút (nếu hạn chế hơn, vì mã kiểm tra lỗi tùy chỉnh có thể kiểm tra các giá trị trong giới hạn, v.v. ).

Đây là một ví dụ về vapplyviệc đảm bảo kết quả của bạn như mong đợi. Điều này tương đồng với một cái gì đó tôi vừa làm việc trong khi cạo PDF, nơi findDsẽ sử dụngđể khớp với một mẫu trong dữ liệu văn bản thô (ví dụ: tôi muốn có một danh sách splittheo thực thể và một regex để khớp với các địa chỉ trong mỗi thực thể. Đôi khi tệp PDF đã được chuyển đổi không theo thứ tự và sẽ có hai địa chỉ cho một thực thể, đã gây ra sự tồi tệ).

> input1 <- list( letters[1:5], letters[3:12], letters[c(5,2,4,7,1)] )
> input2 <- list( letters[1:5], letters[3:12], letters[c(2,5,4,7,15,4)] )
> findD <- function(x) x[x=="d"]
> sapply(input1, findD )
[1] "d" "d" "d"
> sapply(input2, findD )
[[1]]
[1] "d"

[[2]]
[1] "d"

[[3]]
[1] "d" "d"

> vapply(input1, findD, "" )
[1] "d" "d" "d"
> vapply(input2, findD, "" )
Error in vapply(input2, findD, "") : values must be length 1,
 but FUN(X[[3]]) result is length 2

Như tôi nói với các sinh viên của mình, một phần của việc trở thành một lập trình viên là thay đổi suy nghĩ của bạn từ "lỗi là khó chịu" thành "lỗi là bạn của tôi."

Đầu vào độ dài bằng không
Một điểm liên quan là nếu độ dài đầu vào bằng 0, sapplysẽ luôn trả về một danh sách trống, bất kể kiểu đầu vào là gì. So sánh:

sapply(1:5, identity)
## [1] 1 2 3 4 5
sapply(integer(), identity)
## list()    
vapply(1:5, identity)
## [1] 1 2 3 4 5
vapply(integer(), identity)
## integer(0)

Với vapply, bạn được đảm bảo có một loại đầu ra cụ thể, vì vậy bạn không cần phải viết thêm kiểm tra cho các đầu vào có độ dài bằng không.

Điểm chuẩn

vapply có thể nhanh hơn một chút vì nó đã biết định dạng nó sẽ mong đợi kết quả.

input1.long <- rep(input1,10000)

library(microbenchmark)
m <- microbenchmark(
  sapply(input1.long, findD ),
  vapply(input1.long, findD, "" )
)
library(ggplot2)
library(taRifx) # autoplot.microbenchmark is moving to the microbenchmark package in the next release so this should be unnecessary soon
autoplot(m)

autoplot


15

Các phím bấm bổ sung liên quan có vapplythể giúp bạn tiết kiệm thời gian gỡ lỗi các kết quả khó hiểu sau này. Nếu hàm bạn đang gọi có thể trả về các kiểu dữ liệu khác nhau, vapplythì chắc chắn nên được sử dụng.

Một ví dụ mà bạn nghĩ đến sẽ nằm sqlQuerytrong RODBCgói. Nếu có lỗi khi thực hiện truy vấn, hàm này trả về một charactervectơ có thông báo. Vì vậy, ví dụ: giả sử bạn đang cố gắng lặp lại một vectơ tên bảng tnamesvà chọn giá trị tối đa từ cột số 'NumCol' trong mỗi bảng với:

sapply(tnames, 
   function(tname) sqlQuery(cnxn, paste("SELECT MAX(NumCol) FROM", tname))[[1]])

Nếu tất cả các tên bảng đều hợp lệ, điều này sẽ dẫn đến một numericvectơ. Nhưng nếu một trong các tên bảng xảy ra thay đổi trong cơ sở dữ liệu và truy vấn không thành công, kết quả sẽ bị ép buộc vào chế độ character. Tuy nhiên, vapplyviệc sử dụng với FUN.VALUE=numeric(1)sẽ ngăn lỗi ở đây và ngăn nó xuất hiện ở đâu đó dưới dòng --- hoặc tệ hơn, hoàn toàn không.


13

Nếu bạn luôn muốn kết quả của mình là một cái gì đó cụ thể ... ví dụ như một vectơ logic. vapplyđảm bảo điều này xảy ra nhưng sapplykhông nhất thiết phải làm như vậy.

a<-vapply(NULL, is.factor, FUN.VALUE=logical(1))
b<-sapply(NULL, is.factor)

is.logical(a)
is.logical(b)

4
tôi nghĩ rằng điều rõ ràng nhất để làm là logical(1)trong trường hợp này, như vẻ FALSE như nó đặt một tùy chọn để “OFF” thay vì chỉ định một loại
bay cừu
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.