Return multiple columns in dplyr mutate

Viewed 1118

How can I use mutate to apply a function to a column when the function returns multiple columns? Below I am trying to make dummy/one-hot columns from a character column (I know there are likely 100+ ways to make dummy columns but this is to illustrate the point of returning multiple columns). It returns the dollar signs (e.g., Treatment$Isnonchilled instead of just nonchilledas shown in the image below. This means the column is not an atomic vector but a data frame.

MWE

library(textshape)
library(dplyr)

one_hot <- function(x, drop.jth = TRUE, keep.na = TRUE, prefix = "Is", ...) {
    y <- tibble::as_tibble(textshape::mtabulate(x))
    if (keep.na) y[is.na(x), ] <- NA
    if(drop.jth) y <- y[1:(ncol(y) - 1)]
    colnames(y) <- paste0(prefix, colnames(y))
    y
}

CO2 %>%
    as_tibble() %>%
    mutate(
        conc2 = conc^2,
        across(c(Treatment), one_hot)
    ) 

Shows the column is not an atomic vector as desired but a data.frame

CO2 %>%
    as_tibble() %>%
    mutate(
        conc2 = conc^2,
        across(c(Treatment), one_hot)
    ) %>%
    lapply(class)

$Plant
[1] "ordered" "factor" 

$Type
[1] "factor"

$Treatment
[1] "tbl_df"     "tbl"        "data.frame"

$conc
[1] "numeric"

$uptake
[1] "numeric"

$conc2
[1] "numeric"

enter image description here

3 Answers

Well, you don't have to modify your function. Just do this

CO2 %>%
  as_tibble() %>%
  mutate(
    conc2 = conc^2,
    across(c(Treatment), one_hot)$Treatment # see here
  ) 

Output

# A tibble: 84 x 7
   Plant Type   Treatment   conc uptake   conc2 Isnonchilled
   <ord> <fct>  <fct>      <dbl>  <dbl>   <dbl>        <int>
 1 Qn1   Quebec nonchilled    95   16      9025            1
 2 Qn1   Quebec nonchilled   175   30.4   30625            1
 3 Qn1   Quebec nonchilled   250   34.8   62500            1
 4 Qn1   Quebec nonchilled   350   37.2  122500            1
 5 Qn1   Quebec nonchilled   500   35.3  250000            1
 6 Qn1   Quebec nonchilled   675   39.2  455625            1
 7 Qn1   Quebec nonchilled  1000   39.7 1000000            1
 8 Qn2   Quebec nonchilled    95   13.6    9025            1
 9 Qn2   Quebec nonchilled   175   27.3   30625            1
10 Qn2   Quebec nonchilled   250   37.1   62500            1
# ... with 74 more rows

For mutation across many columns,

CO2 %>%
  as_tibble() %>%
  mutate(
    conc2 = conc^2,
    bind_cols(as.list(across(starts_with("T"), one_hot)))
  )

Output

# A tibble: 84 x 8
   Plant Type   Treatment   conc uptake   conc2 IsQuebec Isnonchilled
   <ord> <fct>  <fct>      <dbl>  <dbl>   <dbl>    <int>        <int>
 1 Qn1   Quebec nonchilled    95   16      9025        1            1
 2 Qn1   Quebec nonchilled   175   30.4   30625        1            1
 3 Qn1   Quebec nonchilled   250   34.8   62500        1            1
 4 Qn1   Quebec nonchilled   350   37.2  122500        1            1
 5 Qn1   Quebec nonchilled   500   35.3  250000        1            1
 6 Qn1   Quebec nonchilled   675   39.2  455625        1            1
 7 Qn1   Quebec nonchilled  1000   39.7 1000000        1            1
 8 Qn2   Quebec nonchilled    95   13.6    9025        1            1
 9 Qn2   Quebec nonchilled   175   27.3   30625        1            1
10 Qn2   Quebec nonchilled   250   37.1   62500        1            1
# ... with 74 more rows

The output of the function is a data.frame. Inside the function, I am using the pull function to get the vector.

library(textshape)
library(dplyr)

one_hot <- function(x, drop.jth = TRUE, keep.na = TRUE, prefix = "Is", ...) {
  y <- tibble::as_tibble(textshape::mtabulate(x))
  if (keep.na) y[is.na(x), ] <- NA
  if(drop.jth) y <- y[1:(ncol(y) - 1)]
  colnames(y) <- paste0(prefix, colnames(y))
  y %>% pull(1) # you need to transform the df to a vector
}

CO2 %>%
  as_tibble() %>%
  mutate(
    conc2 = conc^2,
    across(c(Treatment), one_hot)
  ) 

Using your original function and purrr::map, you could generate a list of columns then bind them back to your original dataframe.

purrr::map(c('Treatment','Type'), ~one_hot(CO2[[.x]])) %>%
  bind_cols(CO2)
# A tibble: 84 x 7
   Isnonchilled IsQuebec Plant Type   Treatment   conc uptake
          <int>    <int> <ord> <fct>  <fct>      <dbl>  <dbl>
 1            1        1 Qn1   Quebec nonchilled    95   16  
 2            1        1 Qn1   Quebec nonchilled   175   30.4
 3            1        1 Qn1   Quebec nonchilled   250   34.8
 4            1        1 Qn1   Quebec nonchilled   350   37.2
 5            1        1 Qn1   Quebec nonchilled   500   35.3
Related