如何将dplyr与变量用于列名

时间:2017-04-14 06:06:13

标签: r dynamic dplyr

我希望通过使用变量传递列名来动态地使用dplyr变异数据帧的一列。例如,我有以下数据框:

DF <- data.frame(A = 1:10, 
                 B = 11:20, 
                 C = c(23:30, 21:22), 
                 D = c(39:40, 31:38), 
                 E = c(TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE))

DF
    A  B  C  D    E
1   1 11 23 39 TRUE
2   2 12 24 40 TRUE
3   3 13 25 31 TRUE
4   4 14 26 32 TRUE
5   5 15 27 33 TRUE
6   6 16 28 34 TRUE
7   7 17 29 35 TRUE
8   8 18 30 36 TRUE
9   9 19 21 37 TRUE
10 10 20 22 38 TRUE

现在我想将那些

行的列E的值更改为FALSE
  • B列中的值小于0.1分位数OR或大于B列中所有值的0.9分位数
  • 如果相同条件适用于C或C列
  • 如果相同的条件适用于D列

因此结果数据框应如下所示:

    A  B  C  D    E
1   1 11 23 39 FALSE
2   2 12 24 40 FALSE
3   3 13 25 31 FALSE
4   4 14 26 32 TRUE
5   5 15 27 33 TRUE
6   6 16 28 34 TRUE
7   7 17 29 35 TRUE
8   8 18 30 36 FALSE
9   9 19 21 37 FALSE
10 10 20 22 38 FALSE

我编写了一个脚本,它只基于一行来改变数据框,并且效果很好:

DF <- DF %>%
    dplyr::mutate(E = if_else(B < quantile(B, 0.9), E, FALSE)) %>%
    dplyr::mutate(E = if_else(B > quantile(B, 0.1), E, FALSE))

DF
    A  B  C  D     E
1   1 11 23 39 FALSE
2   2 12 24 40  TRUE
3   3 13 25 31  TRUE
4   4 14 26 32  TRUE
5   5 15 27 33  TRUE
6   6 16 28 34  TRUE
7   7 17 29 35  TRUE
8   8 18 30 36  TRUE
9   9 19 21 37  TRUE
10 10 20 22 38 FALSE

然而,当我试图让它变得动态时,它不起作用:

for (col in cols) {
  DF <- DF %>%
      dplyr::mutate_(E = if_else(col < quantile(col, 0.9), E, FALSE)) %>%
      dplyr::mutate_(E = if_else(col > quantile(col, 0.1), E, FALSE))
}
Error in (1 - h) * qs[i] : non-numeric argument to binary operator

我该如何解决这个问题?

3 个答案:

答案 0 :(得分:1)

我们可以使用interp

library(dplyr)
library(lazyeval)
for (col in cols) {
  DF <- DF %>%
            mutate_(E = interp(~if_else(Col<quantile(Col, 0.9), E, FALSE),
                                        Col=as.name(col))) %>%
            mutate_(E = interp(~if_else(Col>quantile(Col, 0.1), E, FALSE),
                                        Col = as.name(col)))
        } 

DF
#    A  B  C  D     E
#1   1 11 23 39 FALSE
#2   2 12 24 40 FALSE
#3   3 13 25 31 FALSE
#4   4 14 26 32  TRUE
#5   5 15 27 33  TRUE
#6   6 16 28 34  TRUE
#7   7 17 29 35  TRUE
#8   8 18 30 36 FALSE
#9   9 19 21 37 FALSE
#10 10 20 22 38 FALSE

其中

cols <- names(DF)[2:4]

更新

如果我们还需要传递'E'列

for (col in cols) {
    DF <- DF %>%
        mutate_(.dots = setNames(list(interp(~if_else(Col < quantile(Col, 0.9), Col2, FALSE), 
                    .values = list(Col= as.name(col), Col2 = as.name(names(DF)[5])))), names(DF)[5])) %>%
        mutate_(.dots = setNames(list(interp(~if_else(Col > quantile(Col, 0.1), Col2, FALSE), 
                    .values = list(Col= as.name(col), Col2 = as.name(names(DF)[5])))), names(DF)[5]))
}
 DF
#   A  B  C  D     E
#1   1 11 23 39 FALSE
#2   2 12 24 40 FALSE
#3   3 13 25 31 FALSE
#4   4 14 26 32  TRUE
#5   5 15 27 33  TRUE
#6   6 16 28 34  TRUE
#7   7 17 29 35  TRUE
#8   8 18 30 36 FALSE
#9   9 19 21 37 FALSE

