2015-01-15 24 views
39

Tôi muốn parameterise việc tính toán sau bằng dplyr mà tìm thấy những giá trị của Sepal.Length có liên quan đến nhiều hơn một giá trị của Sepal.Width:luận Pass để dplyr chức năng

library(dplyr) 

iris %>% 
    group_by(Sepal.Length) %>% 
    summarise(n.uniq=n_distinct(Sepal.Width)) %>% 
    filter(n.uniq > 1) 

Thông thường tôi sẽ viết một cái gì đó như thế này:

not.uniq.per.group <- function(data, group.var, uniq.var) { 
    iris %>% 
     group_by(group.var) %>% 
     summarise(n.uniq=n_distinct(uniq.var)) %>% 
     filter(n.uniq > 1) 
} 

Tuy nhiên, phương pháp này sẽ phát sinh lỗi vì dplyr sử dụng non-standard evaluation. Chức năng này nên được viết như thế nào?

+3

Như một vấn đề về phong cách, tôi sẽ khuyên bạn không nên sử dụng dấu chấm trong các tên trong R hiện đại, ngoại trừ trong tổng số S3. Thật khó hiểu. Quy ước đặt tên được sử dụng (trong số những người khác) bởi 'dplyr' là đẹp hơn nhiều:' names_with_underscores'. –

+1

