dplyr +" meta" -columns:当列包含要使用的其他列的名称而不是数据时

时间:2015-05-16 02:19:10

标签: r dplyr

我想知道以下问题是否在dplyr中有一个优雅的解决方案。

要提供简单的可重现示例,请考虑以下data.frame:

df <- data.frame( a=1:5, b=2:6, c=3:7,
                  ref=c("a","a","b","b","c"), 
                  stringsAsFactors = FALSE )

此处abc是常规数字变量,而ref用于引用哪个列是&#34; main&#34;该观察的价值。例如:

  a b c ref
1 1 2 3   a
2 2 3 4   a
3 3 4 5   b
4 4 5 6   b
5 5 6 7   c

例如,对于观察3,ref==b以及列b包含值。对于观察1,ref==a以及列a包含主值。

拥有此data.frame的问题是使用dplyr为每个观察创建一个main值的新列。

  a b c ref main
1 1 2 3   a    1
2 2 3 4   a    2
3 3 4 5   b    4
4 4 5 6   b    5
5 5 6 7   c    7

我可能需要使用dplyr,因为这一个操作是更长的dplyr %>%数据转换链的一部分。

4 个答案:

答案 0 :(得分:6)

这是一种简单,快捷的方式,可以让您坚持dplyr链接:

require(data.table)
df %>% setDT %>% .[,main:=get(ref),by=ref]
#    a b c ref main
# 1: 1 2 3   a    1
# 2: 2 3 4   a    2
# 3: 3 4 5   b    4
# 4: 4 5 6   b    5
# 5: 5 6 7   c    7

感谢@akrun以最快的方式和基准测试来展示它(请参阅他的回答)。

setDT修改了df的类,因此您不必在将来的链中再次转换为data.table

转换应该适用于链中的任何未来代码,但dplyrdata.table都在积极开发中,因此为了安全起见,可以使用

df %>% data.table %>% .[,main:=get(ref),by=ref]

答案 1 :(得分:5)

我们可以在base R中使用行/列索引执行此操作。我们使用matchcbind获取带有行索引(1:nrow(df))的列索引并提取元素。索引速度非常快。

df$main <- df[-4][cbind(1:nrow(df),match(df$ref,names(df)[-4]))]
df
#    a b c ref main
#1 1 2 3   a    1
#2 2 3 4   a    2
#3 3 4 5   b    4
#4 4 5 6   b    5
#5 5 6 7   c    7

类似的dplyr链是

df %>% 
  `[[<-.data.frame`(.,"main",value=.[-4][
           cbind(1:nrow(.),match(.$ref,names(.)[-4]))])

基准

set.seed(24)
df <- data.frame(a= sample(10, 1e6, replace=TRUE), b= sample(20, 1e6, 
replace=TRUE), c= sample(40,1e6, replace=TRUE), ref= sample(letters[1:3],
 1e6, replace=TRUE), stringsAsFactors=FALSE)
df2 <- copy(df)
df3 <- copy(df)
df4 <- copy(df)

akrun <- function() {df$main <- df[-4][cbind(1:nrow(df),match(df$ref,names(df)[-4]))]}
akrun2 <- function(){setDT(df3)[, main:=get(ref), ref]}
Frank <- function() {df2 %>% data.table %>% .[,main:=.SD[[ref]],by=ref]}
Frank2 <- function() {setDT(df4)[, main:= .SD[[ref]], by =ref]}
MrFlick <- function() {getval <- . %>%
                                  mutate(id=factor(1:n())) %>% 
                                  gather(col, val, a:c) %>% 
                                  group_by(id) %>% 
                                  summarize(val=first(val[col==ref])) %>% 
                                  select(val)
                       df2 %>%
                          cbind(., getval(.))}

akhmed <- function() {df %>%
                        group_by(ref) %>%
                        do({
                        eval(parse(text=sprintf("main <- .$%s",.$ref[1])))
                        data.frame(., main = main, stringsAsFactors=FALSE)
                         }) %>% 
                        ungroup()
         }

 system.time(akrun())
 #user  system elapsed 
 #0.07    0.00    0.07

 system.time(akrun2())
#user  system elapsed 
# 0.018   0.000   0.018 

system.time(Frank())
# user  system elapsed 
# 0.028   0.000   0.028 

system.time(Frank2())
# user  system elapsed 
# 0.018   0.000   0.018 

system.time(MrFlick())
#  user  system elapsed 
#42.725   0.066  42.777 

 system.time(akhmed())
 #user  system elapsed 
 # 1.125   0.004   1.169 


library(microbenchmark)
microbenchmark(akrun(), akrun2(), Frank(), Frank2(), unit='relative', times=20L)
#Unit: relative
# expr      min       lq     mean   median        uq      max neval cld
# akrun() 3.732126 3.822714 3.768758 3.784908 3.7490118 3.448839    20   c
#akrun2() 1.000000 1.000000 1.000000 1.000000 1.0000000 1.000000    20 a  
# Frank() 1.457337 1.455412 1.672008 1.493600 1.6575381 3.697565    20  b 
# Frank2() 1.001986 1.005541 1.003171 1.003474 0.9980418 1.013425    20 a  

答案 2 :(得分:2)

dplyr mutates一次对整个列进行操作,这种类型的操作本身并不适合。另一种策略可能是使用tidyr库以长格式制作“整洁”数据然后进行子集化。这是你如何做到的。

library(tidyr)
library(dplyr)

getval <- . %>% mutate(id=factor(1:n())) %>% 
    gather(col, val, a:c) %>% group_by(id) %>% 
    summarize(val=first(val[col==ref])) %>% select(val)

df %>% cbind(., getval(.))

这确实假设每个ref值对应于存在的列。

答案 3 :(得分:1)

自我回答:我发现这是一个可能不是最优雅的解决方案,但似乎有效:

library(dplyr)

df2 <- df %>%
  group_by(ref) %>%
  do({
    eval(parse(text=sprintf("main <- .$%s",.$ref[1])))
    data.frame(., main = main, stringsAsFactors=FALSE)
  }) %>% ungroup()

df2

给了我这个:

  a b c ref main
1 1 2 3   a    1
2 2 3 4   a    2
3 3 4 5   b    4
4 4 5 6   b    5
5 5 6 7   c    7

我仍然想知道这是否可以通过一些简单的mutate_来完成?