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

304

J'ai un grand ensemble de données 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
}

pour supprimer toutes les colonnes indésirables. Est-ce la solution optimale?

leroux
la source
1
dormir sur le problème, je pensais que cela subset(data, select=c(...))aide dans mon cas pour la suppression de vars. la question était cependant principalement sur la paste("data$",var.out[i],sep="")partie pour accéder aux colonnes d'intérêt à l'intérieur de la boucle. comment puis-je coller ou composer en quelque sorte un nom de colonne? Merci à tous pour votre attention et votre aide
leroux
7
Duplicata possible des colonnes Drop dans la trame de données R
jangorecki

Réponses:

380

Vous devez utiliser l'indexation ou la subsetfonction. Par exemple :

R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
R> df
  x y z u
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 whichfonction et l' -opérateur dans l'indexation des colonnes:

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

Ou, beaucoup plus simple, utilisez l' selectargument de la subsetfonction: vous pouvez alors utiliser l' -opérateur directement sur un vecteur de noms de colonnes, et vous pouvez même omettre les guillemets autour des noms!

R> subset(df, select=-c(z,u))
  x y
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")]
  x y
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6

R> subset(df, select=c(x,y))
  x y
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6
juba
la source
2
l' selectargument de la subsetfonction a parfaitement fonctionné! Merci juba!
leroux
2
whichn'est pas nécessaire, voir la réponse d'Ista. Mais le sous-ensemble avec -est sympa! Je ne savais pas ça!
TMS
5
subsetsemble bon, mais la façon dont il supprime silencieusement les valeurs manquantes me semble assez dangereuse.
static_rtti
2
subsetest en effet très pratique, mais n'oubliez pas d'éviter de l'utiliser à moins que vous n'utilisiez R de manière interactive. Voir l'avertissement dans la documentation de la fonction et cette question SO pour en savoir plus.
Waldir Leoncio
4
"vous pouvez même omettre les guillemets autour des noms!", vous devez en fait omettre les guillemets, sinon vous obtiendrez un argument invalide à l'opérateur unaire. Si vous avez certains caractères (par exemple "-") dans vos noms, vous ne pouvez pas du tout utiliser cette méthode car la suppression des guillemets empêchera R d'analyser correctement votre code.
oh54
122

Ne pas utiliser -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...

Utilisez plutôt un sous-ensemble ou la !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

J'ai appris cela d'une expérience douloureuse. Ne pas abuser which()!

Ista
la source
31
setdiffest également utile:setdiff(names(dat), c("foo", "bar"))
hadley
La setdiffproposition de @hadley est très bonne pour les longues listes de noms.
JASC
48

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 l'a souligné Ista, et plus rapide à écrire et à exécuter. Donc, vous n'aurez besoin que de:

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

puis, réaffectez simplement les 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 vous en avertit). Plus précisément, un problème pour moi est que si vous souhaitez utiliser directement la fonction drop de susbset (), vous devez écrire sans guillemets l'expression correspondant aux noms de 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

En bonus , voici un petit benchmark 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)
Antoine Lizée
la source
2
J'aime votre deuxième alternative en utilisant NULL, mais pourquoi quand vous mettez plus de deux noms est-il nécessaire de l'attribuer list(NULL)? Je suis seulement curieux de savoir comment cela fonctionne, car j'ai essayé avec un seul nom et je n'ai pas besoinlist()
Darwin PC
3
@ DarwinPC Oui. Si vous accédez directement à un élément vectoriel (avec $ou [[), l'utilisation <- list(NULL)entraînera en fait des résultats erronés. Si vous accédez à un sous-ensemble de la trame de données avec une ou plusieurs colonnes, <- list(NULL)c'est la voie à suivre, même si elle n'est pas nécessaire pour une trame de données à une colonne (car elle df['myColumns']sera convertie en vecteur si nécessaire).
Antoine Lizée
27

Vous pouvez également essayer le dplyrpackage:

R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
R> df
  x y z u
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
  z u
1 3 4
2 4 5
3 5 6
4 6 7
5 7 8
Megatron
la source
4
L'utilisation dplyr::select(df2, -one_of(c('x','y')))fonctionnera toujours (avec un avertissement) même si certaines des colonnes nommées n'existent pas
divibisan
13

Voici une solution rapide pour cela. Disons que vous avez une trame 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
  A B C
1 1 3 5
2 2 4 6

Si je veux supprimer une colonne, dites B, utilisez simplement grep sur les noms de colonnes 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
  A C
1 1 5
2 2 6

La beauté de grep est que vous pouvez spécifier plusieurs colonnes qui correspondent à 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
  A B C D  E
1 1 3 5 7  9
2 2 4 6 8 10

Supprimer les colonnes B et D:

> X<-X[,-grep("B|D",colnames(X))]
> X
  A C  E
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
  A B C D  E
1 1 3 5 7  9
2 2 4 6 8 10
> X<-X[,!grepl("B|D",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10

Si j'essaie de supprimer une colonne qui n'existe pas, rien ne devrait se produire:

> X<-X[,!grepl("G",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10
Joben R. Ilagan
la source
3
X[,-grep("B",colnames(X))]ne renverra aucune colonne dans le cas où aucun nom de colonne ne contient B, plutôt que de renvoyer toutes les colonnes comme souhaité. Considérez avec X <- irispour un exemple. C'est le problème de l'utilisation d'indices négatifs avec des valeurs calculées. Considérez greplplutôt.
Matthew Lundberg
6

J'ai essayé de supprimer une colonne lors de l'utilisation du package data.tableet j'ai obtenu un résultat inattendu. Je pense en quelque sorte que ce qui suit mérite d'être publié. Juste une petite mise en garde.

[Édité 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, 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

Fondamentalement, la syntaxe de data.tablen'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!

Mark Miller
la source
1
Ou vous pouvez utiliser DT[,var.out := NULL]pour supprimer les colonnes que vous souhaitez faire.
mnel
Le sous - ensemble (x, select = ...) méthode fonctionne pour les deux data.frameet les data.tableclasses
momeara
3

J'ai changé le code en:

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

leroux
la source
Pourquoi voulez-vous faire cela en boucle? Les réponses La réponse de Juba vous montre comment le faire en une seule étape. Pourquoi compliquer les choses?
Ista
bien sûr, j'utilise l' selectargument de la subsetfonction dans mon code. je voulais juste voir comment je pouvais accéder à des colonnes arbitraires dans une boucle au cas où je voudrais faire autre chose que de simplement supprimer la colonne. l'ensemble de données d'origine a environ 1200 vars et je ne souhaite en utiliser que 4 sans savoir exactement où ils se trouvent.
leroux
2

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 grand ensemble de données. 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 avec des noms numérotés séquentiellement:

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
Mark Miller
la source
2
df2 <- df[!names(df) %in% c("c1", "c2")]
Marvin W
la source
-1

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 retourne 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 faites simplement:

for(i in 1:length(var.out)) {
  data[var.out[i]] <- NULL
}
Andriy T.
la source
-1
df = mtcars 
supprimer vs et am parce qu'ils sont catégoriques. Dans l'ensemble de données vs est dans la colonne numéro 8, am est dans la colonne numéro 9

dfnum = df[,-c(8,9)]

Abhilash Ponnam
la source