R: применить против do.call

Я только что прочитал профиль @David Arenburg и нашел кучу полезных советов о том, как развить хорошие навыки/привычки R-программирования, и один из них особенно поразил меня. Я всегда думал, что функции применения в R были краеугольным камнем работы с фреймами данных, но он пишет:

Если вы работаете с data.frames, забудьте о функции apply — что бы вы ни делали — не используйте ее. Особенно с маржой 1 (единственный хороший вариант использования этой функции — работать со столбцами матрицы — маржа 2).

Несколько хороших альтернатив: ?do.call, ?pmax/pmin, ?max.col, ?rowSums/rowMeans/etc, замечательные пакеты matrixStats (для матриц), ?rowsum и многие другие.

Кто-нибудь может мне это объяснить? Почему функции применения не одобряются?


person Helen    schedule 06.06.2018    source источник
comment
На самом деле я говорю конкретно о apply-, а не обо всей *apply семье. Основная проблема с apply заключается в том, что он преобразует все данные в матрицу, которая искажает данные (поскольку matrix не может хранить разные классы, в отличие от фрейма данных), поэтому дает неожиданные результаты. Следовательно, при работе со столбцами лучше использовать остальную часть семейства *apply, например lapply или sapply. С другой стороны, поскольку R векторизован, язык apply с запасом 1 будет очень медленным (независимо от проблемы matrix), поэтому я предлагаю вместо этого использовать векторизованные альтернативы.   -  person David Arenburg    schedule 06.06.2018
comment
Ага, понял, большое спасибо за разъяснение!   -  person Helen    schedule 06.06.2018
comment
Кроме того, это полезная информация о семействе *apply.   -  person David Arenburg    schedule 06.06.2018
comment
Здорово! Спасибо еще раз :)   -  person Helen    schedule 06.06.2018


Ответы (3)


  • apply(DF, 1, f) преобразует каждую строку DF в вектор, а затем передает этот вектор в f. Если бы DF было смесью строк и чисел, тогда строка была бы преобразована в вектор символов перед передачей ее в f, так что, например, apply(iris, 1, function(x) sum(x[-5])) не будет работать, даже если строка iris[i, -5] содержит все числовые элементы. Строка преобразуется в строку символов, и вы не можете суммировать строки символов. С другой стороны, apply(iris[-5], 1, sum) будет работать так же, как rowSums(iris[-5]).

  • если f создает вектор, результатом является матрица, а не другой фрейм данных; Кроме того, результатом является транспонирование того, что вы могли бы ожидать. Этот

    apply(BOD, 1, identity)
    

    дает следующее, а не возвращает BOD:

           [,1] [,2] [,3] [,4] [,5] [,6]
    Time    1.0  2.0    3    4  5.0  7.0
    demand  8.3 10.3   19   16 15.6 19.8
    

    Много лет назад Хэдли Уикхем опубликовал публикацию iapply, которая идемпотент в том смысле, что iapply(mat, 1, identity) возвращает mat, а не t(mat), где mat — матрица. Совсем недавно с его пакетом plyr можно было написать:

    library(plyr)
    ddplyr(BOD, 1, identity)
    

    и получить BOD обратно в виде фрейма данных.

С другой стороны, apply(BOD, 1, sum) даст тот же результат, что и rowSums(BOD), а apply(BOD, 1, f) может быть полезно для функций f, для которых f производит скаляр и нет аналога, как в случае sum / rowSums. Кроме того, если f создает вектор, и вы не возражаете против матричного результата, вы можете транспонировать вывод apply самостоятельно, и, хотя это уродливо, это сработает.

person G. Grothendieck    schedule 06.06.2018

Я думаю, что автор имеет в виду, что вы должны использовать предварительно созданные/векторизованные функции (потому что это проще), если вы можете, и избегать применения (потому что в принципе это цикл for и занимает больше времени):

library(microbenchmark)

d <- data.frame(a = rnorm(10, 10, 1),
                b = rnorm(10, 200, 1))

# bad - loop
microbenchmark(apply(d, 1, function(x) if (x[1] < x[2]) x[1] else x[2]))

# good - vectorized but same result
microbenchmark(pmin(d[[1]], d[[2]])) # use double brackets!

# edited:
# -------
# bad: lapply
microbenchmark(data.frame(lapply(d, round, 1)))

# good: do.call faster than lapply
microbenchmark(do.call("round", list(d, digits = 1)))

# --------------
# Unit: microseconds
#                                  expr     min    lq     mean  median      uq     max neval
# do.call("round", list(d, digits = 1)) 104.422 107.1 148.3419 134.767 184.524 332.009   100
#                            expr     min       lq     mean  median      uq      max neval
# data.frame(lapply(d, round, 1)) 235.619 243.2055 298.5042 252.353 276.004 1550.265   100
#
#                                  expr    min      lq    mean median       uq     max neval
# do.call("round", list(d, digits = 1)) 96.389 97.5055 113.075 98.175 105.5375 730.954   100
#                            expr     min       lq     mean  median      uq      max neval
# data.frame(lapply(d, round, 1)) 235.619 243.2055 298.5042 252.353 276.004 1550.265   100
person r.user.05apr    schedule 06.06.2018
comment
так что все функции применения по сути являются циклами? лаппли, саппли и т. д.? - person Helen; 06.06.2018
comment
Как это отвечает на do.call часть? - person pogibas; 06.06.2018
comment
Отредактировано. (.. и относительно цикла for; согласно burns-stat.com/pages /Tutor/R_inferno.pdf с помощью применения скрытия цикла) - person r.user.05apr; 06.06.2018
comment
Можете ли вы добавить результаты microbenchmark к своему ответу? - person Tung; 06.06.2018
comment
@Erosennin - да, применимая семья - это петли. Попробуйте прочитать этот вопрос от @DavidArunberg. - person Parfait; 06.06.2018

Это связано с тем, как R хранит матрицы и кадры данных*. Как вы, возможно, знаете, data.frame — это list векторов, то есть каждый столбец в data.frame является вектором. Будучи векторизованным языком, предпочтительнее работать с векторами, и именно по этой причине apply с запасом 2 не одобряется: при этом вы не будете работать с векторами, скорее, вы будете охватывать разные векторы на каждой итерации.

Насколько я знаю, использование apply с полем 1 не сильно отличается от использования do.call. Хотя последнее может обеспечить большую гибкость использования.

*Эта информация должна быть где-то в руководствах.

person Novice    schedule 06.06.2018