【问题标题】:Programmatically factorize selected columns in data frame, the tidy way?以编程方式分解数据框中的选定列,整洁的方式?
【发布时间】:2017-08-20 12:59:25
【问题描述】:

这是一个简化的例子:

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

到目前为止,我最好的解决方案是使用 purrrmap2(),但是 IMO 的分配不是最优雅的:

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

有没有人有更整洁的解决方案?请注意,我的原始数据框有数百列,我需要用不同的级别/标签对其中的大多数进行重构,因此该过程必须自动化。

【问题讨论】:

  • Hadley 有一个 forcats 包,如果这听起来很有趣的话。
  • 谢谢,我看了一下,它非常好 - 但是它没有直接提供按我想要的方式操作数据帧的功能......

标签: r dplyr tidyverse purrr


【解决方案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

【讨论】:

    【解决方案2】:

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

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

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

    或者使用管道运算符%&gt;%做同样的事情:

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

    【讨论】:

    • 感谢您的建议,我会将您的帖子标记为已回答,看来这是目前为止的最佳方法。
    【解决方案3】:

    我不知道这个答案是否满足您对整洁的要求,因为它使用了一个普通的旧 for 循环。但它确实可以完成工作,而且我认为它易于阅读/理解并且速度相当快。

    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
    

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

    编辑

    关于我假设 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
    

    【讨论】:

    • 感谢@David 非常详尽的回答!看来我提出的原始解决方案要快得多……
    【解决方案4】:

    对于这个问题,您可以使用基本 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" 
    

    【讨论】:

      猜你喜欢
      • 2020-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多