Supprimer les colonnes du bloc de données par nom

874

J'ai un certain nombre de colonnes que je voudrais supprimer d'un bloc de données. Je sais que nous pouvons les supprimer individuellement en utilisant quelque chose comme:

df$x <- NULL

Mais j'espérais le faire avec moins de commandes.

De plus, je sais que je pourrais supprimer des colonnes en utilisant l'indexation entière comme ceci:

df <- df[ -c(1, 3:6, 12) ]

Mais je crains que la position relative de mes variables ne change.

Étant donné la puissance de R, j'ai pensé qu'il pourrait y avoir un meilleur moyen que de supprimer chaque colonne une par une.

Btibert3
la source
13
Quelqu'un peut-il m'expliquer pourquoi R n'a pas quelque chose de simple comme df#drop(var_name), et à la place, nous devons faire ces contournements compliqués?
ifly6
2
@ ifly6 La fonction 'subset ()' dans R est à peu près aussi parcimonieuse que la fonction 'drop ()' en Python, sauf que vous n'avez pas besoin de spécifier l'argument de l'axe ... Je conviens que c'est ennuyeux de ne pas pouvoir être un seul mot-clé / syntaxe ultime et facile à mettre en œuvre dans tous les domaines pour quelque chose d'aussi basique que de supprimer une colonne.
Paul Sochacki

Réponses:

912

Vous pouvez utiliser une simple liste de noms:

DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
drops <- c("x","z")
DF[ , !(names(DF) %in% drops)]

Ou, alternativement, vous pouvez faire une liste de ceux à conserver et vous y référer par nom:

keeps <- c("y", "a")
DF[keeps]

EDIT: Pour ceux qui ne connaissent pas encore l' dropargument de la fonction d'indexation, si vous souhaitez conserver une colonne comme trame de données, vous devez:

keeps <- "y"
DF[ , keeps, drop = FALSE]

drop=TRUE(ou ne pas le mentionner) supprimera les dimensions inutiles, et donc retournera un vecteur avec les valeurs de la colonne y.

Joris Meys
la source
19
la fonction de sous-ensemble fonctionne mieux car elle ne convertira pas un
bloc de
3
@ mut1na vérifie l'argument drop = FALSE de la fonction d'indexation.
Joris Meys
4
Cela ne devrait-il pas être DF[,keeps]au lieu de DF[keeps]?
lindelof
8
@lindelof Non. C'est possible, mais vous devez ensuite ajouter drop = FALSE pour empêcher R de convertir votre bloc de données en vecteur si vous ne sélectionnez qu'une seule colonne. N'oubliez pas que les blocs de données sont des listes, donc la sélection de liste (unidimensionnelle comme je l'ai fait) fonctionne parfaitement bien et renvoie toujours une liste. Ou une trame de données dans ce cas, c'est pourquoi je préfère l'utiliser.
Joris Meys
7
@AjayOhri Oui, ce serait le cas. Sans virgule, vous utilisez la méthode de "liste" de sélection, ce qui signifie que même lorsque vous extrayez une seule colonne, vous obtenez toujours un bloc de données renvoyé. Si vous utilisez la méthode "matrice", comme vous le faites, vous devez savoir que si vous ne sélectionnez qu'une seule colonne, vous obtenez un vecteur au lieu d'un bloc de données. Pour éviter cela, vous devez ajouter drop = FALSE. Comme expliqué dans ma réponse, et dans le commentaire juste au-dessus de la vôtre ...
Joris Meys
453

Il y a aussi la subsetcommande, utile si vous savez quelles colonnes vous voulez:

df <- data.frame(a = 1:10, b = 2:11, c = 3:12)
df <- subset(df, select = c(a, c))

MISE À JOUR après commentaire de @hadley: Pour supprimer les colonnes a, c, vous pouvez faire:

