Reed
Reed

Reputation: 308

Dynamic formula not working with startsWith and colnames

I'm working on making a function to create tables and I need to have some conditional rules involved for formatting. One will be based on a column name, however when I send it down using as.formula it seems to be over doing it. I've made an example here:

library(tidyverse) 
library(rlang)

a <- as_tibble(x =cbind( Year = c(2018, 2019, 2020), a = 1:3,
             b.1 = c("a", "b", "c"),
             b.2 = c("d", "e", "f"), 
             fac = c("This", "This","That")))

foo <- function(x, y, z, ...){
  y_var <- enquo(y)
  
  x %>%
    filter(Year %in% c(2018, 2019),
           ...) %>%
    mutate(!!quo_name(y_var) := factor(!!y_var,
                              levels = z,
                              ordered = TRUE)) %>%
    arrange(!!y_var)
    
}


to.table <- function(x, y, z, ...){
  y_var <- enquo(y)
  
  df.in <- foo(x=x,
               y=!!y_var,
               z= z)
  cond <- paste("~!is.na(", quo_name(y_var),")")
  cond.2 <- paste("~startsWith(colnames(", df.in, "),\"b\")")
  
  
  flextable(df.in) %>%
    bold(i = as.formula(cond),
         part = "body") %>%
    bg(i = as.formula(cond.2),
       bg = "Red3",
       j = as.formula(cond.2))

}

to.table(x=a,
         y=Year,
         z= c(2020,2018,2019),
         fac == "This")
Error in startsWith(colnames(2:3), "b") : non-character object(s) 

From the error I've been reviving it looks like solved the expression before it gets put through the as.formula as those two columns are the correct answer. Proof:

df.in <- foo(x=a,
          y=Year,
          z= c(2020,2018,2019),
          fac == "This")
startsWith(colnames(df.in), prefix = "b")
[1] FALSE FALSE  TRUE  TRUE FALSE

What am I missing here? If anyone has a solution, or suggestion on how to do things differently potentially using quosures or other tidyverse friendly methods I would much appreciate it.

Extension: To make this a bit more clear, I may need to elaborate on my intended use of this example. I'm trying to figure out how to take names generated dynamically in a function represented as foo that start with a specified value (generally 3 columns), and then check those columns for a specified value that I can then highlight in a specific Color. Additionally in the answer cond is used in both of the i= designation, the two separate conditions in will likely never overlap.

Upvotes: 2

Views: 220

Answers (1)

akrun
akrun

Reputation: 887088

We could specify the j with the column names of the data created i.e. startsWith returns a logical vector from the column names based on the names that starts with 'b', use the logical vector to extract the column names with [ (nm1).

to.table <- function(x, y, z, ...){
  y_var <- enquo(y)
  
  df.in <- foo(x=x,
               y=!!y_var,
               z= z)
  
  cond <- as.formula(glue::glue('~ !is.na({quo_name(y_var)})'))
  nm1 <- names(df.in)[startsWith(names(df.in), prefix = "b")]
  
  flextable(df.in) %>%
      bold(i = cond,
           part = "body") %>%
      bg(i = cond,
        bg = "Red3",
         j = nm1)
  
 
}

-testing

to.table(x=a,
         y=Year,
         z= c(2020,2018,2019),
         fac == "This")

-output

enter image description here


In the OP's post formula created for 'cond' is fine although it is a bit more flexible by using glue whereas the second one i.e. 'cond.2' returns

paste("~startsWith(colnames(", df.in, "),\"b\")")
[1] "~startsWith(colnames( 2:3 ),\"b\")"                   "~startsWith(colnames( c(\"1\", \"2\") ),\"b\")"      
[3] "~startsWith(colnames( c(\"a\", \"b\") ),\"b\")"       "~startsWith(colnames( c(\"d\", \"e\") ),\"b\")"      
[5] "~startsWith(colnames( c(\"This\", \"This\") ),\"b\")"

It is because df.in is a data.frame on which we are trying to paste the startsWith(colnames( string. Each of the lines returned are the column values


If we want to get either 'a' or 'b' column names prefix with 'red' color, change the startsWith to grep which can take a regex as pattern

to.table <- function(x, y, z, ...){
  y_var <- enquo(y)
  
  df.in <- foo(x=x,
               y=!!y_var,
               z= z)
  
  cond <- as.formula(glue::glue('~ !is.na({quo_name(y_var)})'))
  nm1 <- grep("^(a|b)", names(df.in), value = TRUE)
  
  flextable(df.in) %>%
    bold(i = cond,
         part = "body") %>%
    bg(i = cond,
       bg = "Red3",
       j = nm1)
  
  
}
to.table(x=a,
         y=Year,
         z= c(2020,2018,2019),
         fac == "This")

-output

enter image description here


If we want to color based on the value of 'a'

to.table <- function(x, y, z, ...){
  y_var <- enquo(y)
  
  df.in <- foo(x=x,
               y=!!y_var,
               z= z)
  
  cond <- as.formula(glue::glue('~ !is.na({quo_name(y_var)})'))
  nm1 <- names(df.in)[startsWith(names(df.in), prefix = "b")]
  
  flextable(df.in) %>%
    bold(i = cond,
         part = "body") %>%
    bg(i = ~ a == 2,
       bg = "Red3",
       j = nm1)
  
}



to.table(x=a,
         y=Year,
         z= c(2020,2018,2019),
         fac == "This")

-output

enter image description here

Upvotes: 2

Related Questions