Применить несколько функций к каждой строке кадра данных - PullRequest
20 голосов
/ 24 августа 2011

Каждый раз, когда я думаю, что понимаю работу с векторами, то, что кажется простой проблемой, выворачивает мою голову наизнанку. Много читать и пробовать разные примеры не помогло в этом случае. Пожалуйста, накормите меня здесь ...

Я хочу применить две пользовательские функции к каждой строке кадра данных и добавить результаты в виде двух новых столбцов. Вот мой пример кода:

# 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 и более базовый подход. Оба помогут в моем понимании. Конечно, пожалуйста, укажите, где я иду не так, если это очевидно. ; -)

Теперь вернемся к файлам помощи для меня!

Редактировать: Мне бы хотелось многовариантное решение, так как имена столбцов могут со временем меняться и расширяться. Это также позволяет повторно использовать код в будущем.

Ответы [ 4 ]

19 голосов
/ 24 августа 2011

Я показываю три альтернативных однострочных:

  • Использование функции 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
19 голосов
/ 24 августа 2011

Я думаю, вы думаете, что здесь слишком сложно. Что не так с двумя отдельными apply() звонками? Тем не менее, есть гораздо лучший способ сделать то, что вы делаете здесь, который не включает зацикливание / применение вызовов Я рассмотрю их отдельно, но второе решение предпочтительнее, поскольку оно действительно векторизовано.

Версия двух звонков

Первые два отдельных прикладных вызова с использованием всех базовых функций 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
6 голосов
/ 24 августа 2011

Здесь много хороших ответов. Я начал это, когда Гэвин Симпсон занимался монтажом, поэтому мы рассмотрим некоторые аналогичные вопросы. То, что делают параллельные min и max (pmin и pmax), в значительной степени именно то, для чего вы пишете свои функции. Может быть немного непрозрачно, что 0 делает в pmax (0, Bar1, Bar2), но по существу 0 перерабатывается, так что это все равно что делать

pmax(c(0,0,0,0,0,0), Bar1, Bar2)

Это займет каждый предмет из трех пройденных вещей и найдет их максимум. Таким образом, максимум будет 0, если он был отрицательным и выполняет большую часть того, что сделал ваш оператор ifelse. Вы можете переписать, чтобы получить векторы и комбинировать вещи с функциями, аналогичными тем, что вы делали, и это может сделать его немного более прозрачным. В этом случае мы просто передадим фрейм данных в новую параллельную и быструю функцию findMFE, которая будет работать с любым числовым фреймом данных и получит вектор.

findMFE <- function(dataf){
    MFE <- do.call( pmax, c(dataf, 0, na.rm = TRUE))
}

MFE <- findMFE(df1)

Что делает эта функция, так это добавляет дополнительный столбец 0 к переданному фрейму данных, а затем вызывает pmax, передавая каждый отдельный столбец df1, как если бы это был список (фреймы данных являются списками, так что это легко).

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

findMFE <- function(dataf){
    MFE <- do.call( pmax, c(dataf, 0, na.rm = TRUE))
    ifelse(is.infinite(MFE), 0, MFE)
}

Теперь это правильное использование функции ifelse () для вектора. Я сделал это в качестве примера для вас, но использование MFE [is.infinite (MFE)] <- 0 Гэвином Симпсоном более эффективно. Обратите внимание, что эта функция findMFE не используется в цикле, она просто проходит весь фрейм данных. </p>

Сравнимый 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, MFEandMAE) </p>

Несколько советов

Если у вас есть скаляр, если в операторе if не используется ifelse (), используйте if () else. Это гораздо быстрее в скалярных ситуациях. И ваши функции скалярны, и вы пытаетесь их векторизовать. ifelse () уже векторизован и работает очень быстро, когда используется таким образом, но намного медленнее, чем if (), когда используется скаляр.

Кроме того, если вы собираетесь помещать вещи в цикл или применять операторы, поместите как можно меньше. Например, в вашем случае ifelse () действительно нужно было вынуть из цикла и впоследствии применить ко всему результату MFE.

1 голос
/ 24 августа 2011

Если вы действительно, действительно этого хотите, вы можете:

FindMAEandMFE <- function(x){
    t(apply(x, 1, function(currow){c(MAE=FindMAE(currow), MFE=FindMFE(currow))}))
}

(не проверено - он должен возвращать массив с двумя (именованными, я думаю) столбцами и таким количеством строк, что и data.frameимел).Теперь вы можете сделать:

df1<-cbind(df1, FindMAEandMFE(df1))

Очень неприглядно.Пожалуйста, прислушайтесь к совету Гэвина.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...