df <- subset(df, select = -c(a, c))
Prasad Chalasani
la source
3
Je souhaite vraiment que la subsetfonction R ait une option comme "allbut = FALSE", qui "inverse" la sélection lorsqu'elle est définie sur TRUE, c'est-à-dire qui conserve toutes les colonnes sauf celles de la selectliste.
Prasad Chalasani
4
@prasad, voir la réponse @joris ci-dessous. Un sous-ensemble sans critère de sous-ensemble est un peu exagéré. Essayez simplement:df[c("a", "c")]
JD Long
@JD Je le savais, mais j'aime la commodité syntaxique de la subsetcommande où vous n'avez pas besoin de mettre des guillemets autour des noms de colonnes - Je suppose que cela ne me dérange pas de taper quelques caractères supplémentaires juste pour éviter de citer des noms :)
Prasad Chalasani
11
Notez que vous ne devez pas utiliser à l' subsetintérieur d'autres fonctions.
Ari B. Friedman du
196
within(df, rm(x))

est probablement le plus simple, ou pour plusieurs variables:

within(df, rm(x, y))

Ou si vous avez affaire à data.tables (par Comment supprimer une colonne par nom dans data.table? ):

dt[, x := NULL]   # Deletes column x by reference instantly.

dt[, !"x"]   # Selects all but x into a new data.table.

ou pour plusieurs variables

dt[, c("x","y") := NULL]

dt[, !c("x", "y")]
Max Ghenis
la source
26
within(df, rm(x))est de loin la solution la plus propre. Étant donné que c'est une possibilité, toutes les autres réponses semblent inutilement compliquées par un ordre de grandeur.
Miles Erickson du
2
Notez que within(df, rm(x))cela ne fonctionnera pas s'il y a des colonnes en double nommées xdans df.
MichaelChirico
2
@MichaelChirico pour clarifier, il ne supprime ni l'un ni l'autre mais semble changer les valeurs des données. On a de plus gros problèmes si c'est le cas, mais voici un exemple: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))retourne data.frame(x = 2, x = 2).
Max Ghenis
1
@MilesErickson Le problème est que vous comptez sur une fonction within()puissante mais qui utilise également NSE. La note sur la page d'aide indique clairement que pour la programmation, des soins suffisants doivent être utilisés.
Joris Meys
@MilesErickson À quelle fréquence rencontrerait-on une trame de données contenant des noms en double?
HSchmale
115

Vous pouvez utiliser %in%comme ceci:

df[, !(colnames(df) %in% c("x","bar","foo"))]
Joshua Ulrich
la source
1
Suis-je en train de manquer quelque chose, ou est-ce effectivement la même solution que la première partie de la réponse de Joris? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher
9
@DanielFletcher: c'est pareil. Regardez les horodatages sur les réponses. Nous avons répondu en même temps ... il y a 5 ans. :)
Joshua Ulrich
5
Nutty. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher
54

list (NULL) fonctionne également:

dat <- mtcars
colnames(dat)
# [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
# [11] "carb"
dat[,c("mpg","cyl","wt")] <- list(NULL)
colnames(dat)
# [1] "disp" "hp"   "drat" "qsec" "vs"   "am"   "gear" "carb"
Vincent
la source
1
Brillant! Cela étend l'affectation NULL à une seule colonne de manière naturelle et évite (apparemment) la copie (bien que je ne sache pas ce qui se passe sous le capot, il n'est donc peut-être pas plus efficace d'utiliser la mémoire ... mais cela me semble clairement syntaxiquement plus efficace.)
c-urchin
6
Vous n'avez pas besoin de liste (NULL), NULL est suffisant. par exemple: dat [, 4] = NULL
CousinCocaine
8
La question de l'OP était de savoir comment supprimer plusieurs colonnes. dat [, 4: 5] <- NULL ne fonctionnera pas. C'est là que la liste (NULL) entre en jeu. Cela fonctionne pour 1 ou plusieurs colonnes.
Vincent
Cela ne fonctionne pas non plus lorsque vous essayez de supprimer un nom de colonne dupliqué.
MichaelChirico
@MichaelChirico Fonctionne très bien pour moi. Donnez une étiquette si vous souhaitez supprimer la première des colonnes du même nom ou donnez des indices pour chaque colonne que vous souhaitez supprimer. Si vous avez un exemple où cela ne fonctionne pas, je serais intéressé de le voir. Peut-être l'afficher comme une nouvelle question?
Vincent
42

