将两个数据帧绑定在一起时如何合并因子?

时间:2017-10-22 16:11:38

标签: r dplyr tibble

这是一个相当小的再现代码。真实数据集较大且有许多因素,因此手动列出因素是不实际的。还有更有趣的数据转换,我想继续使用dplyr。

library(dplyr)
a = data.frame(f=factor(c("a", "b")), g=c("a", "a"))
b = data.frame(f=factor(c("a", "c")), g=c("a", "a"))
a = a %>% group_by(g) %>% mutate(n=1)
b = b %>% group_by(g) %>% mutate(n=2)
rbind(a,b)

这会产生:

# A tibble: 4 x 3
# Groups:   g [1]
      f      g     n
  <chr> <fctr> <dbl>
1     a      a     1
2     b      a     1
3     a      a     2
4     c      a     2
Warning messages:
1: In bind_rows_(x, .id) : Unequal factor levels: coercing to character
2: In bind_rows_(x, .id) :
  binding character and factor vector, coercing into character vector
3: In bind_rows_(x, .id) :
  binding character and factor vector, coercing into character vector

这些警告很烦人,如果我不使用group_by,它实际上会消失:

> a = data.frame(f=factor(c("a", "b")), g=c("a", "a"))
> b = data.frame(f=factor(c("a", "c")), g=c("a", "a"))
> a = a %>% mutate(n=1)
> b = b %>% mutate(n=2)
> rbind(a,b)
  f g n
1 a a 1
2 b a 1
3 a a 2
4 c a 2

data.frame之前明确转换为rbind也有效:

> rbind(data.frame(a),data.frame(b))
  f g n
1 a a 1
2 b a 1
3 a a 2
4 c a 2

基础R或dplyr rbind / bind_rows是否有一种简单的方法可以自动合并这些因素及其级别,而不是将它们转换为字符(这对我来说没什么意义),同时仍然使用dplyr用于数据转换?

我发现https://stackoverflow.com/a/30468468/388803提出了手动合并因子的解决方案,但这非常详细。

我的实际用例是使用read.table加载两个.csv文件,进行一些数据转换,然后合并数据,因为它们是互补的。 我目前的解决方法是在数据转换结束时调用data.frame(data)。 我想知道为什么dplyr / tibble不能自动合并因子,因为它在这种情况下似乎是安全的。这可能会改善吗?

3 个答案:

答案 0 :(得分:2)

使用data.table的解决方案 将您的data.frame转换为data.table并使用n添加:=(无需dplyr)。

a <- data.frame(f=factor(c("a", "b")), g=c("a", "a"))
b <- data.frame(f=factor(c("a", "c")), g=c("a", "a"))
library(data.table)
rbind(setDT(a)[, n := 1], 
      setDT(b)[, n := 2])
   f g n
1: a a 1
2: b a 1
3: a a 2
4: c a 2

答案 1 :(得分:2)

如果因子只是字符串的有效存储,可以在合并之前将它们转换为字符串,然后转换为因子:

bind_rowsFactors <- function(
  ### bind_rows on two data.frames with merging factors levels
  a      ##<< first data.frame to bind
  , b    ##<< second data.frame to bind
  , ...  ##<< further arguments to \code{bind_rows}
){
  isInconsistentFactor <- sapply( names(a),  function(col){
    (is.factor(a[[col]]) | is.factor(b[[col]])) &&
      any(levels(a[[col]]) != levels(b[[col]]))
  })
  if (sum(isInconsistentFactor)) warning(
    "releveling factors ", paste(names(a)[isInconsistentFactor], collapse = ","))
  for (col in names(a)[isInconsistentFactor]) {
    a <- mutate(ungroup(a), !!col := as.character(!!rlang::sym(col)))
    b <- mutate(ungroup(b), !!col := as.character(!!rlang::sym(col)))
  }
  ans <- bind_rows(a, b, ...)
  # convert former factors form string back to factor
  for (col in names(ans)[isInconsistentFactor]) {
    ans <- mutate(ungroup(ans), !!col := factor(!!rlang::sym(col)))
  }
  ##value<< result of \code{bind_rows} with inconsistend factor columns still factors
  ans
}

library(dplyr)
a = data.frame(f = factor(c("a", "b")), g = c("a", "a"))
b = data.frame(f = factor(c("a", "c")), g = c("a", "a"))
a = a %>% group_by(g) %>% mutate(n = 1)
b = b %>% group_by(g) %>% mutate(n = 2)
#bind_rows(a,b)
bind_rowsFactors(a,b)

对于带有dplyr和未定义符号的非标准平均而言,奇怪的!!rlang::sym符号只是workaround

上面的代码会在重新定义f的因子级别时发出警告,否则会返回绑定的data.frame,其中列f是一个因素。

# A tibble: 4 x 3
  f     g         n
  <fct> <fct> <dbl>
1 a     a        1.
2 b     a        1.
3 a     a        2.
4 c     a        2.
Warning message:
In bind_rowsFactors(a, b) : releveling factors f

答案 2 :(得分:2)

我在解决类似任务时遇到了这个问题。使用forcats::lvls_union,您可以在一系列因素中获得所有级别的字符向量-在这种情况下为a$fb$f。然后,您可以使用forcats::fct_expand扩展每个数据框的f,以具有因子水平的并集。

library(tidyverse)

a <- data.frame(f = factor(c("a", "b")), g = c("a")) %>%
  mutate(n = 1) %>%
  group_by(g)

b <- data.frame(f = factor(c("a", "c")), g = c("a")) %>%
  mutate(n = 2) %>%
  group_by(g)

all_lvls <- lvls_union(list(a$f, b$f))

获得级别的并集后,您可以同时mutate两个数据帧并调用bind_rows

bind_rows(
  a %>% mutate(f = fct_expand(f, all_lvls)),
  b %>% mutate(f = fct_expand(f, all_lvls))
)
#> # A tibble: 4 x 3
#> # Groups:   g [1]
#>   f     g         n
#>   <fct> <fct> <dbl>
#> 1 a     a         1
#> 2 b     a         1
#> 3 a     a         2
#> 4 c     a         2

或者,要获得相同的结果,可以在两个数据帧的列表上map展开每个f。使用map_dfr是一种快捷方式,例如调用map,然后管道传输到bind_rows

map_dfr(list(a, b), ~mutate(., f = fct_expand(f, all_lvls)))
#> # A tibble: 4 x 3
#> # Groups:   g [1]
#>   f     g         n
#>   <fct> <fct> <dbl>
#> 1 a     a         1
#> 2 b     a         1
#> 3 a     a         2
#> 4 c     a         2

reprex package(v0.2.0)于2018-07-17创建。