LMc
LMc

Reputation: 18612

Conditionally add pipe using function argument if argument is not null with dplyr

I have a function with an argument subset that has the default value of NULL. Within the function if subset is NULL I do not want to add a conditional pipe. Otherwise, I want to use the value of subset within the pipe:

library(tidyverse)

f <- function(subset = NULL){
  
  iris %>% 
    {if (is.null(substitute(subset))) . else filter(., {{ subset }} < 2.2)}
  
}

f() # gives error posted below
## Desired output: entire iris dataset

f(subset = Sepal.Width) # works
Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1            5           2          3.5           1 versicolor

However, using the curly brackets, {{ subset}} is evaluating too early when subset = NULL and is trying to filter where NULL < 2.2. f() returns the following error:

Error: Problem with filter() input ..1.

x Input ..1 must be of size 150 or 1, not size 0.

i Input ..1 is NULL < 2.2.

Upvotes: 2

Views: 579

Answers (3)

akrun
akrun

Reputation: 886938

Here is an approach to return the full dataset when an error happens with tryCatch

f <- function(subset = NULL){

  tryCatch(  iris %>% 
    filter({{ subset }} < 2.2)
      , error = function(err) iris)
    

 }

-testing

dim(f())
#[1] 150   5

dim(f(subset = Sepal.Width))
#[1] 1 5

Upvotes: 2

r2evans
r2evans

Reputation: 160407

You should evaluate is.null(substitute(subset)) in the function body, not within the if clause. That clause is evaluated differently (due to %>% stack management) than in the parent.

This works:

f <- function(subset = NULL){
  isnull <- is.null(substitute(subset))
  iris %>% 
    {if (isnull) . else filter(., {{ subset }} < 2.2)}
}

head( f() )
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          5.1         3.5          1.4         0.2  setosa
# 2          4.9         3.0          1.4         0.2  setosa
# 3          4.7         3.2          1.3         0.2  setosa
# 4          4.6         3.1          1.5         0.2  setosa
# 5          5.0         3.6          1.4         0.2  setosa
# 6          5.4         3.9          1.7         0.4  setosa
f(subset = Sepal.Width)
#   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
# 1            5           2          3.5           1 versicolor

Upvotes: 2

WilliamGram
WilliamGram

Reputation: 683

I found an approach which is probably not the most elegant, but it has the advantage of being fairly legible and intuitive.

First I turn the object into a string and compare with the variables in the dataset. If the object you entered is not there, it will just return the data as is. If in turn the column is in the dataset, it will filter it using the bang-bang operator !! in combination with sym which converts your string to a symbol.

Hope that works for you.

library(dplyr)
f <- function(data, subset = NULL, value = 2.2) {
  subsetName = deparse(substitute(subset))

  if (!subsetName %in% names(data)) {return(data)}

  return(data %>% filter(!!sym(subsetName) < value))
}

Upvotes: 1

Related Questions