How do I create a function to mutate new columns with a variable name and "_pct"?

Viewed 214

Using mtcars as an example. I would like to write a function that creates a count and pct column such as below -

library(tidyverse)

mtcars %>% 
  group_by(cyl) %>% 
  summarise(count = n()) %>% 
  ungroup() %>% 
  mutate(cyl_pct = count/sum(count))

This produces the output -

# A tibble: 3 x 3
    cyl count mpg_pct
  <dbl> <int>   <dbl>
1     4    11   0.344
2     6     7   0.219
3     8    14   0.438

However, I would like to create a function where I can specify the group_by column to be any column and the mutate column will be name the column name specified in the groub_by, and a _pct. So if I want to use disp, disp will be my group_by variable and the function will mutate a disp_pct column.

3 Answers

Similar to akrun's answer, but using {{ instead of !!:

foo = function(data, col) {
  data %>%
    group_by({{col}}) %>%
    summarize(count = n()) %>%
    ungroup %>% 
    mutate(
      "{{col}}_pct" := count / sum(count)
    )
}

foo(mtcars, cyl)
# `summarise()` ungrouping output (override with `.groups` argument)
# # A tibble: 3 x 3
#     cyl count cyl_pct
#   <dbl> <int>   <dbl>
# 1     4    11   0.344
# 2     6     7   0.219
# 3     8    14   0.438

Assuming that the input is unquoted, convert to symbol with ensym, evaluate (!!) within group_by while converting the symbol into a string (as_string) and paste the prefix '_pct' for the new column name. In mutate we can use := along with !! to assign the column name from the object created ('colnm')

library(stringr)
library(dplyr)
f1 <- function(dat, grp) {
        grp <- ensym(grp)
        colnm <- str_c(rlang::as_string(grp), '_pct')
        dat %>%
           group_by(!!grp) %>%
           summarise(count = n(), .groups = 'drop') %>%
           mutate(!! colnm := count/sum(count))
     }

-testing

f1(mtcars, cyl)
# A tibble: 3 x 3
#    cyl count cyl_pct
#  <dbl> <int>   <dbl>
#1     4    11   0.344
#2     6     7   0.219
#3     8    14   0.438

This is probably no different than the one posted by my dear friend @akrun. However, in my version I used enquo function instead of ensym. There is actually a subtle difference between the two and I thought you might be interested to know:

  • As per documentation of nse-defuse, ensym returns a raw expression whereas enquo returns a "quosure" which is in fact a "wrapper containing an expression and an environment". So we need one extra step to access the expression of quosure made by enquo.
  • In this case we use get_expr for our purpose. So here is just another version of writing this function that I thought might be of interest to whomever read this post in the future.
library(dplyr)
library(rlang)

fn <- function(data, Var) {
  Var <- enquo(Var)
  colnm <- paste(get_expr(Var), "pct", sep = "_")

  data %>% 
    group_by(!!Var) %>% 
    summarise(count = n()) %>% 
    ungroup() %>% 
    mutate(!! colnm := count/sum(count))
}

fn(mtcars, cyl)

# A tibble: 3 x 3
    cyl count cyl_pct
  <dbl> <int>   <dbl>
1     4    11   0.344
2     6     7   0.219
3     8    14   0.438
Related