Sostituisci un valore in un data frame in base a un'istruzione condizionale ("if")


122

Nel frame di dati R codificato per sotto, vorrei sostituire tutte le volte che B appare con b.

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

questo fornisce:

   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

Il mio primo tentativo è stato quello di utilizzare le istruzioni fore in questo ifmodo:

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

ma come sono sicuro puoi vedere, questo sostituisce TUTTI i valori di junk$nmcon b. Posso capire perché questo sta facendo questo, ma non riesco a convincerlo a sostituire solo quei casi di spazzatura $ nm in cui era il valore originale B.

NOTA: sono riuscito a risolvere il problema gsubma nell'interesse di apprendere il RI vorrei ancora sapere come far funzionare il mio approccio originale (se possibile)


1
potresti voler aggiungere stringsAsFactors = FALSE alla costruzione data.frame originale.
jimmyb

@jimmyb Perché? I fattori sono utili e necessari se si sta modellando con la maggior parte del codice di modellazione di R. Il modo corretto di affrontare questo problema è riconoscere che i dati sono un fattore. Se non vuoi / hai bisogno di questa conversione, puoi fare come dici. Se vuoi il fattore, ci sono modi semplici per eseguire la manipolazione che @Kenny vuole eseguire.
Gavin Simpson

1
Quindi i fattori erano più popolari a causa delle prestazioni, tuttavia, ora che le stringhe sono immutabili e il valore hash dei fattori è meno ovvio, poiché la maggior parte della funzionalità R di base le convertirà direttamente (anche se con avvisi). Penso che i fattori si traducano in un numero significativo di bug che trovo nel codice R delle persone.
jimmyb

Risposte:


217

Più facile convertire nm in caratteri e quindi apportare la modifica:

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

EDIT: E se davvero hai bisogno di mantenere nm come fattori, aggiungi questo alla fine:

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

4
as.character () rende la vita molto più semplice quando si lavora con i fattori. +1
Brandon Bertelsen

4
cosa succede se hai più colonne?
geodex

43

un altro modo utile per sostituire i valori

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

25

La risposta breve è:

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

Dai un'occhiata ai vettori indice in R Introduzione (se non l'hai ancora letto).


MODIFICARE. Come notato nei commenti, questa soluzione funziona per i vettori di caratteri, quindi fallisci sui tuoi dati.

Per il fattore il modo migliore è cambiare il livello:

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

Aggiunta breve: l'utilizzo di% in% aiuta davvero solo se hai un set sul lato destro, come c("B","C"). Fare junk$nm[junk$nm == "B"]è il modo migliore.
Thilo

1
Oh, un'altra importante aggiunta: per fare questo è necessario prima aggiungere il livello bdel fattore al fattore nm. La versione di diliop è infatti quella migliore se vuoi lavorare con i personaggi, non con i fattori. (Pensa sempre al tipo che hanno le tue variabili per primo!)
Thilo

questo non funziona sui dati creati da @Kenny perché i dati sono fattori. Hai dimenticato un passaggio o hai l'impostazione globale per interrompere la conversione dei caratteri in fattori?
Gavin Simpson

4
@Thilo Una delle differenze importanti tra %in%e ==è il NAtrattamento: c(1,2,NA)==1TRUE, FALSE, NAma c(1,2,NA) %in% 1TRUE, FALSE, FALSE. E sì, ho dimenticato di controllare se questo funziona: /
Marek

20

Poiché i dati che mostri sono fattori, complica un po 'le cose. La risposta di @ diliop affronta il problema convertendola nmin una variabile carattere. Per tornare ai fattori originali è necessario un ulteriore passaggio.

Un'alternativa è manipolare i livelli del fattore in atto.

> 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

È abbastanza semplice e spesso dimentico che esiste una funzione sostitutiva per levels().

Modifica: come notato da @Seth nei commenti, questo può essere fatto in una riga, senza perdita di chiarezza:

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

6
Bello. Non sapevo della funzione di sostituzione di levels(). Che ne dici di una fodera junk <- within(junk, levels(nm)[levels(nm)=="B"] <- "b")?

Ma lo chiami due volte :)
Marek

2
@Marek schiaffeggia la testa Mostra solo che non si dovrebbe rispondere ai commenti su SO quando è ormai l'ora di andare a letto. Proviamo di nuovo ...
Gavin Simpson

@Seth Indeed - gentile. Non sei sicuro del motivo per cui ho separato i passaggi? Forse per l'esposizione ...
Gavin Simpson

11

Il modo più semplice per farlo in un comando è usare il whichcomando e inoltre non è necessario modificare i fattori in carattere facendo questo:

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

5

Hai creato una variabile fattore in nmquindi devi evitare di farlo o aggiungere un livello aggiuntivo agli attributi del fattore. Dovresti anche evitare di usare <-negli argomenti di data.frame ()

Opzione 1:

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

Opzione 2:

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

@DWin grazie per il tuo contributo sul problema e la necessità di considerare il tipo di variabile. Ho accettato la risposta di @ diliop perché era la prima funzionante. So che ci sono molti problemi su <- vs = ma (se si può rispondere brevemente) perché dovrebbe essere usato con data.frame?
DQdlM

Non è necessario aggiungere bcome livello, basta cambiare il livello che è Ba b.
Gavin Simpson

@ KennyPeanuts: il nome della colonna è un problema, guarda a <- data.frame(x<-1:10). Il nome della sua colonna non è xma piuttosto disordinato x....1.10. Meglio usare data.frame (x = 1: 10). Allora sai qual è il nome della tua colonna.
IRTFM

@ Gavin: più facile da aggiungere che da sostituire, e ancora più facile non renderlo un fattore.
IRTFM

@Dwin più facile? Non sono d'accordo - vedi la mia risposta per qualcosa di semplice. L'aggiunta di livelli può sorprenderti, ad esempio nella modellazione con predict()cui si lamenterà se i livelli dei fattori nei nuovi dati non corrispondono a quelli utilizzati per adattarsi al modello. Più pulito a lungo termine per ottenere i dati formattati come vuoi, correttamente, piuttosto che fare affidamento su scorciatoie. Sono d'accordo che potrebbe essere più facile non renderlo un fattore, ma se lo è già, o deve esserlo per qualche esercizio di modellazione ...
Gavin Simpson

1

Se stai lavorando con variabili carattere (nota che stringsAsFactorsqui è falso) puoi usare sostituisci:

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

Chiama questa funzione usando la riga sottostante.

d=stata.replace(d,"under20",1,"age<20")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.