Почему `[` лучше чем `subset`?

400

Когда мне нужно отфильтровать data.frame, т.е. извлечь строки, которые удовлетворяют определенным условиям, я предпочитаю использовать subsetфункцию:

subset(airquality, Month == 8 & Temp > 90)

Вместо [функции:

airquality[airquality$Month == 8 & airquality$Temp > 90, ]

Есть две основные причины моего предпочтения:

  1. Я считаю, что код читается лучше, слева направо. Даже люди, которые ничего не знают о R, могут сказать, что subsetделает приведенное выше утверждение.

  2. Поскольку в selectвыражении столбцы могут называться переменными , я могу сохранить несколько нажатий клавиш. В приведенном выше примере мне нужно было набрать airqualityтолько один раз subset, но три раза [.

Так что я жил счастливым, использовал subsetвезде, потому что он короче и лучше читается, даже отстаивая его красоту среди моих коллег-программистов. Но вчера мой мир распался. Читая subsetдокументацию, я замечаю этот раздел:

Предупреждение

Это удобная функция, предназначенная для интерактивного использования. Для программирования лучше использовать стандартные функции подмножеств, такие как [, и, в частности, нестандартная оценка подмножества аргументов может иметь непредвиденные последствия.

Может ли кто-нибудь помочь уточнить, что авторы имеют в виду?

Во-первых, что они подразумевают под « для интерактивного использования »? Я знаю, что такое интерактивный сеанс, в отличие от сценария, запускаемого в режиме BATCH, но я не понимаю, какое это должно иметь значение.

Тогда, не могли бы вы объяснить « нестандартную оценку подмножества аргументов » и почему это опасно, может быть, привести пример?

flodel
источник
14
Это немного меньше (но меньше, чем подмножество) для использования,with(airquality, airquality[Month == 8 & Temp > 90, ])
Тайлер Ринкер
7
Вы также можете взглянуть на Cirlces 8.2.31 и 8.2.32 из «The R Inferno» burns-stat.com/pages/Tutor/R_inferno.pdf
Патрик Бернс
9
Вместо этого попробуйте data.table, синтаксис по умолчанию похож на airquality [Month == 8 & Temp> 90,] - очень читабельный и намного быстрее.
Стан Хаклев
3
ХОРОШО. так что если подмножество плохо использовать - как насчет [против dplyr :: filter ()?
userJT
4
Для тех, кто интересуется, dplyr::filterесть такая же проблема. Т.е. если в среде есть переменная с таким именем, она будет использовать ее вместо переменной во фрейме данных. Делает для запутанной отладки!
Deleet

Ответы:

241

На этот вопрос хорошо ответили в комментариях @James, указывая на превосходное объяснение Хэдли Уикхемом опасности subset(и подобных ей функций) [здесь] . Иди прочти это!

Это довольно длинное чтение, поэтому может быть полезно записать здесь пример, который использует Хэдли, который самым непосредственным образом решает вопрос «что может пойти не так?»:

Хэдли предлагает следующий пример: предположим, что мы хотим поместить в подмножество, а затем переупорядочить фрейм данных, используя следующие функции:

scramble <- function(x) x[sample(nrow(x)), ]

subscramble <- function(x, condition) {
  scramble(subset(x, condition))
}

subscramble(mtcars, cyl == 4)

Это возвращает ошибку:

Ошибка в eval (expr, envir, enclos): объект 'cyl' не найден

потому что R больше не «знает», где найти объект с именем «cyl». Он также указывает на действительно странные вещи, которые могут произойти, если случайно в глобальной среде появится объект с именем 'cyl':

cyl <- 4
subscramble(mtcars, cyl == 4)

cyl <- sample(10, 100, rep = T)
subscramble(mtcars, cyl == 4)

(Запустите их и убедитесь сами, это довольно безумно.)

joran
источник
2
Могу ли я задать несколько вопросов новичку для уточнения? Когда мы пишем subset(mtcars, cyl == 4)(на верхнем уровне), где R ищет цил? Если он смотрит на mtcarsобъект, который передается subset(), то не должен ли он быть в состоянии найти, cylдаже если scrambleнаходится в другой функции, так mtcarsкак он все еще передается ему? Если мой вопрос не имеет смысла, вы можете подробнее рассказать о том, почему R больше не может найти cyl. Спасибо!
Гейзенберг
4
@Anh Внутри subset.data.frame, вещь, которую мы пытаемся оценить в этот момент, просто condition. Это не существует в mtcars. Так subset.data.frameиспользует, enclos = parent.frame()чтобы убедиться, что conditionправильно оценивается как cyl == 4. Но потом мы вернулись обратно к рамке, и теперь, когда R ищет cylего, он больше не заглядывает внутрь mtcars. Если бы мы не использовали enclos, что-то вроде subset(mtcars,cyl == a)не сработало бы вообще.
Джоран
Кто-нибудь знает, почему subset () не просто реализует более быстрый и безопасный метод [,] за кулисами?
Bjorks номер один фанат
1
@MikePalmice Это делает. Последняя строка subset.data.frameявляется x[r, vars, drop = drop]. Проблема в том, как перейти от не заключенных в кавычки subsetи selectаргументов к чему-то, что вы можете достоверно передать [.data.frame.
Джоран
@joran понял, спасибо. как вы думаете, стоит ли использовать фильтр dplyr вместо []?
Bjorks номер один фан
30

И [скорее:

require(microbenchmark)        
microbenchmark(subset(airquality, Month == 8 & Temp > 90),airquality[airquality$Month == 8 & airquality$Temp > 90,])
    Unit: microseconds
                                                           expr     min       lq   median       uq     max neval
                     subset(airquality, Month == 8 & Temp > 90) 301.994 312.1565 317.3600 349.4170 500.903   100
     airquality[airquality$Month == 8 & airquality$Temp > 90, ] 234.807 239.3125 244.2715 271.7885 340.058   100
bartektartanus
источник
36
Да и нет. Я думаю, что разница во времени, которую вы видите, связана с двумя вещами. 1) небольшая (<100 микросекунд) служебная нагрузка и 2) в subsetотличие от [удаления строк, в которых оценивается фильтр NA. Сделайте это, и вы увидите, что они оба такие же быстрые, если сравнивать их «довольно»:x <- do.call(rbind, rep(list(airquality), 100)); microbenchmark(subset(x, Month == 8 & Temp > 90),{ i <- x$Month == 8 & x$Temp > 90; x[!is.na(i) & i ,] })
flodel