1#' Combine levels from two or more factors to create a new factor 2#' 3#' Computes a factor whose levels are all the combinations of the levels of the input factors. 4#' 5#' @param ... <[`dynamic-dots`][rlang::dyn-dots]> Additional factors 6#' or character vectors. 7#' @param sep A character string to separate the levels 8#' @param keep_empty If TRUE, keep combinations with no observations as levels 9#' @return The new factor 10#' 11#' @export 12#' @examples 13#' fruit <- factor(c("apple", "kiwi", "apple", "apple")) 14#' colour <- factor(c("green", "green", "red", "green")) 15#' eaten <- c("yes", "no", "yes", "no") 16#' fct_cross(fruit, colour) 17#' fct_cross(fruit, colour, eaten) 18#' fct_cross(fruit, colour, keep_empty = TRUE) 19fct_cross <- function(..., sep = ":", keep_empty = FALSE) { 20 21 flist <- list2(...) 22 if (length(flist) == 0) { 23 return(factor()) 24 } 25 26 .data <- tibble::as_tibble(flist, .name_repair = "minimal") 27 .data <- lapply(.data, check_factor) 28 29 newf <- exec(paste, !!!.data, sep = sep) 30 31 old_levels <- lapply(.data, levels) 32 grid <- exec(expand.grid, old_levels) 33 new_levels <- exec(paste, !!!grid, sep = sep) 34 35 if (!keep_empty) { 36 new_levels <- intersect(new_levels, newf) 37 } 38 factor(newf, levels = new_levels) 39} 40