Appliquer plusieurs fonctions à chaque ligne d'une dataframe

chaque fois que je pense comprendre comment travailler avec des vecteurs, ce qui semble être un simple problème me retourne la tête. Beaucoup de lecture et d'essais de différents exemples n'ont pas aidé à cette occasion. Veuillez cuillère à me nourrir ici...

je veux appliquer deux fonctions personnalisées pour chaque ligne d'un dataframe et ajouter les résultats de deux nouvelles colonnes. Voici mon exemple de code:

# 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

ce serait génial d'obtenir une réponse en utilisant la bibliothèque plyr et une base comme approche. Les deux vont de l'aide dans ma compréhension. Bien sûr, nous vous prions d'où je vais mal, si elle est évidente. ; -)

Maintenant vers les fichiers d'aide pour moi!

Edit: je voudrais une solution multivariée car les noms de colonne peuvent changer et s'étendre avec le temps. Il permet également de réutiliser le code à l'avenir.

20
demandé sur Look Left 2011-08-24 14:23:22

4 réponses

je pense que vous pensez trop complexe ici. Quel est le problème avec deux apply() appels? Il y a cependant une bien meilleure façon de faire ce que vous faites ici qui ne comporte pas de boucle/appliquer les appels. Je vais les traiter séparément, mais la deuxième solution est préférable car elle est vraiment vectorisée.

appliquer Deux appels version

les deux premiers appels distincts s'appliquent en utilisant des fonctions toutes bases 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

ce Qui donne:

> 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

Ok, boucler sur les lignes de df1 deux fois est peut-être un peu inefficace, mais même pour les grands problèmes vous avez passé plus de temps déjà pensée à propos de faire cela habilement en une seule passe que vous sauverez en faisant de cette façon.

utilisant des fonctions vectorisées pmax() et pmin()

donc une meilleure façon de faire ceci est de noter le pmax() et pmin() fonctions et de réaliser qu'ils peuvent faire ce que chaque apply(df1, 1, FindFOO() les appels passaient. Pour exemple:

> (tmp <- with(df1, pmax(0, Bar1, Bar2, na.rm = TRUE)))
[1] 3 2 3 0 0 0

serait MFE de votre Question. C'est très simple à travailler avec si vous avez deux colonnes et ils sont Bar1 et Bar2 ou les 2 premières colonnes de df1, toujours. Mais ce n'est pas très général; et si vous avez plusieurs colonnes que vous voulez calculer sur etc? pmax(df1[, 1:2], na.rm = TRUE) ne pas faire ce que nous voulons:

> 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

L'astuce pour obtenir une solution générale à l'aide de pmax() et pmin() est d'utiliser do.call() pour organiser les appels à ces deux fonctions pour nous. Mise à jour de vos fonctions pour utiliser cette idée, nous avons:

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
}

ce qui donne:

> 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

et pas apply() dans la vue. Si vous voulez faire cela en une seule étape, c'est maintenant beaucoup plus facile à enrouler:

FindMAEandMFE2 <- function(x){
    cbind(MFE = FindMFE2(x), MAE = FindMAE2(x))
}

qui peut être utilisé comme:

> 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
19
répondu Gavin Simpson 2011-08-24 11:14:46

je montre trois autres one-liners:

  • each fonction de plyr
  • plyreach function avec la base de R
  • pmin et pmax fonctions qui sont vectoriser

Solution 1: plyr et chaque

plyr le paquet définit le each fonction qui fait ce que vous voulez. À partir de ?each:regrouper plusieurs fonctions en un seul fonction. cela signifie que vous pouvez résoudre votre problème en utilisant une doublure unique:

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

Solution 2: chaque et de la base de R

Vous pouvez, bien sûr, utiliser each avec des fonctions de base. Voici comment vous pouvez l'utiliser avec apply - il suffit de noter que vous devez transposer les résultats avant d'ajouter à vos données originales.cadre.

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

Solution 3: Utilisation de fonctions vectorisées

utilisant des fonctions vectorisées pmin et pmax, vous pouvez utiliser cette ligne de commande:

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
répondu Andrie 2011-08-24 11:55:33

Il y a beaucoup de bonnes réponses ici. J'ai commencé ça pendant que Gavin Simpson éditait donc nous couvrons un terrain similaire. Ce que font les min et max parallèles (pmin et pmax) est à peu près exactement ce pour quoi vous écrivez vos fonctions. Il peut être un peu opaque ce que le 0 fait dans pmax (0, Bar1, Bar2) mais essentiellement 0 est recyclé de sorte que c'est comme faire

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

qui va prendre chaque élément des trois choses passées et trouver le max d'entre eux. Donc, le max sera 0 s'il était négatif et accomplit une grande partie de ce que votre déclaration ifelse a fait. Vous pouvez réécrire de sorte que vous obtenez des vecteurs et combiner des choses avec des fonctions similaires à ce que vous faisiez et qui pourrait le rendre un peu plus transparent. Dans ce cas, nous ne faisons que passer la dataframe à une nouvelle fonction parallèle et rapide de findMFE qui fonctionnera avec n'importe quelle dataframe numérique et sortira un vecteur.

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

MFE <- findMFE(df1)

Ce que cette fonction n'est d'ajouter une colonne supplémentaire de 0s à la trame de données et ensuite appeler pmax passer chaque colonne séparée de df1 comme si c'était une liste (les dataframes sont des listes donc c'est facile).

maintenant, je note que vous voulez en fait corriger les valeurs Inf dans vos données qui ne sont pas dans votre exemple... nous pourrions ajouter une ligne supplémentaire à votre fonction...

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

maintenant, c'est l'utilisation correcte de la fonction ifelse() sur un vecteur. Je l'ai fait comme un exemple pour vous, mais L'utilisation de MFE par Gavin Simpson est.infinite (MFE)] <- 0 est plus efficace. Notez que cette findMFE la fonction n'est pas utilisée dans une boucle, elle passe juste toute la base de données.

L'comparables findMAE est...

findMAE <- function(dataf){
    MAE <- do.call( pmin, c(dataf, 0, na.rm = TRUE))
    ifelse(is.infinite(MAE), 0, MAE)
}

et la fonction combinée est tout simplement...

findMFEandMAE <- function(dataf){
    MFE <- findMFE(dataf)
    MAE <- findMAE(dataf)
    return(data.frame(MFE, MAE))
}

Pfeandmae < - findMFEandMAE (df1) df1 < - cbind (df1, MFEandMAE)

Quelques conseils

si vous avez une déclaration scalaire if n'utilisez pas ifelse (), utilisez if () else. C'est beaucoup plus rapide dans des situations scalaires. Vos fonctions sont scalaires et vous essayez de les vectoriser. ifelse() est déjà vectorisé et fonctionne très rapidement lorsqu'il est utilisé de cette façon mais beaucoup plus lentement que si () autrement lorsqu'il est utilisé scalar.

Aussi, si vous allez mettre des trucs dans une boucle ou d'appliquer la déclaration mettre aussi peu possible. Par exemple, dans votre cas, l'ifelse() devait vraiment être retiré de la boucle et appliqué à l'ensemble du résultat MFE par la suite.

6
répondu John 2017-10-03 18:38:56

Si vous vraiment, voulez vraiment, vous pouvez:

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

(pas testé - elle doit retourner un tableau avec deux (nommé, je crois), les colonnes et autant de lignes que de données.cadre a). Maintenant, vous pouvez faire:

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

Très dégueulasse. Suivez les conseils de Gavin.

1
répondu Nick Sabbe 2011-08-24 10:49:24