Mutate using tidyeval

I have a similar problem as https://forum.posit.co/t/filter-uisng-enquos/6197,
but I still can't make it.

Suppose I have a dataframe

library(tidyverse)
df <- tribble(
  ~id, ~x, ~y, ~z, ~g,
  #--|--|--|--|--
  "a", 13.1, 14, 4, 1,
  "b", 15.2, 7, 5, 0,
  "c", 12.5, 10, 1, 0,
  "d", 20, 11, 3, 1
)
df
#> # A tibble: 4 x 5
#>   id        x     y     z     g
#>   <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 a      13.1    14     4     1
#> 2 b      15.2     7     5     0
#> 3 c      12.5    10     1     0
#> 4 d      20      11     3     1

and cutoffs for x, y, z columns

cutoffs <- list(
  x = 13,
  y = 12,
  z = 3
)

I want to change the value of each column to 0 or 1, according to the specified cutoffs (different columns, different cutoffs).
This is easy to do with dplyr::if_else,

df %>%
  mutate(x = if_else(x < cutoffs$x, 1, 0)) %>%
  mutate(y = if_else(y < cutoffs$y, 1, 0)) %>%
  mutate(z = if_else(z < cutoffs$z, 1, 0))
#> # A tibble: 4 x 5
#>   id        x     y     z     g
#>   <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 a         0     0     0     1
#> 2 b         0     1     0     0
#> 3 c         1     1     1     0
#> 4 d         0     1     0     1

However, i want to write a tidyeval function to do same thing, like this

get_deprivation_df <- function(df, ...) {
	cutoffs_vars = enquos(...)
	
	if( length(cutoffs_vars) == 0 ){
		stop(paste("please provide cutoffs for each column"))
	}
	
	# ...
}

and using

df %>% get_deprivation_df(
	cutoffs = list(
	x = 13,
	y = 12,
	z = 3
	)
)

I'm a beginner for tidyeval, could you please give me some help and advice?

Here is one attempt that solves it for you. This is a pattern that I seem to use quite often, but I'm not sure it's ideal since there is a lot of repetition that is not so nice, but I hope idea is clear:

library(tidyverse)
df <- tribble(
  ~id, ~x, ~y, ~z, ~g,
  #--|--|--|--|--
  "a", 13.1, 14, 4, 1,
  "b", 15.2, 7, 5, 0,
  "c", 12.5, 10, 1, 0,
  "d", 20, 11, 3, 1
)
cutoffs <- list(
  x = 13,
  y = 12,
  z = 3
)

get_deprivation_df <- function(df, ..., cutoffs) {
  vars <- rlang::enexprs(...)
  quos <- purrr::map(vars, function(var){
    rlang::quo(dplyr::if_else(!!var < cutoffs[[rlang::as_name(var)]], 1, 0))
  }) %>%
    purrr::set_names(nm = purrr::map_chr(vars, rlang::as_name))
  
  df %>%
    dplyr::mutate(!!!quos)
}

df %>%
  get_deprivation_df(x, y, z, cutoffs = cutoffs)
#> # A tibble: 4 x 5
#>   id        x     y     z     g
#>   <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 a         0     0     0     1
#> 2 b         0     1     0     0
#> 3 c         1     1     1     0
#> 4 d         0     1     0     1

Created on 2019-01-20 by the reprex package (v0.2.1)

5 Likes

If you reshape to long form, you can do everything in one call:

library(tidyverse)

df <- tribble(
    ~id, ~x, ~y, ~z, ~g,
    "a", 13.1, 14, 4, 1,
    "b", 15.2, 7, 5, 0,
    "c", 12.5, 10, 1, 0,
    "d", 20, 11, 3, 1
)

cutoffs <- list(x = 13, y = 12, z = 3)    # ideally switch `list` to `c`, but it gets coerced anyway

df %>% 
    gather(var, val, x:z) %>%
    mutate(below_cutoff = as.integer(val < cutoffs[var]))
#> # A tibble: 12 x 5
#>    id        g var     val below_cutoff
#>    <chr> <dbl> <chr> <dbl>        <int>
#>  1 a         1 x      13.1            0
#>  2 b         0 x      15.2            0
#>  3 c         0 x      12.5            1
#>  4 d         1 x      20              0
#>  5 a         1 y      14              0
#>  6 b         0 y       7              1
#>  7 c         0 y      10              1
#>  8 d         1 y      11              1
#>  9 a         1 z       4              0
#> 10 b         0 z       5              0
#> 11 c         0 z       1              1
#> 12 d         1 z       3              0

Reshape back to wide with tidyr::spread, if you like.

4 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.