以编程方式分解数据框中的选定列,整齐的方式?

时间:2017-08-20 12:59:25

标签: r dplyr tidyverse purrr

这是一个简化的例子:

library(tidyverse)

frame <- tribble(
  ~a, ~b, ~c,
   1,  1,  2,
   5,  4,  7,
   2,  3,  4, 
   3,  1,  6
)

key <- tribble(
  ~col, ~name, ~type, ~labels,
     1,   "a",   "f",     c("one", "two", "three", "four", "five"),
     2,   "b",   "f",     c("uno", "dos", "tres", "cuatro"),
     3,   "c",   "f",     1:7
)

是否有一种优雅的方式可以编程扫描frame中的列并根据key中的参数应用特定的因子类?预期结果将是:

# A tibble: 4 x 3
       a      b      c
  <fctr> <fctr> <fctr>
1    one    uno      2
2   five cuatro      7
3    two   tres      4
4  three    uno      6

到目前为止,我所使用的最佳解决方案是使用purrr&#39; map2(),但IMO并不是最优雅的分配:

frame[key$col] <- map2(key$col, key$labels, 
        function(x, y) factor(frame[[x]], levels = 1:length(y), labels = y))

有没有人有更整洁的解决方案?请注意,我的原始数据框有数百列,我需要重新考虑大部分列的不同级别/标签,因此该过程必须自动化。

4 个答案:

答案 0 :(得分:1)

这是另一种解决方案。我不确定它是多么“优雅”。希望有人可以改进。

suppressPackageStartupMessages(library(tidyverse))

frame <- tribble(
  ~a, ~b, ~c,
  1,  1,  2,
  5,  4,  7,
  2,  3,  4, 
  3,  1,  6
)

key <- tribble(
  ~col, ~name, ~type, ~labels,
  1,   "a",   "f",     c("one", "two", "three", "four", "five"),
  2,   "b",   "f",     c("uno", "dos", "tres", "cuatro"),
  3,   "c",   "f",     1:7
)

colnames(frame) %>% 
  map(~ {
    factor(pull(frame, .x),
           levels = 1:length(pluck(key[key$name == .x, "labels"], 1, 1)),
           labels = pluck(key[key$name == .x, "labels"], 1, 1))
  }) %>% 
  set_names(colnames(frame)) %>% 
  as_tibble()
#> # A tibble: 4 x 3
#>        a      b      c
#>   <fctr> <fctr> <fctr>
#> 1    one    uno      2
#> 2   five cuatro      7
#> 3    two   tres      4
#> 4  three    uno      6

答案 1 :(得分:0)

我不知道这个答案是否满足你整洁的要求,因为它使用了一个普通的旧循环。但它完成了这项工作,在我看来,它很容易阅读/理解,而且速度也相当快。

library(tidyverse)
frame <- tribble(
 ~a, ~b, ~c,
 1,  1,  2,
 5,  4,  7,
 2,  3,  4, 
 3,  1,  6
)

key <- tribble(
 ~col, ~name, ~type, ~labels,
 1,   "a",   "f",     c("one", "two", "three", "four", "five"),
 2,   "b",   "f",     c("uno", "dos", "tres", "cuatro"),
 3,   "c",   "f",     1:7
)

for (i in 1:nrow(key)) {
 var <- key$name[[i]]
 x <- frame[[var]]
 labs <- key$labels[[i]]
 lvls <- 1:max(length(x), length(labs)) # make sure to have the right lengths

 frame <- frame %>% mutate(!! var := factor(x, levels = lvls, labels = labs))
}

frame
#> # A tibble: 4 x 3
#>        a      b      c
#>   <fctr> <fctr> <fctr>
#> 1    one    uno      2
#> 2   five cuatro      7
#> 3    two   tres      4
#> 4  three    uno      6

典型的整洁方法是重塑数据以将所有变量放在一列中,然后将函数应用于该列,最后将其重新整形为原始格式。但是,因素并不是真的那样,因此我们需要使用其他方法。因素甚至被认为是整洁的吗?

修改

关于我假设for循环类似于map2 - 函数,我错了。

以下是一些基准:

library(microbenchmark)

frame1 <- frame
frame2 <- frame

microbenchmark(
 map2 = {
  frame1[key$col] <- map2(key$col, key$labels, 
                          function(x, y) factor(frame[[x]], 
                                                levels = 1:max(frame[[x]],
                                                               length(y)), 
                                                labels = y))
 },
 forloop = {
  for (i in 1:nrow(key)) {
   var <- key$name[[i]]
   x <- frame2[[var]]
   labs <- key$labels[[i]]
   lvls <- 1:max(length(x), length(labs))
   frame2 <- frame2 %>% mutate(!! var := factor(x, levels = lvls, labels = labs))
  }
 }
)

# Unit: microseconds
# expr         min         lq       mean    median         uq       max neval cld
# map2      375.53   416.5805   514.3126   450.825   484.2175  3601.636   100  a 
# forloop 11407.80 12110.0090 12816.6606 12564.176 13425.6840 16632.682   100   b

答案 2 :(得分:0)

我很想知道为此提出了哪些其他解决方案。我唯一的建议是稍微更改建议的解决方案,以便更清楚frame将以某种方式进行修改,而不是将其留在map2使用的函数体中。

例如,在调用frame时将map2作为附加参数传递:

frame[key$col] <- map2(key$col, key$labels, 
                       function(x, y, z) factor(z[[x]], levels = 1:length(y), labels = y), 
                       frame)

或者使用管道运算符%>%执行相同的操作:

frame[key$col] <- frame %>%
  { map2(key$col, key$labels, 
         function(x, y, z) factor(z[[x]], levels = 1:length(y), labels = y), .) }

答案 3 :(得分:0)

对于这个问题,您可以使用基本R代码:

(A=`names<-`(data.frame(mapply(function(x,y)x[y],key$labels,frame)),key$name))
      a      b c
1   one    uno 2
2  five cuatro 7
3   two   tres 4
4 three    uno 6

 sapply(A,class)
   a        b        c 
"factor" "factor" "factor"