Труба Магритта в функциях R
Существуют ли случаи, когда нецелесообразно использовать канал Магритта внутри функций R с точки зрения (1) скорости и (2) возможности эффективной отладки?
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
Заключение
Не стесняйтесь использовать трубу в своих функциях, если это наиболее удобный способ для вас, чтобы написать код. Если вы начинаете сталкиваться с проблемами или если вам нужно, чтобы ваш код был как можно быстрее, переключитесь на другую парадигму.