Tôi sử dụng chủ yếu là ggplot2
để hiển thị. Thông thường, tôi thiết kế cốt truyện tương tác (tức là mã ggplot2
thô sử dụng NSE) nhưng cuối cùng, tôi thường kết thúc gói mã đó thành hàm nhận dữ liệu và biến số . Và điều này luôn luôn là một chút về một cơn ác mộng .Đánh giá lười biếng cho ggplot2 bên trong một hàm
Vì vậy, các tình huống điển hình trông như thế này. Tôi có một số dữ liệu và tôi tạo một âm mưu cho nó (trong trường hợp này, một ví dụ rất đơn giản, sử dụng tập dữ liệu mpg đi kèm với ggplot2
).
library(ggplot2)
data(mpg)
ggplot(data = mpg,
mapping = aes(x = class, y = hwy)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
Và khi tôi hoàn thành việc thiết kế cốt truyện, tôi thường muốn sử dụng nó cho biến khác nhau hoặc dữ liệu, vv Vì vậy, tôi tạo ra một chức năng tiếp nhận dữ liệu và các biến cho cốt truyện như các đối số . Nhưng do NSE, nó là không dễ dàng như viết tiêu đề chức năng và sau đó sao chép/dán và thay thế biến cho đối số hàm. Điều đó sẽ không hoạt động, như hình dưới đây.
mpg <- mpg
plotfn <- function(data, xvar, yvar){
ggplot(data = data,
mapping = aes(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, class, hwy) # Can't find object
## Don't know how to automatically pick scale for object of type function. Defaulting to continuous.
## Warning: restarting interrupted promise evaluation
## Error in eval(expr, envir, enclos): object 'hwy' not found
plotfn(mpg, "class", "hwy") #
Vì vậy, tôi phải quay trở lại và sửa chữa các mã, ví dụ, sử dụng aes_string
intead của aes
sử dụng NSE (trong ví dụ này nó là khá dễ dàng, nhưng cho lô phức tạp hơn, với nhiều biến đổi và lớp, điều này trở thành cơn ác mộng).
plotfn <- function(data, xvar, yvar){
ggplot(data = data,
mapping = aes_string(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, "class", "hwy") # Now this works
Và điều là tôi tìm NSE rất thuận tiện và cũng lazyeval
. Vì vậy, Tôi thích làm một cái gì đó như thế này.
mpg <- mpg
plotfn <- function(data, xvar, yvar){
data_gd <- data.frame(
xvar = lazyeval::lazy_eval(substitute(xvar), data = data),
yvar = lazyeval::lazy_eval(substitute(yvar), data = data))
ggplot(data = data_gd,
mapping = aes(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, class, hwy) # Now this works
plotfn(mpg, "class", "hwy") # This still works
plotfn(NULL, rep(letters[1:4], 250), 1:100) # And even this crazyness works
Điều này cho phép chức năng cốt truyện của tôi rất nhiều tính linh hoạt. Ví dụ: bạn có thể chuyển các tên biến được trích dẫn hoặc không được trích dẫn và thậm chí cả dữ liệu trực tiếp thay vì tên biến (loại lạm dụng đánh giá lười biếng).
Nhưng điều này có một vấn đề lớn. Không thể sử dụng hàm theo chương trình.
dynamically_changing_xvar <- "class"
plotfn(mpg, dynamically_changing_xvar, hwy)
## Error in eval(expr, envir, enclos): object 'dynamically_changing_xvar' not found
# This does not work, because it never finds the object
# dynamically_changing_xvar in the data, and it does not get evaluated to
# obtain the variable name (class)
Vì vậy, tôi không thể sử dụng các vòng lặp (ví dụ lapply) để tạo ra những âm mưu tương tự cho kết hợp khác nhau của các biến, hoặc dữ liệu. Vì vậy, tôi đã nghĩ đến việc lạm dụng nhiều hơn, đánh giá tiêu chuẩn, không chuẩn và tiêu chuẩn, và cố gắng kết hợp tất cả để tôi có cả hai, sự linh hoạt được hiển thị ở trên và khả năng sử dụng hàm theo chương trình. Về cơ bản, điều tôi làm là sử dụng tryCatch
trước tiên biểu thức cho mỗi biến và nếu không, để đánh giá biểu thức được phân tích cú pháp .
plotfn <- function(data, xvar, yvar){
data_gd <- NULL
data_gd$xvar <- tryCatch(
expr = lazyeval::lazy_eval(substitute(xvar), data = data),
error = function(e) eval(envir = data, expr = parse(text=xvar))
)
data_gd$yvar <- tryCatch(
expr = lazyeval::lazy_eval(substitute(yvar), data = data),
error = function(e) eval(envir = data, expr = parse(text=yvar))
)
ggplot(data = as.data.frame(data_gd),
mapping = aes(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, class, hwy) # Now this works, again
plotfn(mpg, "class", "hwy") # This still works, again
plotfn(NULL, rep(letters[1:4], 250), 1:100) # And this crazyness still works
# And now, I can also pass a local variable to the function, that contains
# the name of the variable that I want to plot
dynamically_changing_xvar <- "class"
plotfn(mpg, dynamically_changing_xvar, hwy)
Vì vậy, ngoài tính linh hoạt nói trên, bây giờ tôi có thể sử dụng một lớp lót để tạo ra nhiều ô giống nhau, với các biến số khác nhau (hoặc dữ liệu) .
lapply(c("class", "fl", "drv"), FUN = plotfn, yvar = hwy, data = mpg)
## [[1]]
##
## [[2]]
##
## [[3]]
Mặc dù nó là rất thực tế, tôi nghi ngờ đây không phải là thói quen tốt. Nhưng thực tiễn xấu đến mức nào? Đó là câu hỏi quan trọng của tôi. Những lựa chọn thay thế khác nào tôi có thể sử dụng để có những điều tốt nhất của cả hai thế giới?
Tất nhiên, tôi có thể thấy mẫu này có thể gây ra sự cố. Ví dụ.
# If I have a variable in the global environment that contains the variable
# I want to plot, but whose name is in the data passed to the function,
# then it will use the name of the variable and not its content
drv <- "class"
plotfn(mpg, drv, hwy) # Here xvar on the plot is drv and not class
Và một số (nhiều?) Các vấn đề khác. Nhưng có vẻ như với tôi rằng lợi ích trong điều khoản về tính linh hoạt cú pháp lớn hơn những vấn đề khác. Bất kỳ suy nghĩ về điều này?
Cách tốt nhất là tạo ra một cặp chức năng. Một là NSE, SE khác. Điều này được vạch ra trong 'vignette ('nse')'. Điều này có nghĩa là sử dụng 'aes_' thay vì' aes'. – Axeman
Cảm ơn, ..., vâng, tôi sợ rằng đó sẽ là câu trả lời. Mặc dù tôi thấy những lợi ích của dplyr & co. "Lược đồ đặt tên nhất quán: SE là tên NSE với _ ở cuối", nó luôn luôn làm lỗi tôi phải sử dụng một hàm khác để lập trình và làm việc tương tác. – elikesprogramming