Chcę utworzyć suped-up wersja dplyr::bind_rows, która pozwala uniknąć ostrzeżeń Unequal factor levels: coercing to character, gdy w DFS są obecne kolumny czynnikowe, które próbujemy połączyć (co może mieć również kolumny bezczynnika). Oto przykład:

df1 <- dplyr::data_frame(age = 1:3, gender = factor(c("male", "female", "female")), district = factor(c("north", "south", "west")))
df2 <- dplyr::data_frame(age = 4:6, gender = factor(c("male", "neutral", "neutral")), district = factor(c("central", "north", "east")))

Następnie zwraca bind_rows_with_factor_columns(df1, df2) (bez ostrzeżeń):

dplyr::data_frame(
  age = 1:6,
  gender = factor(c("male", "female", "female", "male", "neutral", "neutral")),
  district = factor(c("north", "south", "west", "central", "north", "east"))
)

Oto, co mam do tej pory:

bind_rows_with_factor_columns <- function(...) {
  factor_columns <- purrr::map(..., function(df) {
      colnames(dplyr::select_if(df, is.factor))
  })

  if (length(unique(factor_columns)) > 1) {
      stop("All factor columns in dfs must have the same column names")
  }

  df_list <- purrr::map(..., function (df) {
    purrr::map_if(df, is.factor, as.character) %>% dplyr::as_data_frame()
  })

  dplyr::bind_rows(df_list) %>%
    purrr::map_at(factor_columns[[1]], as.factor) %>%
    dplyr::as_data_frame()
}

Zastanawiam się, czy ktoś ma jakieś pomysły, jak włączyć pakiet forcats, aby potencjalnie unikać konieczności zamykania czynników do postaci lub jeśli ktoś ma jakiekolwiek sugestie, aby zwiększyć wydajność tego, utrzymując tę samą funkcjonalność ( Chciałbym trzymać się składni tidyverse). Dzięki!

5
Nick Resnick 16 luty 2017, 18:22

2 odpowiedzi

Najlepsza odpowiedź

Będzie odpowiedzieć na moje własne pytanie oparte na świetnym rozwiązaniu znajomego:

bind_rows_with_factor_columns <- function(...) {
  purrr::pmap_df(list(...), function(...) {
    cols_to_bind <- list(...)
    if (all(purrr::map_lgl(cols_to_bind, is.factor))) {
      forcats::fct_c(cols_to_bind)
    } else {
      unlist(cols_to_bind)
    }
  })
}
1
Nick Resnick 16 luty 2017, 15:42

Może być prostsze, użycie dplyr::bind_rows z ostrzeżeniami stłumionymi, a następnie konwertować wszystkie nowe kolumny znaków z powrotem do czynników. Ma to zaletę wiązania {X1}} przez nazwy kolumn (umożliwiające różne zamawianie kolumn i włączenia dodatkowych kolumn) i nadal działa, gdy zmienne czynnik są czasami rejestrowane jako znaki.

library(tidyverse)

bind_rows_keep_factors <- function(...) {
  ## Identify all factors
  factors <- unique(unlist(
    map(list(...), ~ select_if(..., is.factor) %>% names())
  ))
  ## Bind dataframes, convert characters back to factors
  suppressWarnings(bind_rows(...)) %>% 
    mutate_at(vars(one_of(factors)), factor)  
}

dat1 <- tibble(
  id = 1:2,
  fruit = factor(c("banana", "apple"))
)

dat2 <- tibble(
  id = 3:4,
  fruit = c("pear", "banana"),
  taste = c("Mmmm", "yum")
)

bind_rows_keep_factors(dat1, dat2)
# A tibble: 4 x 3
     id fruit  taste
  <int> <fct>  <chr>
1     1 banana NA   
2     2 apple  NA   
3     3 pear   Mmmm 
4     4 banana yum 

Oczywiście zamawianie poziomów czynników jest zakłócany (powrócić do alfabetycznego).

1
JWilliman 6 kwiecień 2018, 01:57