Si vous souhaitez supprimer les colonnes par référence et éviter la copie interne associée, data.framesvous pouvez utiliser le data.tablepackage et la fonction:=

Vous pouvez passer un nom de vecteur de caractères sur le côté gauche de l' :=opérateur et NULLcomme RHS.

library(data.table)

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
DT <- data.table(df)
# or more simply  DT <- data.table(a=1:10, b=1:10, c=1:10, d=1:10) #

DT[, c('a','b') := NULL]

Si vous souhaitez prédéfinir les noms en tant que vecteur de caractères en dehors de l'appel à [, encapsulez le nom de l'objet dans ()ou {}pour forcer le LHS à être évalué dans la portée appelante et non en tant que nom dans la portée de DT.

del <- c('a','b')
DT <- data.table(a=1:10, b=1:10, c=1:10, d=1:10)
DT[, (del) := NULL]
DT <-  <- data.table(a=1:10, b=1:10, c=1:10, d=1:10)
DT[, {del} := NULL]
# force or `c` would also work.   

Vous pouvez également utiliser set, ce qui évite la surcharge de [.data.table, et travaille aussi pour data.frames!

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
DT <- data.table(df)

# drop `a` from df (no copying involved)

set(df, j = 'a', value = NULL)
# drop `b` from DT (no copying involved)
set(DT, j = 'b', value = NULL)
mnel
la source
41

Il existe une stratégie potentiellement plus puissante basée sur le fait que grep () retournera un vecteur numérique. Si vous avez une longue liste de variables comme je le fais dans l'un de mes ensembles de données, certaines variables qui se terminent par ".A" et d'autres qui se terminent par ".B" et vous ne voulez que celles qui se terminent par ".A" (avec avec toutes les variables qui ne correspondent à aucun modèle, procédez comme suit:

dfrm2 <- dfrm[ , -grep("\\.B$", names(dfrm)) ]

Dans le cas présent, en utilisant l'exemple de Joris Meys, ce n'est peut-être pas aussi compact, mais ce serait:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
IRTFM
la source
1
Si nous définissons dropsen premier lieu comme paste0("^", drop_cols, "$"), cela devient beaucoup plus agréable (lire: plus compact) avec sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico
30

Une autre dplyrréponse. Si vos variables ont une structure de dénomination commune, vous pouvez essayer starts_with(). Par exemple

library(dplyr)
df <- data.frame(var1 = rnorm(5), var2 = rnorm(5), var3 = rnorm (5), 
                 var4 = rnorm(5), char1 = rnorm(5), char2 = rnorm(5))
df
#        var2      char1        var4       var3       char2       var1
#1 -0.4629512 -0.3595079 -0.04763169  0.6398194  0.70996579 0.75879754
#2  0.5489027  0.1572841 -1.65313658 -1.3228020 -1.42785427 0.31168919
#3 -0.1707694 -0.9036500  0.47583030 -0.6636173  0.02116066 0.03983268
df1 <- df %>% select(-starts_with("char"))
df1
#        var2        var4       var3       var1
#1 -0.4629512 -0.04763169  0.6398194 0.75879754
#2  0.5489027 -1.65313658 -1.3228020 0.31168919
#3 -0.1707694  0.47583030 -0.6636173 0.03983268

Si vous souhaitez supprimer une séquence de variables dans le bloc de données, vous pouvez utiliser :. Par exemple, si vous souhaitez supprimer var2, var3et toutes les variables entre les deux, il vous restera simplement var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268
Pat W.
la source
1
Sans oublier toutes les autres opportunités qui viennent avec select(), comme contains()ou matches(), qui accepte également regex.
ha_pu
23

Une autre possibilité:

df <- df[, setdiff(names(df), c("a", "c"))]

ou

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
scentoni
la source
2
Dommage que cela ne soit pas plus voté car l'utilisation de setdiffest optimale surtout dans le cas d'un très grand nombre de colonnes.
ctbrown
Un autre angle à ce sujet:df <- df[ , -which(grepl('a|c', names(df)))]
Joe
23
DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
DF

Production:

    x  y z  a
1   1 10 5 11
2   2  9 5 12
3   3  8 5 13
4   4  7 5 14
5   5  6 5 15
6   6  5 5 16
7   7  4 5 17
8   8  3 5 18
9   9  2 5 19
10 10  1 5 20

DF[c("a","x")] <- list(NULL)

Production:

        y z
    1  10 5
    2   9 5
    3   8 5
    4   7 5
    5   6 5
    6   5 5
    7   4 5
    8   3 5    
    9   2 5
    10  1 5
Kun Ren
la source
23

Solution Dplyr

Je doute que cela obtienne beaucoup d'attention ici, mais si vous avez une liste de colonnes que vous souhaitez supprimer et que vous souhaitez le faire dans une dplyrchaîne que j'utilise one_of()dans la selectclause:

Voici un exemple simple et reproductible:

undesired <- c('mpg', 'cyl', 'hp')

mtcars <- mtcars %>%
  select(-one_of(undesired))

La documentation peut être trouvée en exécutant ?one_ofou ici:

http://genomicsclass.github.io/book/pages/dplyr_tutorial.html

User632716
la source
22

Par intérêt, cela signale l'une des étranges incohérences de syntaxe multiples de R. Par exemple, étant donné un bloc de données à deux colonnes:

df <- data.frame(x=1, y=2)

Cela donne une trame de données

subset(df, select=-y)

mais cela donne un vecteur

df[,-2]

Tout cela est expliqué dans ?[mais ce n'est pas exactement le comportement attendu. Enfin du moins pas pour moi ...

jkeirstead
la source
18

Voici une dplyrfaçon de procéder:

#df[ -c(1,3:6, 12) ]  # original
df.cut <- df %>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)  # with dplyr::select()

J'aime cela car il est intuitif de lire et de comprendre sans annotation et robuste aux colonnes changeant de position dans le bloc de données. Il suit également l'idiome vectorisé utilisé -pour supprimer des éléments.

c.gutierrez
la source
Ajoutant à cela que (1) l'utilisateur veut remplacer le df d'origine (2) magrittr a un %<>% opérateur pour remplacer l'objet d'entrée, il pourrait être simplifiédf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek
1
Si vous avez une longue liste de colonnes à supprimer, avec dplyr, il pourrait être plus facile de les regrouper et de mettre un seul moins:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar
14

Je continue de penser qu'il doit y avoir un meilleur idiome, mais pour la soustraction de colonnes par nom, j'ai tendance à faire ce qui suit:

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)

# return everything except a and c
df <- df[,-match(c("a","c"),names(df))]
df
JD Long
la source
4
Pas une bonne idée d'annuler le match -df[,-match(c("e","f"),names(df))]
hadley
. @ JDLong - Et si je veux déposer la colonne où commence le nom de la colonne -?
Chetan Arvind Patil
12

Il y a une fonction appelée dropNamed()dans le BBmiscpackage de Bernd Bischl qui fait exactement cela.

BBmisc::dropNamed(df, "x")

L'avantage est qu'il évite de répéter l'argument de trame de données et convient donc à la canalisation magrittr(tout comme les dplyrapproches):

df %>% BBmisc::dropNamed("x")
krlmlr
la source
9

Autre solution si vous ne souhaitez pas utiliser @ hadley ci-dessus: Si "COLUMN_NAME" est le nom de la colonne que vous souhaitez supprimer:

df[,-which(names(df) == "COLUMN_NAME")]
Nick Keramaris
la source
1
(1) Le problème est de supprimer plusieurs colonnes à la fois. (2) Cela ne fonctionnera pas si ce COLUMN_NAMEn'est pas le cas df(vérifiez vous-même:) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]est plus simple et ne souffre pas de (2)
Marek
Pouvez-vous donner plus d'informations sur cette réponse?
Akash Nayak
8

Au-delà de ce qui a été select(-one_of(drop_col_names))démontré dans les réponses précédentes, il existe quelques autres dplyroptions pour supprimer des colonnes select()qui n'impliquent pas la définition de tous les noms de colonnes spécifiques (en utilisant les exemples de données dplyr starwars pour une certaine variété de noms de colonnes):

library(dplyr)
starwars %>% 
  select(-(name:mass)) %>%        # the range of columns from 'name' to 'mass'
  select(-contains('color')) %>%  # any column name that contains 'color'
  select(-starts_with('bi')) %>%  # any column name that starts with 'bi'
  select(-ends_with('er')) %>%    # any column name that ends with 'er'
  select(-matches('^f.+s$')) %>%  # any column name matching the regex pattern
  select_if(~!is.list(.)) %>%     # not by column name but by data type
  head(2)

# A tibble: 2 x 2
homeworld species
  <chr>     <chr>  
1 Tatooine  Human  
2 Tatooine  Droid 

Si vous devez supprimer une colonne qui peut ou peut ne pas exister dans le bloc de données, voici une légère torsion select_if()qui, contrairement à l'utilisation one_of(), ne lancera pas d' Unknown columns:avertissement si le nom de la colonne n'existe pas. Dans cet exemple, 'bad_column' n'est pas une colonne dans le bloc de données:

starwars %>% 
  select_if(!names(.) %in% c('height', 'mass', 'bad_column'))
sbha
la source
4

Fournissez le bloc de données et une chaîne de noms séparés par des virgules à supprimer:

remove_features <- function(df, features) {
  rem_vec <- unlist(strsplit(features, ', '))
  res <- df[,!(names(df) %in% rem_vec)]
  return(res)
}

Utilisation :

remove_features(iris, "Sepal.Length, Petal.Width")

entrez la description de l'image ici

Cybernétique
la source
1

Recherchez l'index des colonnes que vous souhaitez supprimer à l'aide de which. Donnez à ces index un signe négatif ( *-1). Sous-ensemble ensuite sur ces valeurs, ce qui les supprimera de la trame de données. Ceci est un exemple.

DF <- data.frame(one=c('a','b'), two=c('c', 'd'), three=c('e', 'f'), four=c('g', 'h'))
DF
#  one two three four
#1   a   d     f    i
#2   b   e     g    j

DF[which(names(DF) %in% c('two','three')) *-1]
#  one four
#1   a    g
#2   b    h
Milan
la source
1

Si vous avez une grande data.framemémoire et que vous utilisez peu de mémoire [ . . . . ou rmetwithin pour supprimer les colonnes d'undata.frame , comme subsetc'est actuellement le cas (R 3.6.2) en utilisant plus de mémoire - à côté de l'indice du manuel à utiliser de manière subsetinteractive .

getData <- function() {
  n <- 1e7
  set.seed(7)
  data.frame(a = runif(n), b = runif(n), c = runif(n), d = runif(n))
}

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF <- DF[setdiff(names(DF), c("a", "c"))] ##
#DF <- DF[!(names(DF) %in% c("a", "c"))] #Alternative
#DF <- DF[-match(c("a","c"),names(DF))]  #Alternative
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#0.1 MB are used

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF <- subset(DF, select = -c(a, c)) ##
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#357 MB are used

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF <- within(DF, rm(a, c)) ##
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#0.1 MB are used

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF[c("a", "c")]  <- NULL ##
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#0.1 MB are used
GKi
la source