How do I efficiently use different arguments for a custom function that relies on dplyr?

Viewed 72

There is a data frame, e.g.:

df <- data.frame(k = sample(1:2, 100, replace = TRUE),
                 l = sample(1:2, 100, replace = TRUE),
                 g = sample(1:3, 100, replace = TRUE, prob = c(0.2, 0.6, 0.2)))

And I will need proportion plots for l and k grouped by g so I write myself a function:

library(tidyverse)

fun_gg_factor <- function(p) {
  df %>%
    group_by(g) %>%
    count({{p}}) %>%
    mutate(Anteil = n / sum(n)) %>%
    ggplot(aes(x = {{p}}, y = Anteil)) +
    geom_col(position = position_dodge()) +
    facet_grid(.~g)
}

And it works as intended:

fun_gg_factor(k)

enter image description here

That is nice. But my rl df has more variables than k and l. Much more. So I do not want to call the function manually dozens of times like this:

fun_gg_factor(k)
fun_gg_factor(l)
fun_gg_factor(m)
.
.
.
fun_gg_factor(z)

sapply() and its forms come to mind:

sapply(c(k, l), fun_gg_factor)

That does not work, as k and l are not objects. Even if they were, that is not what I want. I do not need a plot for every element of df$k - I want plots for the different columns.
Maybe I try a loop:

for (i in c(k, l)) {
  fun_gg_factor(i)
}

But no, k and l are still no objects.
Obviously my representation of the problem is lacking. How do I efficiently use different arguments for this or any similar custom function?

2 Answers

Making use of the .data pronoun from rlang you could pass the variable names as a string to your function which makes it easy to loop over a set of variables using e.g. lapply. To this end replace {{ p }} by .data[[p]] in your function:

set.seed(42)

df <- data.frame(
  k = sample(1:2, 100, replace = TRUE),
  l = sample(1:2, 100, replace = TRUE),
  g = sample(1:3, 100, replace = TRUE, prob = c(0.2, 0.6, 0.2))
)

library(ggplot2)
library(dplyr)

fun_gg_factor <- function(p) {
  df %>%
    group_by(g) %>%
    count(.data[[p]]) %>%
    mutate(Anteil = n / sum(n)) %>%
    ggplot(aes(x = .data[[p]], y = Anteil)) +
    geom_col(position = position_dodge()) +
    facet_grid(. ~ g)
}

lapply(names(df)[!names(df) %in% "g"], fun_gg_factor)
#> [[1]]

#> 
#> [[2]]

Here is another way of force-evaluating a user-defined argument in the form of character string. We first turn the argument into a symbol with rlang::sym and then force evaluate it with !! called bang-bang operator:

library(rlang)

fun_gg_factor <- function(p) {
  df %>%
    group_by(g) %>%
    count(!!sym(p)) %>%
    mutate(Anteil = n / sum(n)) %>%
    ggplot(aes(x = !!sym(p), y = Anteil)) +
    geom_col(position = position_dodge()) +
    facet_grid(.~ g)
}
Related