2017-08-22 3 views
6

Disons que j'ai un vecteur avec des noms répétés:Modifier plusieurs éléments R vecteur avec des noms répétés

x <- c(a=1, b=2, a=3, c=4, c=5, b=1, d=1) 

Je veux rechercher et modifier des éléments nommés. Si je définis

ElementsToChange <- c("a","b","c") 

ChangeTo <- c(9,8,7) 

Je souhaite modifier tous les éléments nommés « a » à 9 tous ceux qui sont nommés « b » à 8 etc. si je fais:

x[ElementsToChange] <- ChangeTo 

Cela ne changera que la premier (plutôt que tous) les éléments.

Comment puis-je tout changer, d'une manière simple et élégante?

+1

près de celui-ci: https://stackoverflow.com/q/33244299/3871924 – agenis

+1

Une autre est 'prendre v1 <- setNames (setNames (REMPLACERPAR ElementsToChange) [noms (x)], les noms (x)); v1 [is.na (v1)] <- x [est.na (v1)] ' – akrun

+3

@Frank je voulais dire" hé, cette question peut aider ";-) – agenis

Répondre

10

Vous pouvez le faire en utilisant une seule commande split *:

split(x, names(x))[ElementsToChange] <- ChangeTo 
#x 
#a b a c c b d 
#9 8 9 7 7 8 1 

Cette première divise x vecteur par ses noms, puis sous-ensembles tous les éléments qui font partie du vecteur ElementsToChange et remplace ces valeurs par les valeurs ChangeTo.

* techniquement, vous utilisez split<- ici, c'est-à-dire l'affectation sur les données éclatées. Le vecteur original conserve sa structure après.

+2

c'est une solution géniale. Jamais pensé que vous pourriez attribuer après une scission! – agenis

3

éléments nommés fonctionnent beaucoup mieux lorsque les noms sont uniques, mais vous pouvez faire quelque chose comme

Reduce(function(x, z) { 
    x[names(x)==z$k] <- z$v 
    x 
}, split(data.frame(k=ElementsToChange, v=ChangeTo), seq_along(ChangeTo)), init=x) 
# a b a c c b d 
# 9 8 9 7 7 8 1 

Mais vraiment, il serait plus facile avec clé/valeurs appropriées dans un data.frame.

library(tidyverse) 
dd <- data_frame(k=names(x), v=x) 
tt <- data_frame(k=ElementsToChange, newv=ChangeTo) 
dd %>% left_join(tt) %>% mutate(v=coalesce(newv, v), newv=NULL) 
+0

belle astuce avec' newv = NULL' dans 'mutate' – Sotos

4

Je suppose que vous devez conserver aucune correspondance, ce qui le rend un peu moins élégant:

ifelse(names(x)%in%ElementsToChange,ChangeTo[match(names(x),ElementsToChange)],x) 
[1] 9 8 9 7 7 8 1 

Si vous voulez que les noms que vous devez sauvegarder renommer le vecteur.

+2

Ou similairement sans ifelse,' m <- match (noms (x), ElementsToChange); replace (x,! is.na (m), ChangeTo [na.omit (m)]) ' – Frank

+0

@Frank, pourquoi pas:' x [! is.na (m)] <- ChangeTo [na.omettre (m)] 'au lieu du remplacer? –

+0

@MikeH Oui. En regardant le code de «remplacer», ils sont essentiellement les mêmes. 'replace', comme' ifelse', crée un nouvel objet, tandis que votre chemin modifie 'x' (ce qui est plus proche de ce que veut l'OP). – Frank

3

Une autre option qui remplace directement avec <-, crédit à @Frank:

m <- match(names(x), ElementsToChange) 
x[!is.na(m)] <- ChangeTo[na.omit(m)] 

x 
a b a c c b d 
9 8 9 7 7 8 1 

Comme il le remplace directement, il garde les noms aussi bien. Vous sous-ensemble x uniquement les noms ElementsToChange et remplacez leurs valeurs avec les éléments appariés dans ChangeTo

2
ids = match(names(x), ElementsToChange) 
replace(x, which(!is.na(ids)), ChangeTo[ids[!is.na(ids)]]) 
#a b a c c b d 
#9 8 9 7 7 8 1 
0

Vous pouvez utiliser cette fonction et fournir vos vecteurs elementsToChange et changeTo pour obtenir la liste modifiée.

library(dplyr) 

change <- function(x, elementsToChange = c("a"), changeTo=c(5)){ 

    if(length(elementsToChange)!=length(changeTo)) {stop("length of elementsToChange is not equal to changeTo")} 
    temp<- tibble(name = names(x), value = x) 
    temp.change<- tibble(name = elementsToChange, value = changeTo) 
    temp2<-temp%>%left_join(temp.change, by = "name") 
    temp2<-within(temp2, { 
    value.y[is.na(value.y)]<- value.x[is.na(value.y)] 
    }) 
    ret<-temp2$value.y 
    names(ret)<-temp2$name 
    return(ret) 
}