Come eliminare colonne per nome in un frame di dati

Ho un ampio set di dati e vorrei leggere colonne specifiche o eliminare tutti gli altri.

data <- read.dta("file.dta") 

Seleziono le colonne a cui non sono interessato:

 var.out <- names(data)[!names(data) %in% c("iden", "name", "x_serv", "m_serv")] 

e di quanto mi piacerebbe fare qualcosa come:

 for(i in 1:length(var.out)) { paste("data$", var.out[i], sep="") <- NULL } 

per eliminare tutte le colonne indesiderate. È questa la soluzione ottimale?

È necessario utilizzare l’indicizzazione o la funzione subset . Per esempio :

 R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8) R> df xyzu 1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 5 6 7 8 

Quindi è ansible utilizzare la funzione e l’operatore - nell’indicizzazione della colonna:

 R> df[ , -which(names(df) %in% c("z","u"))] xy 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 

O, molto più semplice, usa l’argomento select della funzione subset : puoi quindi usare l’operatore - direttamente su un vettore di nomi di colonne, e puoi persino omettere le virgolette attorno ai nomi!

 R> subset(df, select=-c(z,u)) xy 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 

Nota che puoi anche selezionare le colonne che vuoi invece di lasciarle cadere:

 R> df[ , c("x","y")] xy 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 R> subset(df, select=c(x,y)) xy 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 

Non usare -which() per questo, è estremamente pericoloso. Prendere in considerazione:

 dat <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8) dat[ , -which(names(dat) %in% c("z","u"))] ## works as expected dat[ , -which(names(dat) %in% c("foo","bar"))] ## deletes all columns! Probably not what you wanted... 

Usa invece sottoinsieme o il ! funzione:

 dat[ , !names(dat) %in% c("z","u")] ## works as expected dat[ , !names(dat) %in% c("foo","bar")] ## returns the un-altered data.frame. Probably what you want 

L'ho imparato da un'esperienza dolorosa. Non esagerare con which() !

Innanzitutto , è ansible utilizzare l’indicizzazione diretta (con i vettori booleani) anziché accedere nuovamente ai nomi di colonna se si lavora con lo stesso frame di dati; sarà più sicuro come sottolineato da Ista, e più veloce da scrivere e da eseguire. Quindi quello di cui avrai bisogno è solo:

 var.out.bool <- !names(data) %in% c("iden", "name", "x_serv", "m_serv") 

e quindi, semplicemente riassegnare i dati:

 data <- data[,var.out.bool] # or... data <- data[,var.out.bool, drop = FALSE] # You will need this option to avoid the conversion to an atomic vector if there is only one column left 

Secondo , più veloce da scrivere, puoi assegnare direttamente NULL alle colonne che vuoi rimuovere:

 data[c("iden", "name", "x_serv", "m_serv")] <- list(NULL) # You need list() to respect the target structure. 

Infine , puoi usare subset (), ma non può essere realmente usato nel codice (anche il file di aiuto avvisa a riguardo). Nello specifico, un problema per me è che se vuoi usare direttamente la funzione drop di susbset () devi scrivere senza virgolette l'espressione corrispondente ai nomi delle colonne:

 subset( data, select = -c("iden", "name", "x_serv", "m_serv") ) # WILL NOT WORK subset( data, select = -c(iden, name, x_serv, m_serv) ) # WILL 

Come bonus , ecco un piccolo benchmark delle diverse opzioni, che mostra chiaramente che il sottoinsieme è il più lento e che il primo metodo di riassegnazione è il più veloce:

  re_assign(dtest, drop_vec) 46.719 52.5655 54.6460 59.0400 1347.331 null_assign(dtest, drop_vec) 74.593 83.0585 86.2025 94.0035 1476.150 subset(dtest, select = !names(dtest) %in% drop_vec) 106.280 115.4810 120.3435 131.4665 65133.780 subset(dtest, select = names(dtest)[!names(dtest) %in% drop_vec]) 108.611 119.4830 124.0865 135.4270 1599.577 subset(dtest, select = -c(x, y)) 102.026 111.2680 115.7035 126.2320 1484.174 

Grafico di Microbench

Il codice è di seguito:

 dtest <- data.frame(x=1:5, y=2:6, z = 3:7) drop_vec <- c("x", "y") null_assign <- function(df, names) { df[names] <- list(NULL) df } re_assign <- function(df, drop) { df <- df [, ! names(df) %in% drop, drop = FALSE] df } res <- microbenchmark( re_assign(dtest,drop_vec), null_assign(dtest,drop_vec), subset(dtest, select = ! names(dtest) %in% drop_vec), subset(dtest, select = names(dtest)[! names(dtest) %in% drop_vec]), subset(dtest, select = -c(x, y) ), times=5000) plt <- ggplot2::qplot(y=time, data=res[res$time < 1000000,], colour=expr) plt <- plt + ggplot2::scale_y_log10() + ggplot2::labs(colour = "expression") + ggplot2::scale_color_discrete(labels = c("re_assign", "null_assign", "subset_bool", "subset_names", "subset_drop")) + ggplot2::theme_bw(base_size=16) print(plt) 

Puoi anche provare il pacchetto dplyr :

 R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8) R> df xyzu 1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 5 6 7 8 R> library(dplyr) R> dplyr::select(df2, -c(x, y)) # remove columns x and y zu 1 3 4 2 4 5 3 5 6 4 6 7 5 7 8 

Ecco una soluzione rapida per questo. Di ‘, hai una cornice dati X con tre colonne A, B e C:

 > X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6)) > X ABC 1 1 3 5 2 2 4 6 

