Reemplazar un valor en un marco de datos basado en una declaración condicional (`if`)


122

En el marco de datos R codificado a continuación, me gustaría reemplazar todas las veces que B aparecen con b.

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

esto proporciona:

   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

Mi intento inicial fue usar declaraciones fory ifcomo esta:

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

pero como estoy seguro de que puede ver, esto reemplaza TODOS los valores de junk$nmwith b. Puedo ver por qué esto está haciendo esto, pero parece que no puedo lograr que reemplace solo aquellos casos de $ nm basura donde estaba el valor original B.

NOTA: Me las arreglé para resolver el problema con, gsubpero con el interés de aprender, RI aún me gustaría saber cómo hacer que mi enfoque original funcione (si es posible)


1
es posible que desee agregar stringsAsFactors = FALSE a la construcción original de data.frame.
jimmyb

@jimmyb ¿Por qué? Los factores son útiles y necesarios si se está modelando con la mayoría del código de modelado de R. La forma correcta de lidiar con esto es reconocer que los datos son un factor. Si no desea / necesita esta conversión, puede hacer lo que diga. Si quieres el factor, hay formas fáciles de hacer la manipulación que @Kenny quiere realizar.
Gavin Simpson

1
Sin embargo, los factores solían ser más populares debido al rendimiento, ahora que las cadenas son inmutables y el valor de los factores es menos obvio, ya que la mayor parte de la funcionalidad básica de R simplemente los convertirá (aunque con advertencias) directamente. Creo que los factores dan como resultado una cantidad significativa de errores que encuentro en el código R de las personas.
jimmyb

Respuestas:


217

Más fácil convertir nm a caracteres y luego realizar el cambio:

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

EDITAR: Y si de hecho necesita mantener nm como factores, agregue esto al final:

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

4
as.character () hace la vida mucho más fácil cuando se trabaja con factores. +1
Brandon Bertelsen

4
¿Qué pasa si tienes varias columnas?
geodex

43

otra forma útil de reemplazar valores

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

25

La respuesta corta es:

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

Eche un vistazo a los vectores de índice en la Introducción de R (si aún no lo ha leído).


EDITAR. Como se notó en los comentarios, esta solución funciona para vectores de caracteres, por lo que falla en sus datos.

Para el factor, la mejor manera es cambiar de nivel:

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

Adición breve: el uso de% en% solo ayuda realmente si tiene un conjunto en el lado derecho, como c("B","C"). Hacerlo junk$nm[junk$nm == "B"]es la mejor manera.
Thilo

1
Oh, otra adición importante: hacerlo así requiere sumar primero el nivel bdel factor al factor nm. La versión de diliop es de hecho la mejor si quieres trabajar con personajes, no con factores. (¡Piense siempre en el tipo que tienen sus variables primero!)
Thilo

eso no funciona con los datos creados por @Kenny porque los datos son factores. ¿Olvidaste un paso o tienes la configuración global para dejar de convertir caracteres en factores?
Gavin Simpson

4
@Thilo Una de las diferencias importantes entre %in%y ==es el NAmanejo: c(1,2,NA)==1da TRUE, FALSE, NApero c(1,2,NA) %in% 1da TRUE, FALSE, FALSE. Y sí, me olvidé de comprobar si esto funciona: /
Marek

20

Como los datos que muestra son factores, complica un poco las cosas. La respuesta de @ diliop aborda el problema convirtiendo nma una variable de carácter. Para volver a los factores originales se requiere un paso más.

Una alternativa es manipular los niveles del factor en su lugar.

> 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

Eso es bastante simple y a menudo olvido que hay una función de reemplazo para levels().

Editar: como señaló @Seth en los comentarios, esto se puede hacer en una sola línea, sin pérdida de claridad:

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

6
Agradable. No sabía sobre la función de reemplazo de levels(). ¿Qué tal el delineador junk <- within(junk, levels(nm)[levels(nm)=="B"] <- "b")?

Pero lo llamas dos veces :)
Marek

2
@Marek golpea la cabeza Solo demuestra que uno no debe responder a los comentarios sobre SO cuando ya es hora de acostarse. Intentémoslo de nuevo ...
Gavin Simpson

@Seth De hecho, agradable. ¿No estás seguro de por qué separé los pasos? Quizás para la exposición ...
Gavin Simpson

11

La forma más fácil de hacer esto en un comando es usar el whichcomando y tampoco es necesario cambiar los factores en caracteres haciendo esto:

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

5

Ha creado una variable de factor en, nmpor lo que debe evitar hacerlo o agregar un nivel adicional a los atributos de factor. También debe evitar el uso <-en los argumentos de data.frame ()

Opción 1:

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

Opcion 2:

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

@DWin gracias por sus comentarios sobre el problema y la necesidad de considerar el tipo de variable. Acepté la respuesta de @ diliop porque era la primera que funcionaba. Sé que hay muchos problemas con <- vs = pero (si se puede responder brevemente) ¿por qué debería usarse = con data.frame?
DQdlM

No es necesario añadir bcomo un nivel, sólo cambia el nivel que está Ba b.
Gavin Simpson

@KennyPeanuts: el nombre de la columna es un problema, mira a <- data.frame(x<-1:10). Su nombre de columna no es xsino más bien desordenado x....1.10. Es mejor usar data.frame (x = 1: 10). Entonces sabes cuál es el nombre de tu columna.
IRTFM

@Gavin: Es más fácil agregar que reemplazar, e incluso más fácil no convertirlo en un factor.
IRTFM

@Dwin Más fácil? No estoy de acuerdo - vea mi Respuesta para algo simple. Agregar niveles puede sorprenderlo, por ejemplo, en el modelado con el predict()que se quejará si los niveles de los factores en los nuevos datos no coinciden con los utilizados para ajustar el modelo. Más limpio a largo plazo para obtener los datos formateados como desee, correctamente, que depender de atajos. Estoy de acuerdo en que podría ser más fácil no convertirlo en un factor, pero si ya lo es, o necesita serlo para algún ejercicio de modelado ...
Gavin Simpson

1

Si está trabajando con variables de carácter (tenga en cuenta que stringsAsFactorsaquí es falso) puede usar reemplazar:

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)])
}

Llame a esta función usando la línea de abajo.

d=stata.replace(d,"under20",1,"age<20")
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.