Remplacer une valeur dans un bloc de données basé sur une instruction conditionnelle (`if`)


122

Dans la trame de données R codée ci-dessous, je voudrais remplacer toutes les heures qui B apparaissent par b.

junk <- data.frame(x <- rep(LETTERS[1:4], 3), y <- letters[1:12])
colnames(junk) <- c("nm", "val")

cela fournit:

   nm val
1   A   a
2   B   b
3   C   c
4   D   d
5   A   e
6   B   f
7   C   g
8   D   h
9   A   i
10  B   j
11  C   k
12  D   l

Ma première tentative était d'utiliser des instructions foret ifcomme ceci:

for(i in junk$nm) if(i %in% "B") junk$nm <- "b"

mais comme je suis sûr que vous pouvez le voir, cela remplace TOUTES les valeurs de junk$nmavec b. Je peux voir pourquoi cela fait cela, mais je n'arrive pas à le faire remplacer uniquement les cas de junk $ nm où la valeur d'origine était B.

REMARQUE: j'ai réussi à résoudre le problème avec, gsubmais dans l'intérêt de l'apprentissage, je voudrais toujours savoir comment faire fonctionner mon approche originale (si cela est possible)


1
vous voudrez peut-être ajouter stringsAsFactors = FALSE à la construction originale de data.frame.
jimmyb

@jimmyb Pourquoi? Les facteurs sont utiles et nécessaires si l'on modélise avec la plupart du code de modélisation de R. La bonne façon de traiter cela est de reconnaître que les données sont un facteur. Si vous ne voulez / n'avez pas besoin de cette conversion, vous pouvez faire ce que vous dites. Si vous voulez le facteur, il existe des moyens simples de faire la manipulation que @Kenny veut effectuer.
Gavin Simpson

1
Ainsi, les facteurs étaient plus populaires en raison des performances, cependant, maintenant que les chaînes sont immuables et hachées, la valeur des facteurs est moins évidente, car la plupart des fonctionnalités de base R les convertissent simplement (bien qu'avec des avertissements) directement. Je pense que les facteurs entraînent un nombre important de bogues que je trouve dans le code R des gens.
jimmyb

Réponses:


217

Plus facile à convertir nm en caractères, puis à effectuer le changement:

junk$nm <- as.character(junk$nm)
junk$nm[junk$nm == "B"] <- "b"

EDIT: Et si en effet vous avez besoin de maintenir nm comme facteurs, ajoutez ceci à la fin:

junk$nm <- as.factor(junk$nm)

4
as.character () rend la vie tellement plus facile lorsque vous travaillez avec des facteurs. +1
Brandon Bertelsen

4
et si vous avez plusieurs colonnes?
geodex

43

un autre moyen utile de remplacer les valeurs

library(plyr)
junk$nm <- revalue(junk$nm, c("B"="b"))

25

La réponse courte est:

junk$nm[junk$nm %in% "B"] <- "b"

Jetez un œil aux vecteurs d'index dans R Introduction (si vous ne l'avez pas encore lu).


ÉDITER. Comme remarqué dans les commentaires, cette solution fonctionne pour les vecteurs de caractères, donc échouez sur vos données.

Pour le facteur le meilleur moyen est de changer de niveau:

levels(junk$nm)[levels(junk$nm)=="B"] <- "b"

Ajout court: l'utilisation de% en% n'aide vraiment que si vous avez un ensemble sur le côté droit, comme c("B","C"). Faire junk$nm[junk$nm == "B"]est la meilleure façon.
Thilo

1
Oh, un autre ajout important: faire comme ça nécessite d'abord d'ajouter le niveau bde facteur au facteur nm. La version de diliop est en fait la meilleure si vous voulez travailler avec des personnages, pas des facteurs. (Pensez toujours au type de vos variables en premier!)
Thilo

cela ne fonctionne pas sur les données créées par @Kenny car les données sont des facteurs. Avez-vous oublié une étape ou avez-vous le paramètre global pour arrêter la conversion des caractères en facteurs?
Gavin Simpson

4
@Thilo Une des différences importantes entre %in%et ==est la NAmanipulation: c(1,2,NA)==1donne TRUE, FALSE, NAmais c(1,2,NA) %in% 1donne TRUE, FALSE, FALSE. Et oui j'ai oublié de vérifier si ce travail: /
Marek

20

Comme les données que vous montrez sont des facteurs, cela complique un peu les choses. La réponse de @ diliop aborde le problème en se convertissant nmen une variable caractère. Pour revenir aux facteurs d'origine, une étape supplémentaire est nécessaire.

Une alternative consiste à manipuler les niveaux du facteur en place.