Se voglio rimuovere una colonna, ad esempio B, usa solo grep su colnames per ottenere l’indice della colonna, che puoi usare per omettere la colonna.

 > X<-X[,-grep("B",colnames(X))] 

La tua nuova cornice dati X sarà simile alla seguente (questa volta senza la colonna B):

 > X AC 1 1 5 2 2 6 

La bellezza di grep è che puoi specificare più colonne che corrispondono all'espressione regolare. Se avessi X con cinque colonne (A, B, C, D, E):

 > X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6),D=c(7,8),E=c(9,10)) > X ABCDE 1 1 3 5 7 9 2 2 4 6 8 10 

Estrarre le colonne B e D:

 > X<-X[,-grep("B|D",colnames(X))] > X ACE 1 1 5 9 2 2 6 10 

EDIT: Considerando il suggerimento grepl di Matthew Lundberg nei commenti qui sotto:

 > X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6),D=c(7,8),E=c(9,10)) > X ABCDE 1 1 3 5 7 9 2 2 4 6 8 10 > X<-X[,!grepl("B|D",colnames(X))] > X ACE 1 1 5 9 2 2 6 10 

Se provo a eliminare una colonna inesistente, non dovrebbe accadere nulla:

 > X<-X[,!grepl("G",colnames(X))] > X ACE 1 1 5 9 2 2 6 10 

Ho provato a eliminare una colonna durante l’utilizzo del pacchetto data.table e ho ottenuto un risultato inaspettato. Penso che quanto segue possa valere la pena di postare. Solo un piccolo avvertimento.

[Modificato da Matthew …]

 DF = read.table(text = " fruit state grade y1980 y1990 y2000 apples Ohio aa 500 100 55 apples Ohio bb 0 0 44 apples Ohio cc 700 0 33 apples Ohio dd 300 50 66 ", sep = "", header = TRUE, stringsAsFactors = FALSE) DF[ , !names(DF) %in% c("grade")] # all columns other than 'grade' fruit state y1980 y1990 y2000 1 apples Ohio 500 100 55 2 apples Ohio 0 0 44 3 apples Ohio 700 0 33 4 apples Ohio 300 50 66 library('data.table') DT = as.data.table(DF) DT[ , !names(dat4) %in% c("grade")] # not expected !! not the same as DF !! [1] TRUE TRUE FALSE TRUE TRUE TRUE DT[ , !names(DT) %in% c("grade"), with=FALSE] # that's better fruit state y1980 y1990 y2000 1: apples Ohio 500 100 55 2: apples Ohio 0 0 44 3: apples Ohio 700 0 33 4: apples Ohio 300 50 66 

Fondamentalmente, la syntax per data.table NON è esattamente la stessa di data.frame . Ci sono infatti molte differenze, vedi FAQ 1.1 e FAQ 2.17. Sei stato avvertito!

Ecco un’altra soluzione che potrebbe essere utile per gli altri. Il codice sottostante seleziona un numero limitato di righe e colonne da un set di dati di grandi dimensioni. Le colonne sono selezionate come in una delle risposte di juba, tranne per il fatto che utilizzo una funzione Incolla per selezionare un set di colonne con nomi numerati in sequenza:

 df = read.table(text = " state county city region mmatrix X1 X2 X3 A1 A2 A3 B1 B2 B3 C1 C2 C3 1 1 1 1 111010 1 0 0 2 20 200 4 8 12 NA NA NA 1 2 1 1 111010 1 0 0 4 NA 400 5 9 NA NA NA NA 1 1 2 1 111010 1 0 0 6 60 NA NA 10 14 NA NA NA 1 2 2 1 111010 1 0 0 NA 80 800 7 11 15 NA NA NA 1 1 3 2 111010 0 1 0 1 2 1 2 2 2 10 20 30 1 2 3 2 111010 0 1 0 2 NA 1 2 2 NA 40 50 NA 1 1 4 2 111010 0 1 0 1 1 NA NA 2 2 70 80 90 1 2 4 2 111010 0 1 0 NA 2 1 2 2 10 100 110 120 1 1 1 3 010010 0 0 1 10 20 10 200 200 200 1 2 3 1 2 1 3 001000 0 0 1 20 NA 10 200 200 200 4 5 9 1 1 2 3 101000 0 0 1 10 10 NA 200 200 200 7 8 NA 1 2 2 3 011010 0 0 1 NA 20 10 200 200 200 10 11 12 ", sep = "", header = TRUE, stringsAsFactors = FALSE) df df2 <- df[df$region == 2, names(df) %in% c(paste("C", seq_along(1:3), sep=''))] df2 # C1 C2 C3 # 5 10 20 30 # 6 40 50 NA # 7 70 80 90 # 8 100 110 120 
 df2 <- df[!names(df) %in% c("c1", "c2")] 

Ho cambiato il codice in:

 # read data dat<-read.dta("file.dta") # vars to delete var.in<-c("iden", "name", "x_serv", "m_serv") # what I'm keeping var.out<-setdiff(names(dat),var.in) # keep only the ones I want dat <- dat[var.out] 

Ad ogni modo, la risposta di juba è la migliore soluzione al mio problema!

Non posso rispondere alla tua domanda nei commenti a causa del basso punteggio di reputazione.

Il prossimo codice ti darà un errore perché la funzione incolla restituisce una stringa di caratteri

 for(i in 1:length(var.out)) { paste("data$", var.out[i], sep="") <- NULL } 

Ecco una ansible soluzione:

 for(i in 1:length(var.out)) { text_to_source <- paste0 ("data$", var.out[i], "<- NULL") # Write a line of your # code like a character string eval (parse (text=text_to_source)) # Source a text that contains a code } 

o fai semplicemente:

 for(i in 1:length(var.out)) { data[var.out[i]] <- NULL }