1

I have a function that applies to a dataframe, with the exception of some columns. These columns can be filled using tidyselection.

For example, consider this function:

rename_upper = function(df, except=NULL){
  except = names(select(df, {{except}}))
  rename_with(df, ~ifelse(.x %in% except, .x, toupper(.x)))
}
rename_upper(iris, except=3:5) %>% names()
#> [1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "Petal.Length" "Petal.Width"  "Species"   
rename_upper(iris, except=starts_with("Pet")) %>% names()  
#> [1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "Petal.Length" "Petal.Width"  "SPECIES"     

The output is good, but I don't want it to throw an error if except contains a column that doesn't exist in df.

For instance, rename_upper(iris, except=6) throws an error as location 6 doesn't exist.

Using any_of() is unfortunately not a solution because it would restrict except to be a character vector and I couldn't use tidyselection anymore.

Can I have the best of both worlds?

Dan Chaltiel
  • 7,811
  • 5
  • 47
  • 92

2 Answers2

2

Maybe use a try to see if that's any error when selecting for the except column?

Here all columns would be renamed with toupper if there's an error in select with a warning message.

library(dplyr)

rename_upper = function(df, except=NULL){
  tryerror <- try(select(df, {{except}}), silent = T)
  if (class(tryerror) == "try-error") {
    warning(paste0("Column specified in the except argument not found in dataframe '", deparse(substitute(df)), "'"))
    rename_with(df, .cols = everything(), toupper)
  } else {
    except = names(tryerror)
    rename_with(df, ~ifelse(.x %in% except, .x, toupper(.x)))
  }
}

# with error
rename_upper(iris, except=523) %>% names()
[1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "PETAL.LENGTH" "PETAL.WIDTH" 
[5] "SPECIES"     
Warning message:
In rename_upper(iris, except = 523) :
  Column specified in the except argument not found in dataframe 'iris' 

# run as expected
rename_upper(iris, except=3:5) %>% names()
[1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "Petal.Length" "Petal.Width" 
[5] "Species"  
benson23
  • 16,369
  • 9
  • 19
  • 38
  • I was hoping there was a way to do this without `try()`. I find error handling quite difficult to maintain and I'm surprised there isn't a "tidyverse" way to do it. I'll obviously accept your answer if there is not. – Dan Chaltiel Mar 26 '23 at 15:35
  • @DanChaltiel Agreed. Maybe there're `tidyverse` ways to handle errors that we're not aware of. Let's wait to see if others would have a brighter solution! – benson23 Mar 26 '23 at 15:39
1

Try

rename_upper <- function(df, except=NULL){
   except <- rlang::enexpr(except)
   if((!is.numeric(except)) && length(as.list(except))== 1)
  {
   except <- rlang::as_string(except)
   }
  nm1 <- setdiff(names(df), names(select(df, any_of({{except}}))))
 df %>%
    rename_with(~ toupper(.x), all_of(nm1))
  

}

-testing

> rename_upper(iris, except=6)  %>%
+     names
[1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "PETAL.LENGTH" "PETAL.WIDTH"  "SPECIES"     
> 
> rename_upper(iris, except=starts_with("Pet"))  %>%
+   names
[1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "Petal.Length" "Petal.Width"  "SPECIES"  

> rename_upper(iris, except=3:5) %>% names
[1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "Petal.Length" "Petal.Width"  "Species" 
> rename_upper(iris, except=Sepal.Length) %>%
+    names
[1] "Sepal.Length" "SEPAL.WIDTH"  "PETAL.LENGTH" "PETAL.WIDTH"  "SPECIES"        
akrun
  • 874,273
  • 37
  • 540
  • 662
  • Nice try, but tidyselection is somehow lost, as `rename_upper(iris, except=Sepal.Length)` would not work. This would break my backward compatibility :-( – Dan Chaltiel Mar 26 '23 at 18:46
  • @DanChaltiel I updated. Do you have any other cases to test – akrun Mar 27 '23 at 00:33