Подтвердить что ты не робот

Применять несколько функций к каждой строке фрейма данных

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

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

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

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

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

4b9b3361

Ответ 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))

Очень нехорошо. Пожалуйста, обратите внимание на рекомендации Гэвина.