使用 dplyr 在 R 中自动创建变量的最佳方法

Best way to automate variable creation in R using dplyr

df <- as.data.frame(cbind(c(1:10), c(15, 70, 29, 64, 57, 29, 10, 80,81, 71)))

   V1 V2
1   1 15
2   2 70
3   3 29
4   4 64
5   5 57
6   6 29
7   7 10
8   8 80
9   9 81
10 10 71

cuts <- c(5, 10, 90, 95)

我想为所有(在本例中为四个)切割值 x(例如 P5P10P90P95) 表示是否v2 <= x。添加变量的直接方法 "by hand" 不会超出少数范围:

df %<>% 
    mutate( P5 = V2 <=  5) %>% 
    mutate(P10 = V2 <= 10) %>% 
    mutate(P90 = V2 <= 90) %>% 
    mutate(P95 = V2 <= 95)

   V1 V2    P5   P10  P90  P95
1   1 15 FALSE FALSE TRUE TRUE
2   2 70 FALSE FALSE TRUE TRUE
3   3 29 FALSE FALSE TRUE TRUE
4   4 64 FALSE FALSE TRUE TRUE
5   5 57 FALSE FALSE TRUE TRUE
6   6 29 FALSE FALSE TRUE TRUE
7   7 10 FALSE  TRUE TRUE TRUE
8   8 80 FALSE FALSE TRUE TRUE
9   9 81 FALSE FALSE TRUE TRUE
10 10 71 FALSE FALSE TRUE TRUE

显然,要使数据保持 "tidy" 格式,应应用最终的 gather(year, islegal, c(3;6))

我尝试过的另一种方法是

do.call(rbind, lapply(cuts, function(x) { 
                df %>% mutate(year = x, islegal = V2 <= x) 
        })) %>% spread(year, islegal)

   V1 V2     5    10   90   95
1   1 15 FALSE FALSE TRUE TRUE
2   2 70 FALSE FALSE TRUE TRUE
3   3 29 FALSE FALSE TRUE TRUE
4   4 64 FALSE FALSE TRUE TRUE
5   5 57 FALSE FALSE TRUE TRUE
6   6 29 FALSE FALSE TRUE TRUE
7   7 10 FALSE  TRUE TRUE TRUE
8   8 80 FALSE FALSE TRUE TRUE
9   9 81 FALSE FALSE TRUE TRUE
10 10 71 FALSE FALSE TRUE TRUE

显然,我会删除最后的 spread() 以使数据保持 "tidy" 格式。

问题:是否有比第二种方法更好或更通用的使用 {dplyr} 的方法来自动创建变量(像这里的类似分位数的截止值,或虚拟变量或类似),不需要像第一种方法那样显式输入 cuts 的内容?

如果您想 "programatically" 使用 dplyr,您应该查看 "standard evaluation" 替代函数的常用版本。请参阅非标准评估小插图 (vignette("nse", "dplyr"))。

基本上除了 mutate 函数之外,还有一个 mutate_ 函数允许您指定转换列表。在你的情况下,你可以用这样的东西建立你的列表

cuts <- c(5,10,90,95)
mymutate <- setNames(lapply(cuts , function(x) 
     lazyeval::interp(~V2<=x, x=x)), paste0("P", cuts ))

然后你可以用

进行转换
df %>% mutate_(.dots=mymutate )

#    V1 V2    P5   P10  P90  P95
# 1   1 15 FALSE FALSE TRUE TRUE
# 2   2 70 FALSE FALSE TRUE TRUE
# 3   3 29 FALSE FALSE TRUE TRUE
# 4   4 64 FALSE FALSE TRUE TRUE
# 5   5 57 FALSE FALSE TRUE TRUE
# 6   6 29 FALSE FALSE TRUE TRUE
# 7   7 10 FALSE  TRUE TRUE TRUE
# 8   8 80 FALSE FALSE TRUE TRUE
# 9   9 81 FALSE FALSE TRUE TRUE
# 10 10 71 FALSE FALSE TRUE TRUE

当然你不需要 dplyr 来做这么简单的事情。

names(cuts) <- paste0("p", cuts)
data.frame(df, lapply(cuts, function(x) df$V2 <= x))

   V1 V2    p5   p10  p90  p95
1   1 15 FALSE FALSE TRUE TRUE
2   2 70 FALSE FALSE TRUE TRUE
3   3 29 FALSE FALSE TRUE TRUE
4   4 64 FALSE FALSE TRUE TRUE
5   5 57 FALSE FALSE TRUE TRUE
6   6 29 FALSE FALSE TRUE TRUE
7   7 10 FALSE  TRUE TRUE TRUE
8   8 80 FALSE FALSE TRUE TRUE
9   9 81 FALSE FALSE TRUE TRUE
10 10 71 FALSE FALSE TRUE TRUE

如果您打算最终将数据转换为整齐的数据,您可以简单地从一个开始:

library(dplyr)
df <- as.data.frame(cbind(c(1:10), c(15, 70, 29, 64, 57, 29, 10, 80,81, 71)))
cuts <- data_frame(P=c(5, 10, 90, 95))

p_df <- df %>% tidyr::crossing(cuts) %>%
  mutate(flag=V2<=P)
p_df

#   V1 V2  P  flag
#1   1 15  5 FALSE
#2   1 15 10 FALSE
#3   1 15 90  TRUE
#4   1 15 95  TRUE
#5   2 70  5 FALSE
#...

如果原来的格式真的是你想要的,tidyr::spread结果

p_df %>% 
  tidyr::spread(P, flag, sep="")
#   V1 V2    P5   P10  P90  P95
#1   1 15 FALSE FALSE TRUE TRUE
#2   2 70 FALSE FALSE TRUE TRUE
#3   3 29 FALSE FALSE TRUE TRUE
#4   4 64 FALSE FALSE TRUE TRUE
#5   5 57 FALSE FALSE TRUE TRUE
#6   6 29 FALSE FALSE TRUE TRUE
#7   7 10 FALSE  TRUE TRUE TRUE
#8   8 80 FALSE FALSE TRUE TRUE
#9   9 81 FALSE FALSE TRUE TRUE
#10 10 71 FALSE FALSE TRUE TRUE