UPDATE2

使用devel版本的dplyr(很快就会发布0.6.0),我们也可以将变量作为quosures传递,并通过取消引用mutate进行评估

 varN <- quo(E)
 cols <- rlang::parse_quosures(paste(names(DF)[2:4], collapse=";"))
 varN1 <- quo_name(varN)

 for(i in seq_along(cols)) {
    DF <- DF %>%
         mutate(!!varN1 := if_else((!!cols[[i]]) < quantile((!!cols[[i]]), 0.9),
                      (!!varN), FALSE),
                !!varN1 := if_else((!!cols[[i]]) > quantile((!!cols[[i]]), 0.1),
                      (!!varN), FALSE))  


 }  
DF
#    A  B  C  D     E
#1   1 11 23 39 FALSE
#2   2 12 24 40 FALSE
#3   3 13 25 31 FALSE
#4   4 14 26 32  TRUE
#5   5 15 27 33  TRUE
#6   6 16 28 34  TRUE
#7   7 17 29 35  TRUE
#8   8 18 30 36 FALSE
#9   9 19 21 37 FALSE
#10 10 20 22 38 FALSE

或另一个选项是data.table

library(data.table) 
setDT(DF)[,  E := Reduce(`&`, lapply(.SD, function(x) x < quantile(x, 0.9) & 
             x > quantile(x, .1))), .SDcols = 2:4]

 DF
 #    A  B  C  D     E
 #1:  1 11 23 39 FALSE
 #2:  2 12 24 40 FALSE
 #3:  3 13 25 31 FALSE
 #4:  4 14 26 32  TRUE
 #5:  5 15 27 33  TRUE
 #6:  6 16 28 34  TRUE
 #7:  7 17 29 35  TRUE
 #8:  8 18 30 36 FALSE
 #9:  9 19 21 37 FALSE
 #10:10 20 22 38 FALSE

或仅使用base R个功能

DF$E <- Reduce(`&`, lapply(DF[2:4], function(x) x < quantile(x, 0.9) & x > quantile(x, .1)))

DF
#    A  B  C  D     E
#1   1 11 23 39 FALSE
#2   2 12 24 40 FALSE
#3   3 13 25 31 FALSE
#4   4 14 26 32  TRUE
#5   5 15 27 33  TRUE
#6   6 16 28 34  TRUE
#7   7 17 29 35  TRUE
#8   8 18 30 36 FALSE
#9   9 19 21 37 FALSE
#10 10 20 22 38 FALSE

注意:没有使用外部包

注2:所有选项都返回相同的输出

答案 1 :(得分:0)

您可以直接在mutate

中进行迭代
DF %>% mutate(E = apply(sapply(list(B, C, D), 
                               function(x){x < quantile(x, .9) & x > quantile(x, .1)}), 
                        1, all))
##     A  B  C  D     E
## 1   1 11 23 39 FALSE
## 2   2 12 24 40 FALSE
## 3   3 13 25 31 FALSE
## 4   4 14 26 32  TRUE
## 5   5 15 27 33  TRUE
## 6   6 16 28 34  TRUE
## 7   7 17 29 35  TRUE
## 8   8 18 30 36 FALSE
## 9   9 19 21 37 FALSE
## 10 10 20 22 38 FALSE

或使用purrr,

library(tidyverse)

DF %>% mutate(E = list(B, C, D) %>%
                      map(~.x < quantile(.x, .9) & .x > quantile(.x, .1)) %>% 
                      pmap_lgl(all))

或全力投入矩阵:

DF %>% mutate(E = cbind(B, C, D) %>% 
                      apply(2, function(x){x < quantile(x, .9) & x > quantile(x, .1)}) %>% 
                      apply(1, all))

所有人都回报同样的事情。

如果您愿意,可以用between替换不平等,例如between(x, quantile(x, .1), quantile(x, .9)),但因为它被定义为x >= left & x <= right,当边界重要时,它可能会有所不同。

答案 2 :(得分:0)

最简单的解决方案。使用 get() 基础 R 函数 -

for (col in cols) {
DF <- DF %>%
    dplyr::mutate(E = if_else(get(col) < quantile(get(col), 0.9), E, FALSE)) %>%
    dplyr::mutate(E = if_else(get(col) > quantile(get(col), 0.1), E, FALSE))
}