data.tabela alternatywna dla przypadku dplyr, gdy

Jakiś czas temu wprowadzili ładną alternatywę podobną do SQL do ifelse wewnątrz dplyr, czyli case_when.

Czy w data.table istnieje odpowiednik, który pozwoliłby określić różne warunki w ramach jednego [] Oświadczenia, bez ładowania dodatkowych pakietów?

Przykład:

library(dplyr)

df <- data.frame(a = c("a", "b", "a"), b = c("b", "a", "a"))

df <- df %>% mutate(
    new = case_when(
    a == "a" & b == "b" ~ "c",
    a == "b" & b == "a" ~ "d",
    TRUE ~ "e")
    )

  a b new
1 a b   c
2 b a   d
3 a a   e

Z pewnością byłoby to bardzo pomocne i uczyniłoby Kod o wiele bardziej czytelny (jeden z powodów, dla których wciąż używam dplyr w tych przypadkach).

Author: arg0naut91, 2018-10-28

4 answers

[5]} dla twojej wiadomości, bardziej aktualna odpowiedź dla tych, którzy natkną się na ten post 2019. data.table wersje powyżej 1.13.0 posiadają funkcję fcase, która może być użyta. Zauważ, że nie jest to zamiennik dplyr::case_when, ponieważ składnia jest inna, ale będzie to "natywny" data.table sposób obliczania.

# Lazy evaluation
x = 1:10
data.table::fcase(
    x < 5L, 1L,
    x >= 5L, 3L,
    x == 5L, stop("provided value is an unexpected one!")
)
# [1] 1 1 1 1 3 3 3 3 3 3

dplyr::case_when(
    x < 5L ~ 1L,
    x >= 5L ~ 3L,
    x == 5L ~ stop("provided value is an unexpected one!")
)
# Error in eval_tidy(pair$rhs, env = default_env) :
#  provided value is an unexpected one!

# Benchmark
x = sample(1:100, 3e7, replace = TRUE) # 114 MB
microbenchmark::microbenchmark(
dplyr::case_when(
  x < 10L ~ 0L,
  x < 20L ~ 10L,
  x < 30L ~ 20L,
  x < 40L ~ 30L,
  x < 50L ~ 40L,
  x < 60L ~ 50L,
  x > 60L ~ 60L
),
data.table::fcase(
  x < 10L, 0L,
  x < 20L, 10L,
  x < 30L, 20L,
  x < 40L, 30L,
  x < 50L, 40L,
  x < 60L, 50L,
  x > 60L, 60L
),
times = 5L,
unit = "s")
# Unit: seconds
#               expr   min    lq  mean   median    uq    max neval
# dplyr::case_when   11.57 11.71 12.22    11.82 12.00  14.02     5
# data.table::fcase   1.49  1.55  1.67     1.71  1.73   1.86     5

Źródło , dane.1.13.0, wydany 24 lipca 2020 roku.

 22
Author: skedaddle_waznook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-26 06:13:03

1) Jeśli warunki wzajemnie się wykluczają z wartością domyślną, jeśli wszystkie warunki są fałszywe, to działa to:

library(data.table)
DT <- as.data.table(df) # df is from question

DT[, new := c("e", "c", "d")[1 +
                             1 * (a == "a" & b == "b") + 
                             2 * (a == "b" & b == "a")]
]

Dając:

> DT
   a b new
1: a b   c
2: b a   d
3: a a   e

2) Jeżeli wyniki warunków są liczbowe, to jest to jeszcze łatwiejsze. Na przykład załóżmy, że zamiast c i d chcemy 10 i 17 z domyślną wartością 3. Wtedy:

library(data.table)
DT <- as.data.table(df) # df is from question

DT[, new := 3 + 
            (10 - 3) * (a == "a" & b == "b") + 
            (17 - 3) * (a == "b" & b == "a")]

3) zauważ, że dodanie 1-linera jest wystarczające, aby to zaimplementować. Zakłada ona, że w każdym rzędzie jest co najmniej jedna prawdziwa noga.

when <- function(...) names(match.call()[-1])[apply(cbind(...), 1, which.max)]

# test
DT[, new := when(c = a == 'a' & b == 'b', 
                 d = a == 'b' & b == 'a', 
                 e = TRUE)]
 20
Author: G. Grothendieck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-28 14:48:15

To nie jest tak naprawdę odpowiedź, ale trochę za długa na komentarz. Jeśli uznasz za nieodpowiednie, chętnie usunę post.

Istnieje ciekawy post na RStudio Community , który omawia opcje użycia dplyr::case_when bez zwykłych zależności tidyverse.

Podsumowując, wydaje się, że istnieją trzy alternatywy:

  1. Stefan Fleck wyizolował case_when z dplyr i zbudował nowy pakiet lest to zależy tylko od base.
  2. yonicd opracowany noplyr, który "zapewnia podstawową funkcjonalność dplyr i tidyr bez zależności tidyverse".
  3. Bob Rudis (hrbrmstr)jest twórcą freebase, "a' usethis '- like Package for Base R Pseudo-odpowiedniki kodu 'tidyverse'", które też warto sprawdzić.

Jeśli chodzi tylko o case_when, wyobrażam sobie lest może być atrakcyjną i minimalną opcją w połączeniu z data.table.


[[54]} Aktualizacja [29 Października 2019]

Tyson Barrett tidyfast dostępny (obecnie jako wersja 0.1.0) na Githubie, który udostępnia funkcję "dt_case_when for dplyr::case_when() składnia z szybkością data.table::fifelse()".

[[54]} Aktualizacja [25 Lutego 2020] Istnieje również dtplyr, którego autorem jest Lionel Henry i jest utrzymywany przez Hadleya Wickhama, który " zapewnia backend data.tabledla dplyr. Celem dtplyr jest pozwala na napisanie dplyr kodu, który jest automatycznie tłumaczony na odpowiednik, ale zazwyczaj znacznie szybszy, data.table kodu.".
 20
Author: Maurits Evers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-25 08:37:57

Oto wariacja na temat odpowiedzi @ g-grothendieck, która działa na niewyłącznych Warunkach:

DT[, new := c("c", "d", "e")[
  apply(cbind(
    a == "a" & b == "b", 
    a == "b" & b == "a",
    TRUE), 1, which.max)]
  ]

DT
#    a b new
# 1: a b   c
# 2: b a   d
# 3: a a   e
 2
Author: Moody_Mudskipper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-03 12:20:33