Применять несколько функций к каждой строке фрейма данных
Каждый раз, когда я думаю, что понимаю работу с векторами, то, что кажется простой проблемой, превращает мою голову наизнанку. Множество чтений и попыток разных примеров не помогло по этому поводу. Пожалуйста, ложь, кормите меня здесь...
Я хочу применить две пользовательские функции к каждой строке фрейма данных и добавить результаты в виде двух новых столбцов. Вот мой пример кода:
# Required packages:
library(plyr)
FindMFE <- function(x) {
MFE <- max(x, na.rm = TRUE)
MFE <- ifelse(is.infinite(MFE ) | (MFE < 0), 0, MFE)
return(MFE)
}
FindMAE <- function(x) {
MAE <- min(x, na.rm = TRUE)
MAE <- ifelse(is.infinite(MAE) | (MAE> 0), 0, MAE)
return(MAE)
}
FindMAEandMFE <- function(x){
# I know this next line is wrong...
z <- apply(x, 1, FindMFE, FindMFE)
return(z)
}
df1 <- data.frame(Bar1=c(1,2,3,-3,-2,-1),Bar2=c(3,1,3,-2,-3,-1))
df1 = transform(df1,
FindMAEandMFE(df1)
)
#DF1 should end up with the following data...
#Bar1 Bar2 MFE MAE
#1 3 3 0
#2 1 2 0
#3 3 3 0
#-3 -2 0 -3
#-2 -3 0 -3
#-1 -1 0 -1
Было бы здорово получить ответ, используя библиотеку plyr и более базовый подход. Оба помогут в моем понимании. Конечно, пожалуйста, укажите, где я ошибаюсь, если это очевидно.; -)
Теперь вернемся к файлам справки для меня!
Изменить: я хотел бы, чтобы многомерное решение, поскольку имена столбцов могут меняться и расширяться со временем. Это также позволяет повторно использовать код в будущем.
Ответы
Ответ 1
Думаю, вы слишком много думаете об этом. Что не так с двумя отдельными вызовами apply()
? Однако есть гораздо лучший способ сделать то, что вы здесь делаете, что не требует вызовов цикла/приложения. Я рассмотрю их отдельно, но второе решение предпочтительнее, поскольку оно действительно векторизовано.
Два варианта использования вызовов
Первые два отдельных запроса на использование с использованием всех функций Base R:
df1 <- data.frame(Bar1=c(1,2,3,-3,-2,-1),Bar2=c(3,1,3,-2,-3,-1))
df1 <- transform(df1, MFE = apply(df1, 1, FindMFE), MAE = apply(df1, 1, FindMAE))
df1
Что дает:
> df1
Bar1 Bar2 MFE MAE
1 1 3 3 0
2 2 1 2 0
3 3 3 3 0
4 -3 -2 0 -3
5 -2 -3 0 -3
6 -1 -1 0 -1
Хорошо, цикл по строкам df1
в два раза, пожалуй, немного неэффективен, но даже для больших проблем вы потратили больше времени на размышления о том, чтобы сделать это умно за один проход, чем вы сэкономите, сделав это.
Использование векторизованных функций pmax()
и pmin()
Итак, лучший способ сделать это - отметить функции pmax()
и pmin()
и понять, что они могут делать то, что делали все вызовы apply(df1, 1, FindFOO()
. Например:
> (tmp <- with(df1, pmax(0, Bar1, Bar2, na.rm = TRUE)))
[1] 3 2 3 0 0 0
будет MFE из вашего Вопроса. Это очень просто работать, если у вас есть два столбца, и они всегда Bar1
и Bar2
или первые 2 столбца df1
. Но это не очень общее; что, если у вас есть несколько столбцов, которые вы хотите вычислить и т.д.? pmax(df1[, 1:2], na.rm = TRUE)
не будет делать то, что мы хотим:
> pmax(df1[, 1:2], na.rm = TRUE)
Bar1 Bar2
1 1 3
2 2 1
3 3 3
4 -3 -2
5 -2 -3
6 -1 -1
Трюк для получения общего решения с использованием pmax()
и pmin()
заключается в использовании do.call()
для упорядочения вызовов этих двух функций для нас. Обновление ваших функций для использования этой идеи у нас есть:
FindMFE2 <- function(x) {
MFE <- do.call(pmax, c(as.list(x), 0, na.rm = TRUE))
MFE[is.infinite(MFE)] <- 0
MFE
}
FindMAE2 <- function(x) {
MAE <- do.call(pmin, c(as.list(x), 0, na.rm = TRUE))
MAE[is.infinite(MAE)] <- 0
MAE
}
которые дают:
> transform(df1, MFE = FindMFE2(df1), MAE = FindMAE2(df1))
Bar1 Bar2 MFE MAE
1 1 3 3 0
2 2 1 2 0
3 3 3 3 0
4 -3 -2 0 -3
5 -2 -3 0 -3
6 -1 -1 0 -1
а не apply()
. Если вы хотите сделать это за один шаг, теперь это намного проще обернуть:
FindMAEandMFE2 <- function(x){
cbind(MFE = FindMFE2(x), MAE = FindMAE2(x))
}
который можно использовать как:
> cbind(df1, FindMAEandMFE2(df1))
Bar1 Bar2 MFE MAE
1 1 3 3 0
2 2 1 2 0
3 3 3 3 0
4 -3 -2 0 -3
5 -2 -3 0 -3
6 -1 -1 0 -1
Ответ 2
Я показываю три альтернативных однострочных:
- Использование функции
each
plyr
- Использование функции
plyr
each
с базой R
- Используя функции
pmin
и pmax
, которые векторизуют
Решение 1: plyr и каждый
Пакет plyr
определяет функцию each
, которая делает то, что вы хотите. От ?each
: объединение нескольких функций в одну функцию. Это означает, что вы можете решить свою проблему с помощью однострочного интерфейса:
library(plyr)
adply(df1, 1, each(MAE=function(x)max(x, 0), MFE=function(x)min(x, 0)))
Bar1 Bar2 MAE MFE
1 1 3 3 0
2 2 1 2 0
3 3 3 3 0
4 -3 -2 0 -3
5 -2 -3 0 -3
6 -1 -1 0 -1
Решение 2: каждый и основание R
Вы можете, конечно, использовать each
с базовыми функциями. Вот как вы можете использовать его с apply
- просто отметьте, что вам нужно перенести результаты перед добавлением в исходный файл data.frame.
library(plyr)
data.frame(df1,
t(apply(df1, 1, each(MAE=function(x)max(x, 0), MFE=function(x)min(x, 0)))))
Bar1 Bar2 MAE MFE
1 1 3 3 0
2 2 1 2 0
3 3 3 3 0
4 -3 -2 0 -3
5 -2 -3 0 -3
6 -1 -1 0 -1
Решение 3: использование векторизованных функций
Используя векторизованные функции pmin
и pmax
, вы можете использовать этот однострочный слой:
transform(df1, MFE=pmax(0, Bar1, Bar2), MAE=pmin(0, Bar1, Bar2))
Bar1 Bar2 MFE MAE
1 1 3 3 0
2 2 1 2 0
3 3 3 3 0
4 -3 -2 0 -3
5 -2 -3 0 -3
6 -1 -1 0 -1
Ответ 3
Здесь есть много хороших ответов. Я начал это, пока Гавин Симпсон редактировал, поэтому мы покрываем некоторую подобную почву. То, что делают параллельные min и max (pmin и pmax), в точности соответствует тому, для чего вы пишете свои функции. Может быть немного непрозрачно то, что делает 0 в pmax (0, Bar1, Bar2), но по существу 0 получает рециркуляцию, так что ему нравится делать
pmax(c(0,0,0,0,0,0), Bar1, Bar2)
Это займет каждый элемент из трех вещей и найдет их максимум. Таким образом, max будет 0, если он был отрицательным, и выполняет большую часть того, что сделал ваш оператор ifelse. Вы можете переписать так, чтобы получить векторы и комбинировать вещи с функциями, аналогичными тем, что вы делали, и это может сделать его более прозрачным. В этом случае мы просто передадим dataframe новой функции быстрого и быстрого findMFE, которая будет работать с любым числовым фреймворком данных и вытащить вектор.
findMFE <- function(dataf){
MFE <- do.call( pmax, c(dataf, 0, na.rm = TRUE))
}
MFE <- findMFE(df1)
Что делает эта функция, так это добавить дополнительный столбец из 0s в переданный кадр данных, а затем вызвать pmax, проходящий каждый отдельный столбец df1, как если бы это был список (списки данных - это списки, так что это легко).
Теперь я отмечаю, что вы действительно хотите исправить значения Inf в своих данных, которых нет в вашем примере... мы могли бы добавить дополнительную строку к вашей функции...
findMFE <- function(dataf){
MFE <- do.call( pmax, c(dataf, 0, na.rm = TRUE))
ifelse(is.infinite(MFE), 0, MFE)
}
Теперь это правильное использование функции ifelse() на векторе. Я сделал это в качестве примера для вас, но использование Gavin Simpson MFE [is.infinite(MFE)] < - 0 более эффективно. Обратите внимание, что эта функция findMFE не используется в цикле, она просто передала весь кадр данных.
Соответствующий findMAE...
findMAE <- function(dataf){
MAE <- do.call( pmin, c(dataf, 0, na.rm = TRUE))
ifelse(is.infinite(MAE), 0, MAE)
}
и комбинированная функция просто...
findMFEandMAE <- function(dataf){
MFE <- findMFE(dataf)
MAE <- findMAE(dataf)
return(data.frame(MFE, MAE))
}
MFEandMAE < - findMFEandMAE (df1)
df1 < -cbind (df1, MFE и MAE)
Некоторые советы
Если у вас есть скалярный оператор if, не используйте ifelse(), используйте if() else. Это намного быстрее в скалярных ситуациях. И ваши функции скалярны, и вы пытаетесь их векторизовать. ifelse() уже векторизован и работает очень быстро, когда используется таким образом, но намного медленнее, чем if() else при использовании скалярного.
Кроме того, если вы собираетесь помещать материал в цикл или применять выражение, поместите его как можно меньше. Например, в вашем случае ifelse() действительно необходимо было вынуть из цикла и затем применить к всему результату MFE.
Ответ 4
Если вы действительно этого хотите, вы можете:
FindMAEandMFE <- function(x){
t(apply(x, 1, function(currow){c(MAE=FindMAE(currow), MFE=FindMFE(currow))}))
}
(не проверено - он должен возвращать массив с двумя столбцами (названный, я думаю) и столько же строк, сколько имел data.frame). Теперь вы можете сделать:
df1<-cbind(df1, FindMAEandMFE(df1))
Очень нехорошо. Пожалуйста, обратите внимание на рекомендации Гэвина.