2017-06-19 1 views
0

J'ai 2 fichiers.R: problème de fonction défini par l'utilisateur avec appliquer

"increment.tab"

grp increment 
1 10 
2 25 
3 35 
4 50 

"input.tab"

grp pos 
1 10 
1 14 
1 25 
2 3 
2 20 
3 2 
3 10 

Je suis en train d'appliquer une augmentation à la colonne 2 de 'input.tab' comme suit par exemple:

if grp=1, then increment=0 
if grp=2, then increment=10 
if grp=3, then increment=10+25=35 
if grp=4, then increment=10+25+35=70 
... 

afin d'obtenir cette sortie:

grp pos pos_adj 
1 10 10 
1 14 14 
1 25 25 
2 3 13 
2 20 30 
3 2 37 
3 10 45 

Mon plan est d'utiliser apply pour traiter la ligne du fichier d'entrée par ligne:

ref <- read.table("increment.tab", header=T, sep="\t") 
input <- read.table("input.tab", header=T, sep="\t") 

my_fun <- function(x, y){ 
    if(x==1){ 
     inc=0 
    } 
    else{ 
     inc=sum(ref[1:match(x, ref$grp)-1,2]) 
    } 
    result = y + inc 
    return(result) 
} 

input$pos_adj = apply(input, 1, my_fun(input$grp, input$pos)) 

Mais je reçois ce message d'erreur que je ne peux pas vraiment comprendre.

Error in match.fun(FUN) : 
    'my_fun(input$grp, input$pos)' is not a function, character or symbol 
In addition: Warning message: 
In if (x == 1) { : 
    the condition has length > 1 and only the first element will be used 

Pourquoi 'my_fun' n'est pas considéré comme une fonction?

Répondre

2

Votre appel apply échoue parce que votre troisième argument est le résultat d'un appel de fonction, pas une fonction elle-même. De plus, bien qu'il puisse fonctionner avec vos données rudimentaires, s'il y a d'autres types de données dans votre data.frame, il échouera car apply convertit le data.frame en matrix, ce qui entraînera des conversions de type. C'est à cause de cela (et quelques autres raisons) que je recommande de ne pas utiliser apply ici.

Je pense que vous pouvez le vectoriser assez facilement, et l'astuce pour apporter les additions grp peut être résolue avec merge. (Il peut également être fait avec dplyr::left_join.)

Vos données:

increment <- read.table(text = "grp increment 
1 10 
2 25 
3 35 
4 50", header = TRUE) 

input <- read.table(text = "grp pos 
1 10 
1 14 
1 25 
2 3 
2 20 
3 2 
3 10", header = TRUE) 

Je mettrai à jour ce afin que les ajustements sont basés sur la colonne $increment. Vous pouvez remplacer$increment au lieu de en ajoutant$add, à votre disposition.

increment$add <- c(0, cumsum(increment$increment[-nrow(increment)])) 
increment 
# grp increment add 
# 1 1  10 0 
# 2 2  25 10 
# 3 3  35 35 
# 4 4  50 70 

x <- merge(input, increment[,c("grp", "add")], by = "grp") 
x 
# grp pos add 
# 1 1 10 0 
# 2 1 14 0 
# 3 1 25 0 
# 4 2 3 10 
# 5 2 20 10 
# 6 3 2 35 
# 7 3 10 35 

De là, c'est simplement une question d'ajustement.Ces deux éléments sont

x$pos_adj <- x$pos + x$add 
x$add <- NULL # remove the now-unnecessary column 
x 
# grp pos pos_adj 
# 1 1 10  10 
# 2 1 14  14 
# 3 1 25  25 
# 4 2 3  13 
# 5 2 20  30 
# 6 3 2  37 
# 7 3 10  45 

(je suis un peu bavard avec des colonnes et autres. Cela peut certainement être un peu plus compact, mais je voulais qu'il y ait place pour comprendre ce qui est fait et où.)

1

Voici comment faire en utilisant case_when de dplyr. Je n'ai pas utilisé votre increment.tab car les chiffres ne correspondent pas à votre exemple.

version dplyr 0.5.0

library(dplyr) 
input.tab%>% 
    mutate(pos_adj=case_when(.$grp==1 ~ .$pos+0, 
          .$grp==2 ~ .$pos+10, 
          .$grp==3 ~ .$pos+35, 
          .$grp==4 ~ .$pos+70)) 

    grp pos pos_adj 
1 1 10  10 
2 1 14  14 
3 1 25  25 
4 2 3  13 
5 2 20  30 
6 3 2  37 
7 3 10  45 

version dplyr 0.7.0

library(dplyr) 
input.tab%>% 
    mutate(pos_adj=case_when(grp==1 ~ pos+0, 
          grp==2 ~ pos+10, 
          grp==3 ~ pos+35, 
          grp==4 ~ pos+70)) 

données

input.tab <- read.table(text="grp pos 
1 10 
1 14 
1 25 
2 3 
2 20 
3 2 
3 10",header=TRUE,stringsAsFactors=FALSE) 
+0

Merci P Lapointe! Fonctionne très bien avec dplyr – user31888

1

d'abord créer un vecteur pour rechercher des valeurs de

vec = setNames(object = c(0, 10, 35, 70), nm = c(1, 2, 3, 4)) 
vec 
# 1 2 3 4 
# 0 10 35 70 

Ensuite, la recherche des valeurs appropriées de vec et ajouter à pos. L'utilisation de P Lapointe données

increment.tab$pos + vec[match(increment.tab$grp, names(vec))] 
# 1 1 1 2 2 3 3 
#10 14 25 13 30 37 45 
+1

Je pense que je préfère cette utilisation de 'match' comme une recherche, car elle vous permet de définir' nomatch = -Inf' (par exemple) pour quand la recherche 'grp' échoue. Ma réponse 'merge' produirait' NA' avec un travail supplémentaire requis pour réparer/changer. – r2evans

+1

Merci d.b! Fonctionne bien – user31888

1

Vous êtes proche, mais comme @ r2evans expliqué votre appel de fonction est problématique, et apply utilise des matrices. Leur solution est bonne, mais si vous voulez toujours utiliser votre fonction, il vous suffit de modifier légèrement son application et d'utiliser adply depuis la bibliothèque plyr. En utilisant votre exemple ref et input trames de données comme ci-dessus, et sans changer votre fonction elle-même du tout:

new_df <- adply(input, 1, function(df){ 
    c(pos_adj = my_fun(df$grp, df$pos)) 
}) 

> new_df 
    grp pos pos_adj 
1 1 10  10 
2 1 14  14 
3 1 25  25 
4 2 3  13 
5 2 20  30 
6 3 2  37 
7 3 10  45 

Si vous êtes désireux de rester avec apply, vous pouvez aller dans cette voie (encore une fois, sans changer votre fonction):

input$pos_adj <- apply(input, 1, function(df){ 
    my_fun(df["grp"], df["pos"]) 
}) 

> input 
    grp pos pos_adj 
1 1 10  10 
2 1 14  14 
3 1 25  25 
4 2 3  13 
5 2 20  30 
6 3 2  37 
7 3 10  45 
+0

Merci Luc C pour l'explication et pour garder ma fonction. Je comprends mon erreur maintenant. – user31888