Труба Магритта в функциях R


Существуют ли случаи, когда нецелесообразно использовать канал Магритта внутри функций R с точки зрения (1) скорости и (2) возможности эффективной отладки?

1 3

1 ответ:

Существуют преимущества и недостатки использования канала внутри функции. Самое большое преимущество заключается в том, что легче увидеть, что происходит внутри функции, когда вы читаете код. Самым большим недостатком является то, что сообщения об ошибках становятся более трудными для интерпретации, и труба нарушает некоторые из правил оценки R.

Вот вам пример. Предположим, мы хотим сделать бессмысленное преобразование в набор данных mtcars. Вот как мы могли бы сделать это с трубами...
library(tidyverse)
tidy_function <- function() {
  mtcars %>%
    group_by(cyl) %>%
    summarise(disp = sum(disp)) %>%
    mutate(disp = (disp ^ 4) / 10000000000)
}

Вы можете ясно смотрите, что происходит на каждом этапе, даже если это не делает ничего полезного. Теперь давайте посмотрим на временной код, используя подход сэндвича Дагвуда...

base_function <- function() {
  mutate(summarise(group_by(mtcars, cyl), disp = sum(disp)), disp = (disp^5) / 10000000000)
}
Гораздо труднее читать, хотя это дает нам тот же результат...
all.equal(tidy_function(), base_function())
# [1] TRUE
Наиболее распространенный способ избежать использования трубы или сэндвича Дагвуда-сохранить результаты каждого шага в промежуточной переменной...
intermediate_function <- function() {
  x <- mtcars
  x <- group_by(x, cyl)
  x <- summarise(x, disp = sum(disp))
  mutate(x, disp = (disp^5) / 10000000000)
}

Более читабельно, чем последняя функция, и R даст вам немного более подробную информацию информация, когда есть ошибка. Плюс он подчиняется традиционным правилам оценки. Опять же, он дает те же результаты, что и две другие функции...

all.equal(tidy_function(), intermediate_function())
# [1] TRUE

Вы специально спросили о скорости, поэтому давайте сравним эти три функции, запустив каждую из них 1000 раз...

library(microbenchmark)
timing <-
  microbenchmark(tidy_function(),
                 intermediate_function(),
                 base_function(),
                 times = 1000L)
timing
#Unit: milliseconds
                    #expr      min       lq     mean   median       uq       max neval cld
         #tidy_function() 3.809009 4.403243 5.531429 4.800918 5.860111  23.37589  1000   a
 #intermediate_function() 3.560666 4.106216 5.154006 4.519938 5.538834  21.43292  1000   a
         #base_function() 3.610992 4.136850 5.519869 4.583573 5.696737 203.66175  1000   a
Даже в этом тривиальном примере труба немного медленнее, чем два других варианта.

Заключение

Не стесняйтесь использовать трубу в своих функциях, если это наиболее удобный способ для вас, чтобы написать код. Если вы начинаете сталкиваться с проблемами или если вам нужно, чтобы ваш код был как можно быстрее, переключитесь на другую парадигму.