> lev <- with(junk, levels(nm))
> lev[lev == "B"] <- "b"
> junk2 <- within(junk, levels(nm) <- lev)
> junk2
   nm val
1   A   a
2   b   b
3   C   c
4   D   d
5   A   e
6   b   f
7   C   g
8   D   h
9   A   i
10  b   j
11  C   k
12  D   l

C'est assez simple et j'oublie souvent qu'il existe une fonction de remplacement pour levels().

Edit: Comme indiqué par @Seth dans les commentaires, cela peut être fait en une seule ligne, sans perte de clarté:

within(junk, levels(nm)[levels(nm) == "B"] <- "b")

6
Agréable. Je ne connaissais pas la fonction de remplacement pour levels(). Que diriez-vous de la doublure unique junk <- within(junk, levels(nm)[levels(nm)=="B"] <- "b")?

Mais vous l'appelez deux fois :)
Marek

2
@Marek claque la tête Cela montre simplement qu'il ne faut pas répondre aux commentaires sur SO quand il est bien passé l'heure du coucher. Essayons à nouveau ...
Gavin Simpson

@Seth Indeed - sympa. Vous ne savez pas pourquoi j'ai séparé les étapes? Peut-être pour une exposition ...
Gavin Simpson

11

Le moyen le plus simple de le faire en une seule commande est d'utiliser la whichcommande et de ne pas avoir besoin de changer les facteurs en caractère en faisant ceci:

junk$nm[which(junk$nm=="B")]<-"b"

5

Vous avez créé une variable de facteur dans nm, vous devez donc soit éviter de le faire, soit ajouter un niveau supplémentaire aux attributs de facteur. Vous devez également éviter d'utiliser <-dans les arguments de data.frame ()

Option 1:

junk <- data.frame(x = rep(LETTERS[1:4], 3), y =letters[1:12], stringsAsFactors=FALSE)
junk$nm[junk$nm == "B"] <- "b"

Option 2:

levels(junk$nm) <- c(levels(junk$nm), "b")
junk$nm[junk$nm == "B"] <- "b"
junk

@DWin merci pour votre contribution sur le problème et la nécessité de considérer le type de variable. J'ai accepté la réponse de @ diliop car c'était la première qui fonctionnait. Je sais qu'il y a beaucoup de problèmes avec <- vs = mais (si on peut y répondre brièvement) pourquoi devrait = être utilisé avec data.frame?
DQdlM

Vous n'avez pas besoin d'ajouter bun niveau, il suffit de changer le niveau qui est Bà b.
Gavin Simpson

@KennyPeanuts: le nom de la colonne est un problème, regardez a <- data.frame(x<-1:10). Son nom de colonne n'est pas xmais plutôt désordonné x....1.10. Mieux vaut utiliser data.frame (x = 1: 10). Ensuite, vous savez quel est le nom de votre colonne.
IRTFM

@Gavin: plus facile à ajouter qu'à remplacer, et encore plus facile à ne pas en faire un facteur.
IRTFM

@Dwin plus facile? Je ne suis pas d'accord - voir ma réponse pour quelque chose de simple. L'ajout de niveaux peut vous surprendre, par exemple dans la modélisation, predict()qui se plaindra si les niveaux de facteurs dans les nouvelles données ne correspondent pas à ceux utilisés pour s'adapter au modèle. Plus propre à long terme pour que les données soient formatées comme vous le souhaitez, correctement, que de compter sur des raccourcis. Je suis d'accord qu'il serait peut-être plus facile de ne pas en faire un facteur, mais si c'est déjà un facteur, ou s'il doit en être un pour un exercice de modélisation ...
Gavin Simpson

1

Si vous travaillez avec des variables de caractères (notez que stringsAsFactorsc'est faux ici), vous pouvez utiliser replace:

junk <- data.frame(x <- rep(LETTERS[1:4], 3), y <- letters[1:12], stringsAsFactors = FALSE)
colnames(junk) <- c("nm", "val")

junk$nm <- replace(junk$nm, junk$nm == "B", "b")
junk
#    nm val
# 1   A   a
# 2   b   b
# 3   C   c
# 4   D   d
# ...

0
stata.replace<-function(data,replacevar,replacevalue,ifs) {
  ifs=parse(text=ifs)
  yy=as.numeric(eval(ifs,data,parent.frame()))
  x=sum(yy)
  data=cbind(data,yy)
  data[yy==1,replacevar]=replacevalue
  message=noquote(paste0(x, " replacement are made"))
  print(message)
  return(data[,1:(ncol(data)-1)])
}

Appelez cette fonction en utilisant la ligne ci-dessous.

d=stata.replace(d,"under20",1,"age<20")
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.