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

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

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

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

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

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


person Look Left    schedule 24.08.2011    source источник


Ответы (4)


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

Версия с двумя применимыми вызовами

Первые два отдельных вызова apply с использованием функций All-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
person Gavin Simpson    schedule 24.08.2011
comment
@LookLeft - Что касается вашего редактирования, я почти уверен, что векторизованное решение Гэвина _1 _, _ 2_ будет обрабатывать фрейм данных с любым количеством столбцов и с любыми именами. Но я просто догадываюсь, что вы здесь подразумеваете под «многомерным». - person joran; 25.08.2011
comment
+1 за отличное описание. Проблема зацикливания и общее решение с использованием do.call были очень полезными. Я начинаю больше разбираться в векторах и том, как их обрабатывают функции R. Я продолжу играть с каждым примером. - person Look Left; 25.08.2011
comment
@joran. Да, изменение было внесено в ответ на комментарий и ответ Гэвина. Он заметил ограничение и дал отличный ответ. - person Look Left; 25.08.2011

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

  • Использование each функции plyr
  • Использование функции plyr each с базой R
  • Использование векторных функций pmin и pmax

Решение 1.Плир и каждый

Пакет plyr определяет функцию each, которая делает то, что вы хотите. From ?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
person Andrie    schedule 24.08.2011
comment
Просто показывал это. Вы получаете дополнительный бонусный балл за 0 в столбцах pmin / max. Я получаю дополнительные бонусные баллы за разрешение любого количества столбцов в df1: P - person Gavin Simpson; 24.08.2011
comment
@GavinSimpson В моем исправленном ответе показаны три альтернативных (однострочных) способа решения проблемы, два из которых допускают любое количество столбцов. - person Andrie; 24.08.2011
comment
Теперь вы просто выпендриваетесь! ;-) Отлично. Решения 1 и 2 будут медленными (собака медленно решает большие проблемы), и нам, вероятно, не следует поощрять использование невекторизованных решений вместо векторизованных. Но неясно, хочет ли OP общее решение для многомерного применения или решение этой конкретной проблемы. Так что я позволю бесплатному использованию plyr пройти это один раз ;-) - person Gavin Simpson; 24.08.2011
comment
Я хочу многовариантное решение. Вау, извините, пожалуйста, пока я перевариваю всю эту замечательную помощь, и я свяжусь со всеми как можно скорее. - person Look Left; 25.08.2011
comment
+1 за прохождение стороны plyr (). Баг, спасибо! Я перепробовал все три, и Гэвин оказался на высоте. Используя мои фактические данные (100c x 23000r) и немного более сложный процесс, в котором последовательно добавляются столбцы, скорость каждого из них составляет: do.call (Gavin) - 29 секунд, решение 2 - 105 секунд, а решение 1 ... все еще ждет . Так что библиотека plyr () в данном случае - не лучшее решение. Продолжу тестирование. - person Look Left; 25.08.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)

Эта функция добавляет дополнительный столбец нулей в переданный фрейм данных, а затем вызывает 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 не используется в цикле, она просто передает весь фрейм данных.

Сопоставимая находкаMAE ...

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)

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

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

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

person John    schedule 24.08.2011
comment
Я принял вызов и предоставил однострочное решение с использованием функций plyr и normal min и max в моем исправленном ответе. - person Andrie; 24.08.2011
comment
Спасибо, Джон. Описание дополнительного столбца 0 было полезно, и объяснение наилучшего использования ifelse () все еще дайджест; т.е. петля против всего. Я надеюсь, что однажды верну всем услугу или передам ее вперед. - person Look Left; 25.08.2011

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

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

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

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

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

person Nick Sabbe    schedule 24.08.2011
comment
Спасибо, я буду придерживаться совета Гэвина. - person Look Left; 25.08.2011