2016-04-17 17 views
6

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?

+1

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

+0

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

Trả lời

2

Extracting chức năng đề xuất của bạn cho rõ ràng:

library(ggplot2) 
data(mpg) 

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") 
} 

một chức năng như vậy thường là khá hữu ích, vì bạn có thể tự do trộn chuỗi, và tên biến trần. Nhưng như bạn nói, nó có thể không phải lúc nào cũng an toàn. Hãy xem xét ví dụ sau đây có liên quan:

class <- "drv" 
Class <- "drv" 
plotfn(mpg, class, hwy) 
plotfn(mpg, Class, hwy) 

Chức năng của bạn sẽ tạo ra gì? Chúng có giống nhau không (chúng không)? Nó không thực sự rõ ràng với tôi những gì sẽ là kết quả. Lập trình với chức năng như vậy có thể cho kết quả không mong muốn, tùy thuộc vào các biến tồn tại trong data và tồn tại trong môi trường. Vì rất nhiều người sử dụng các tên biến như x, xvar hoặc count (mặc dù chúng có lẽ không nên), mọi thứ có thể lộn xộn.

Ngoài ra, nếu tôi muốn bắt buộc một hoặc giải thích khác là class, tôi không thể.

Tôi muốn nói rằng nó tương tự như sử dụng attach: thuận tiện, nhưng tại một số điểm nó có thể cắn bạn ở phía sau của bạn.

Vì vậy, tôi muốn sử dụng một NSE và SE cặp:

plotfn <- function(data, xvar, yvar) { 
    plotfn_(data, 
      lazyeval::lazy_eval(xvar, data = data), 
      lazyeval::lazy_eval(yvar, data = data)) 
) 
} 

plotfn_ <- function(data, xvar, yvar){ 
    ggplot(data = data, 
     mapping = aes_(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 

Tạo số này là thực sự dễ dàng hơn so với chức năng của bạn, tôi nghĩ. Bạn cũng có thể chọn chụp tất cả các đối số một cách uể oải với lazy_dots.

Bây giờ chúng ta có được dễ dàng hơn để dự đoán kết quả khi sử dụng phiên bản SE an toàn:

class <- "drv" 
Class <- "drv" 
plotfn_(mpg, class, 'hwy') 
plotfn_(mpg, Class, 'hwy') 

Phiên bản NSE vẫn bị ảnh hưởng mặc dù:

plotfn(mpg, class, hwy) 
plotfn(mpg, Class, hwy) 

(tôi thấy nó hơi khó chịu mà ggplot2::aes_ doesn cũng không có dây.)

+1

vâng, tôi đồng ý 100% với "Lập trình với chức năng như vậy có thể cho kết quả không mong muốn, tùy thuộc vào biến tồn tại trong dữ liệu và tồn tại trong môi trường.", ..., đôi khi tôi cảm thấy sự tiện lợi của nó lớn hơn nguy cơ bị cắn ở phía sau của tôi. – elikesprogramming

+0

Hai dòng mã cuối cùng không hoạt động khi tôi thử chạy chúng. – student

+0

NSE trong ngăn nắp đã thay đổi, vì vậy rất có thể. – Axeman

Các vấn đề liên quan