Comment supprimer des colonnes par nom dans un bloc de données

J’ai un grand dataset et je voudrais lire des colonnes spécifiques ou supprimer toutes les autres.

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

Je sélectionne les colonnes qui ne m’intéressent pas:

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

et que j’aimerais faire quelque chose comme:

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

laisser tomber toutes les colonnes indésirables. Est-ce la solution optimale?

Vous devez utiliser l’indexation ou la fonction de subsetsubset . Par exemple :

 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 

Ensuite, vous pouvez utiliser la fonction which et l’opérateur - dans l’indexation de la colonne:

 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 

Ou, bien plus simple, utilisez l’argument select de la fonction subsetsubset : vous pouvez alors utiliser l’opérateur - directement sur un vecteur de noms de colonne, et vous pouvez même omettre les guillemets autour des noms!

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

Notez que vous pouvez également sélectionner les colonnes de votre choix au lieu de supprimer les autres:

 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 

N’utilisez pas -which() pour cela, c’est extrêmement dangereux. Considérer:

 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... 

Au lieu de cela, utilisez un sous-ensemble ou le ! fonction:

 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 

Je l'ai appris de l'expérience douloureuse. Ne pas abuser de which() !

Tout d’abord , vous pouvez utiliser l’indexation directe (avec des vecteurs booléens) au lieu de ré-accéder aux noms de colonne si vous travaillez avec le même bloc de données. Il sera plus sûr, comme le souligne Ista, et plus rapide à écrire et à exécuter. Donc, vous aurez seulement besoin de:

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

puis, simplement réaffecter des données:

 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 

Deuxièmement , plus rapide à écrire, vous pouvez directement affecter NULL aux colonnes que vous souhaitez supprimer:

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

Enfin , vous pouvez utiliser subset (), mais il ne peut pas vraiment être utilisé dans le code (même le fichier d'aide en avertit). Plus précisément, un problème pour moi est que si vous voulez utiliser directement la fonctionnalité drop de susbset (), vous devez écrire sans les guillemets l'expression correspondant aux noms des colonnes:

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

En prime , voici un petit repère des différentes options, qui montre clairement que le sous-ensemble est le plus lent, et que la première méthode de réaffectation est la plus rapide:

  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 

Graphique microbench

Le code est ci-dessous:

 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) 

Vous pouvez également essayer le package 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 

Voici une solution rapide pour cela. Disons que vous avez un bloc de données X avec trois colonnes A, B et 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 

Si je veux supprimer une colonne, disons B, utilisez simplement grep sur colnames pour obtenir l’index de la colonne, que vous pouvez ensuite utiliser pour omettre la colonne.

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

Votre nouveau bloc de données X ressemblerait à ceci (cette fois sans la colonne B):

 > X AC 1 1 5 2 2 6 

La beauté de grep est que vous pouvez spécifier plusieurs colonnes correspondant à l'expression régulière. Si j'avais X avec cinq colonnes (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 

Sortez les colonnes B et D:

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

EDIT: Considérant la suggestion grepl de Matthew Lundberg dans les commentaires ci-dessous:

 > 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 

Si j'essaie d'abandonner une colonne qui n'existe pas, rien ne devrait arriver:

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

J’ai essayé de supprimer une colonne en utilisant le package data.table et j’ai obtenu un résultat inattendu. Je pense en quelque sorte que ce qui suit pourrait être utile. Juste un petit avertissement.

[Edité par 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, ssortingngsAsFactors = 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 

Fondamentalement, la syntaxe de data.table n’est PAS exactement la même que data.frame . Il y a en fait beaucoup de différences, voir FAQ 1.1 et FAQ 2.17. Tu étais prévenu!

Voici une autre solution qui peut être utile aux autres. Le code ci-dessous sélectionne un petit nombre de lignes et de colonnes dans un dataset volumineux. Les colonnes sont sélectionnées comme dans l’une des réponses de juba, sauf que j’utilise une fonction de collage pour sélectionner un ensemble de colonnes dont les noms sont numérotés de manière séquentielle:

 df = read.table(text = " state county city region mmasortingx 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, ssortingngsAsFactors = 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")] 

J’ai changé le code pour:

 # 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] 

Quoi qu’il en soit, la réponse de Juba est la meilleure solution à mon problème!

Je ne peux pas répondre à votre question dans les commentaires en raison du faible score de réputation.

Le code suivant vous donnera une erreur car la fonction coller renvoie une chaîne de caractères

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

Voici une solution possible:

 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 } 

ou juste faire:

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