Search code examples
rdplyrreadr

Using mutate_at with mutate_if


I'm in the process of creating a generic function in my package. The goal is to find columns that are percent columns, and then to use parse_number on them if they are character columns. I haven't been able to figure out a solution using mutate_at and ifelse. I've pasted a reprex below.

 library(tidyverse)


df <- tibble::tribble(
  ~name, ~pass_percent, ~attendance_percent, ~grade,
  "Jon",         "90%",                0.85,    "B",
  "Jim",        "100%",                   1,    "A"
  )

percent_names <- df %>% select(ends_with("percent"))%>% names()


# Error due to attendance_percent already being in numeric value

if (percent_names %>% length() > 0) {
    df <-
      df %>%
      dplyr::mutate_at(percent_names, readr::parse_number)
  }
#> Error in parse_vector(x, col_number(), na = na, locale = locale, trim_ws = trim_ws): is.character(x) is not TRUE

Solution

  • your attendance_percent variable is numeric, not character and parse_number only wants character variables, see here. So a solution would be:

    edited_parse_number <- function(x, ...) {
      if (mode(x) == 'numeric') {
        x
      } else {
        parse_number(x, ...)
      }
    }
    
    
    df %>%
      dplyr::mutate_at(vars(percent_names), edited_parse_number)
    
    #  name  pass_percent attendance_percent grade
    #  <chr>        <dbl>              <dbl> <chr>
    #1 Jon             90               0.85 B    
    #2 Jim            100               1    A   
    

    OR

    if you didn't want to use that extra function, extract character variables at beginning:

    percent_names <- df %>% 
      select(ends_with("percent")) %>% 
      select_if(is.character) %>% 
      names()
    percent_names
    # [1] "pass_percent"
    
    
    df %>%
      dplyr::mutate_at(vars(percent_names), parse_number)
    #   name  pass_percent attendance_percent grade
    #   <chr>        <dbl>              <dbl> <chr>
    # 1 Jon             90               0.85 B    
    # 2 Jim            100               1    A