markfairbanks / tidytable

Tidy interface to 'data.table'
https://markfairbanks.github.io/tidytable/
Other
450 stars 32 forks source link

case_when not working properly when one condition includes NAs #751

Closed bnicenboim closed 1 year ago

bnicenboim commented 1 year ago

Hi,

case_when is not working with tidytable when one condition includes NAs, but if you replace the NA for a number, it will work (or if you use dplyr).

tidytable::tidytable(X = 1:10, Y=rep(c("a","b"),5)) |>
  tidytable::mutate(X = tidytable::case_when(Y == "a" ~ NA,
                               .default = X))
#> Error in `tidytable::case()`:
#> ! Can't convert from `default` <integer> to <logical> due to loss of precision.
#> • Locations: 2, 3, 4, 5, 6, 7, 8, 9, 10
#> Backtrace:
#>      ▆
#>   1. ├─tidytable::mutate(...)
#>   2. │ └─tidytable:::tt_mutate(...)
#>   3. │   └─rlang::eval_tidy(dt_expr, .df, dt_env)
#>   4. ├─...[]
#>   5. ├─data.table:::`[.data.table`(...)
#>   6. │ └─base::eval(jsub, SDenv, parent.frame())
#>   7. │   └─base::eval(jsub, SDenv, parent.frame())
#>   8. │     ├─vctrs::vec_recycle(...)
#>   9. │     └─tidytable::case_when(Y == "a" ~ NA, .default = X)
#>  10. │       └─rlang::eval_tidy(out)
#>  11. └─tidytable::case(Y == "a", NA, default = `<int>`, ptype = NULL, size = NULL)
#>  12.   └─vctrs::vec_cast(default, ptype)
#>  13.     └─vctrs (local) `<fn>`()
#>  14.       └─vctrs:::vec_cast.logical.integer(...)
#>  15.         └─vctrs::maybe_lossy_cast(...)
#>  16.           ├─base::withRestarts(...)
#>  17.           │ └─base (local) withOneRestart(expr, restarts[[1L]])
#>  18.           │   └─base (local) doWithOneRestart(return(expr), restart)
#>  19.           └─vctrs:::stop_lossy_cast(...)
#>  20.             └─vctrs::stop_incompatible_cast(...)
#>  21.               └─vctrs::stop_incompatible_type(...)
#>  22.                 └─vctrs:::stop_incompatible(...)
#>  23.                   └─vctrs:::stop_vctrs(...)
#>  24.                     └─rlang::abort(message, class = c(class, "vctrs_error"), ..., call = call)

tidytable::tidytable(X = 1:10, Y=rep(c("a","b"),5)) |>
  tidytable::mutate(X = tidytable::case_when(Y == "a" ~ 1,
                                             .default = X))
#> # A tidytable: 10 × 2
#>        X Y    
#>    <dbl> <chr>
#>  1     1 a    
#>  2     2 b    
#>  3     1 a    
#>  4     4 b    
#>  5     1 a    
#>  6     6 b    
#>  7     1 a    
#>  8     8 b    
#>  9     1 a    
#> 10    10 b

dplyr::tibble(X = 1:10, Y=rep(c("a","b"),5)) |>
  dplyr::mutate(X = dplyr::case_when(Y == "a" ~ NA,
                       .default = X))
#> # A tibble: 10 × 2
#>        X Y    
#>    <int> <chr>
#>  1    NA a    
#>  2     2 b    
#>  3    NA a    
#>  4     4 b    
#>  5    NA a    
#>  6     6 b    
#>  7    NA a    
#>  8     8 b    
#>  9    NA a    
#> 10    10 b

Created on 2023-03-19 with reprex v2.0.2

markfairbanks commented 1 year ago

Looks like it's not using .default to find the common ptype.

pacman::p_load(tidytable)

df <- tidytable(x = 1:3, y = c("a", "b", "c"))

# Works
df %>%
  mutate(new = case_when(y == "a" ~ NA,
                         y == "b" ~ 1,
                         .default = x))
#> # A tidytable: 3 × 3
#>       x y       new
#>   <int> <chr> <dbl>
#> 1     1 a        NA
#> 2     2 b         1
#> 3     3 c         3

# Fails
df %>%
  mutate(new = case_when(y == "a" ~ NA,
                         .default = x))
#> Error in `tidytable::case()`:
#> ! Can't convert from `default` <integer> to <logical> due to loss of precision.
#> • Locations: 2, 3

#> Backtrace:
#>      ▆
#>   1. ├─df %>% mutate(new = case_when(y == "a" ~ NA, .default = x))
#>   2. ├─tidytable::mutate(., new = case_when(y == "a" ~ NA, .default = x))
#>   3. │ └─tidytable:::tt_mutate(...)
#>   4. │   └─rlang::eval_tidy(dt_expr, .df, dt_env)
#>   5. ├─.df[, `:=`(new = case_when(y == "a" ~ NA, .default = x))]
#>   6. ├─data.table::`[.data.table`(...)
#>   7. │ └─base::eval(jsub, SDenv, parent.frame())
#>   8. │   └─base::eval(jsub, SDenv, parent.frame())
#>   9. │     └─tidytable::case_when(y == "a" ~ NA, .default = x)
#>  10. │       └─rlang::eval_tidy(out)
#>  11. └─tidytable::case(y == "a", NA, default = `<int>`, ptype = NULL, size = NULL)
#>  12.   └─vctrs::vec_cast(default, ptype)
#>  13.     └─vctrs (local) `<fn>`()
#>  14.       └─vctrs:::vec_cast.logical.integer(...)
#>  15.         └─vctrs::maybe_lossy_cast(...)
#>  16.           ├─base::withRestarts(...)
#>  17.           │ └─base (local) withOneRestart(expr, restarts[[1L]])
#>  18.           │   └─base (local) doWithOneRestart(return(expr), restart)
#>  19.           └─vctrs:::stop_lossy_cast(...)
#>  20.             └─vctrs::stop_incompatible_cast(...)
#>  21.               └─vctrs::stop_incompatible_type(...)
#>  22.                 └─vctrs:::stop_incompatible(...)
#>  23.                   └─vctrs:::stop_vctrs(...)
#>  24.                     └─rlang::abort(message, class = c(class, "vctrs_error"), ..., call = vctrs_error_call(call))
markfairbanks commented 1 year ago

All set.

pacman::p_load(tidytable)

tidytable(X = 1:10, Y=rep(c("a","b"),5)) |>
  mutate(X = case_when(Y == "a" ~ NA,
                       .default = X))
#> # A tidytable: 10 × 2
#>        X Y    
#>    <int> <chr>
#>  1    NA a    
#>  2     2 b    
#>  3    NA a    
#>  4     4 b    
#>  5    NA a    
#>  6     6 b    
#>  7    NA a    
#>  8     8 b    
#>  9    NA a    
#> 10    10 b
bnicenboim commented 1 year ago

Thanks!!!