Tôi biết rằng [Hướng dẫn về phong cách của Hadley Wickham] (http://adv-r.had.co.nz/Style.html) đề xuất ký hiệu gạch dưới, nhưng [Hướng dẫn kiểu Google R] (https: // google -styleguide.googlecode.com/svn/trunk/Rguide.xml) thúc đẩy khoảng thời gian (mặc dù không phải cho các chức năng mà tôi đã thực hiện ở đây). Trong các ngôn ngữ khác, khoảng thời gian được sử dụng để truy cập thành viên (ví dụ 'myArray.length' trong javascript), có xung đột nào khác trong R không? – asnr

+7

Hướng dẫn kiểu Google thường khủng khiếp. Trong trường hợp cụ thể này, vấn đề là nó dẫn đến sự mơ hồ với các phương thức S3: là 'some.class.method' một phương thức' some' của lớp 'class.method' hoặc nó là phương thức' some.class' của lớp ' phương thức'? Hơn nữa, nó dẫn đến các tên không nhất quán khi các phần của mã của bạn được triển khai trong C (++), vì các phần đó không hỗ trợ dấu chấm trong tên, yêu cầu ánh xạ tên hàm phụ trợ cho các tên R khác nhau. –

Trả lời

37

Bạn cần phải sử dụng các phiên bản đánh giá tiêu chuẩn của dplyr chức năng (chỉ cần thêm '_' với tên chức năng, tức là. group_by_ & summarise_) và vượt qua chuỗi để chức năng của bạn, mà bạn thì cần phải biến thành các biểu tượng. Để tham số hóa đối số của summaryise_, bạn sẽ cần phải sử dụng interp(), được xác định trong gói lazyeval. Cụ thể:

library(dplyr) 
library(lazyeval) 

not.uniq.per.group <- function(df, grp.var, uniq.var) { 
    df %>% 
     group_by_(grp.var) %>% 
     summarise_(n_uniq=interp(~n_distinct(v), v=as.name(uniq.var))) %>% 
     filter(n_uniq > 1) 
} 

not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width") 

Xem dplyrvignette để đánh giá tiêu chuẩn không cho biết thêm chi tiết.

+1

Bạn không thực sự cần 'as.name' ở đây, phải không? –

+2

Bạn dường như cần nó trong cuộc gọi 'interp'. –

+0

Cảm ơn @Konrad, tôi đã xóa cuộc gọi đầu tiên thành 'as.name', lần thứ hai (như @BondedDust chỉ ra) có vẻ cần thiết. – asnr

2

Tôi đã viết một hàm trong quá khứ làm điều gì đó tương tự với những gì bạn đang làm, ngoại trừ việc nó khám phá tất cả các cột bên ngoài khóa chính và tìm nhiều giá trị duy nhất cho mỗi nhóm.

find_dups = function(.table, ...) { 
    require(dplyr) 
    require(tidyr) 
    # get column names of primary key 
    pk <- .table %>% select(...) %>% names 
    other <- names(.table)[!(names(.table) %in% pk)] 
    # group by primary key, 
    # get number of rows per unique combo, 
    # filter for duplicates, 
    # get number of distinct values in each column, 
    # gather to get df of 1 row per primary key, other column, 
    # filter for where a columns have more than 1 unique value, 
    # order table by primary key 
    .table %>% 
    group_by(...) %>% 
    mutate(cnt = n()) %>% 
    filter(cnt > 1) %>% 
    select(-cnt) %>% 
    summarise_each(funs(n_distinct)) %>% 
    gather_('column', 'unique_vals', other) %>% 
    filter(unique_vals > 1) %>% 
    arrange(...) %>% 
    return 
    # Final dataframe: 
    ## One row per primary key and column that creates duplicates. 
    ## Last column indicates how many unique values of 
    ## the given column exist for each primary key. 
} 

Chức năng này cũng làm việc với các nhà điều hành đường ống:

dat %>% find_dups(key1, key2) 
2

Bạn có thể tránh lazyeval bằng cách sử dụng do để gọi một chức năng ẩn danh và sau đó sử dụng get. Giải pháp này có thể được sử dụng rộng rãi hơn để sử dụng nhiều tập hợp. Tôi thường viết riêng hàm.

library(dplyr) 

not.uniq.per.group <- function(df, grp.var, uniq.var) { 
    df %>% 
    group_by_(grp.var) %>% 
    do((function(., uniq.var) { 
     with(., data.frame(n_uniq = n_distinct(get(uniq.var)))) 
    }  
)(., uniq.var)) %>% 
    filter(n_uniq > 1) 
} 

not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width") 
7

Trong phiên bản devel của dplyr (sớm được phát hành 0.6.0), chúng tôi cũng có thể tận dụng cú pháp hơi khác nhau để thông qua các biến.

f1 <- function(df, grp.var, uniq.var) { 
    grp.var <- enquo(grp.var) 
    uniq.var <- enquo(uniq.var) 

    df %>% 
     group_by(!!grp.var) %>% 
     summarise(n_uniq = n_distinct(!!uniq.var)) %>% 
     filter(n_uniq >1) 


} 

res2 <- f1(iris, Sepal.Length, Sepal.Width) 
res1 <- not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width") 
identical(res1, res2) 
#[1] TRUE 

Đây enquo mất các đối số và trả về giá trị như một quosure (tương tự để thay thế trong cơ sở R) bằng cách đánh giá các đối số chức năng uể oải và bên trong Tóm tắt, chúng tôi yêu cầu nó để unquote (!! hoặc UQ) để nó được đánh giá.

+0

nếu tôi nhận được 'Sepal.Length' và' Sepal.Width' từ danh sách, nó không hoạt động vì nó sẽ ở định dạng ''Sepal.Length" 'và' "Sepal.Width" 'cái gì có thể Tôi làm gì? – KillerSnail

+0

@KillerSnail Bạn nên đăng câu hỏi mới vì giải pháp này dành riêng cho vấn đề được đề cập trong bài đăng của OP – akrun

+0

Câu hỏi trên từ @KillerSnail về bản chất là câu hỏi tôi vừa hỏi ở đây: https://stackoverflow.com/questions/46310123/ r-dplyr-hoạt động-on-a-cột-được biết-chỉ-by-của-string-name – bmosov01

7

Giống như phiên bản dplyr cũ tối đa 0,5, dplyr mới có cơ sở cho cả đánh giá chuẩn (SE) và đánh giá không chuẩn (NSE). Nhưng chúng được thể hiện khác so với trước đây.

Nếu bạn muốn có chức năng NSE, bạn pass bare expressions and use enquo to capture them as quosures. Nếu bạn muốn có hàm SE, bạn bỏ qua enquo và chỉ cần chuyển trực tiếp các ký hiệu (hoặc ký hiệu). Dưới đây là giải pháp SE cho câu hỏi:

library(tidyverse) 
library(rlang) 
f1 <- function(df, grp.var, uniq.var) { 

    df %>% 
     group_by(!!grp.var) %>% 
     summarise(n_uniq = n_distinct(!!uniq.var)) %>% 
     filter(n_uniq > 1) 
} 

a <- f1(iris, quo(Sepal.Length), quo(Sepal.Width)) 
b <- f1(iris, sym("Sepal.Length"), sym("Sepal.Width")) 
identical(a, b) 
#> [1] TRUE 

Lưu ý cách phiên bản SE cho phép bạn làm việc với đối số chuỗi - chỉ biến chúng thành những biểu tượng đầu tiên sử dụng sym(). Để biết thêm thông tin, hãy xem programming with dplyr họa tiết.

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