Löschen Sie Datenrahmenspalten nach Namen

874

Ich habe eine Reihe von Spalten, die ich aus einem Datenrahmen entfernen möchte. Ich weiß, dass wir sie einzeln löschen können, indem wir Folgendes verwenden:

df$x <- NULL

Aber ich hatte gehofft, dies mit weniger Befehlen zu tun.

Außerdem weiß ich, dass ich Spalten mithilfe der Ganzzahlindizierung wie folgt löschen kann:

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

Ich befürchte jedoch, dass sich die relative Position meiner Variablen ändern könnte.

Angesichts der Stärke von R dachte ich, es könnte einen besseren Weg geben, als jede Spalte einzeln zu löschen.

Btibert3
quelle
13
Kann mir jemand erklären, warum R nicht so einfach df#drop(var_name)ist und wir stattdessen diese komplizierten Workarounds durchführen müssen?
ifly6
2
@ ifly6 Die Funktion 'subset ()' in R ist ungefähr so ​​sparsam wie die Funktion 'drop ()' in Python, außer dass Sie das Achsenargument nicht angeben müssen ... Ich stimme zu, dass es ärgerlich ist, dass dies nicht möglich ist Seien Sie nur ein ultimatives, einfaches Schlüsselwort / eine Syntax, die auf der ganzen Linie für etwas so Grundlegendes wie das Löschen einer Spalte implementiert wird.
Paul Sochacki

Antworten:

912

Sie können eine einfache Liste von Namen verwenden:

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

Alternativ können Sie eine Liste der zu erhaltenden Personen erstellen und sie namentlich referenzieren:

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

BEARBEITEN: dropWenn Sie das Argument der Indexierungsfunktion noch nicht kennen , können Sie Folgendes tun, wenn Sie eine Spalte als Datenrahmen beibehalten möchten:

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

drop=TRUE(oder nicht erwähnt) löscht unnötige Dimensionen und gibt daher einen Vektor mit den Werten der Spalte zurück y.

Joris Meys
quelle
19
Die Teilmengenfunktion funktioniert besser, da sie keinen
Datenrahmen
3
@ mut1na Überprüfen Sie das Argument drop = FALSE der Indexierungsfunktion.
Joris Meys
4
Sollte das nicht DF[,keeps]stattdessen sein DF[keeps]?
Lindelof
8
@lindelof Nein. Es kann, aber dann müssen Sie drop = FALSE hinzufügen, um zu verhindern, dass R Ihren Datenrahmen in einen Vektor konvertiert, wenn Sie nur eine einzelne Spalte auswählen. Vergessen Sie nicht, dass Datenrahmen Listen sind, sodass die Listenauswahl (eindimensional wie ich) einwandfrei funktioniert und immer eine Liste zurückgibt. Oder in diesem Fall einen Datenrahmen, weshalb ich ihn lieber benutze.
Joris Meys
7
@ AjayOhri Ja, das würde es. Ohne Komma verwenden Sie die Auswahlmethode "Liste". Dies bedeutet, dass auch beim Extrahieren einer einzelnen Spalte ein Datenrahmen zurückgegeben wird. Wenn Sie wie Sie die "Matrix" -Methode verwenden, sollten Sie sich bewusst sein, dass Sie einen Vektor anstelle eines Datenrahmens erhalten, wenn Sie nur eine einzelne Spalte auswählen. Um dies zu vermeiden, müssen Sie drop = FALSE hinzufügen. Wie in meiner Antwort und im Kommentar direkt über Ihrem erklärt ...
Joris Meys
453

Es gibt auch den subsetBefehl, der nützlich ist, wenn Sie wissen, welche Spalten Sie möchten:

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

AKTUALISIERT nach Kommentar von @hadley: Um die Spalten a, c zu löschen , können Sie Folgendes tun:

df <- subset(df, select = -c(a, c))
Prasad Chalasani
quelle
3
Ich wünschte wirklich, die R- subsetFunktion hätte eine Option wie "allbut = FALSE", die die Auswahl "invertiert", wenn sie auf TRUE gesetzt ist, dh alle Spalten außer denen in der selectListe behält .
Prasad Chalasani
4
@prasad, siehe @joris Antwort unten. Eine Teilmenge ohne Teilmengenkriterien ist ein bisschen übertrieben. Versuchen Sie einfach:df[c("a", "c")]
JD Long
@JD Ich wusste das, aber ich mag die syntaktische Bequemlichkeit des subsetBefehls, bei dem Sie keine Anführungszeichen um die Spaltennamen setzen müssen - ich denke, es macht mir nichts aus, ein paar zusätzliche Zeichen einzugeben, nur um das Zitieren von Namen zu vermeiden :)
Prasad Chalasani
11
Beachten Sie, dass Sie diese nicht subsetin anderen Funktionen verwenden sollten.
Ari B. Friedman
196
within(df, rm(x))

ist wahrscheinlich am einfachsten oder für mehrere Variablen:

within(df, rm(x, y))

Oder wenn Sie es mit data.tables zu tun haben (per Wie löscht man eine Spalte nach Namen in data.table? ):

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

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

oder für mehrere Variablen

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

dt[, !c("x", "y")]
Max Ghenis
quelle
26
within(df, rm(x))ist bei weitem die sauberste Lösung. Da dies möglich ist, erscheint jede andere Antwort um eine Größenordnung unnötig kompliziert.
Miles Erickson
2
Beachten Sie, dass within(df, rm(x))wird nicht funktionieren , wenn es doppelte Spalten mit den Namen xin df.
MichaelChirico
2
@MichaelChirico zur Verdeutlichung, es entfernt keines von beiden, scheint aber die Werte der Daten zu ändern. Man hat größere Probleme, wenn dies der Fall ist, aber hier ist ein Beispiel: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))kehrt zurück data.frame(x = 2, x = 2).
Max Ghenis
1
@MilesErickson Problem ist, dass Sie sich auf eine Funktion verlassen, within()die leistungsstark ist, aber auch NSE verwendet. Der Hinweis auf der Hilfeseite besagt deutlich, dass für die Programmierung ausreichende Sorgfalt angewendet werden sollte.
Joris Meys
@MilesErickson Wie oft stößt man auf einen Datenrahmen mit doppelten Namen?
HSchmale
115

Sie könnten so verwenden %in%:

df[, !(colnames(df) %in% c("x","bar","foo"))]
Joshua Ulrich
quelle
1
Vermisse ich etwas oder ist dies tatsächlich die gleiche Lösung wie der erste Teil von Joris 'Antwort? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher
9
@ DanielFletcher: Es ist das gleiche. Schauen Sie sich die Zeitstempel auf den Antworten an. Wir haben zur gleichen Zeit geantwortet ... vor 5 Jahren. :)
Joshua Ulrich
5
Nussig. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher
54

list (NULL) funktioniert auch:

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
quelle
1
Brillant! Dies erweitert die NULL-Zuweisung auf natürliche Weise auf eine einzelne Spalte und vermeidet (scheinbar) das Kopieren (obwohl ich nicht weiß, was unter der Haube passiert, sodass die Speichernutzung möglicherweise nicht effizienter ist ... scheint mir jedoch klar zu sein syntaktisch effizienter.)
c-urchin
6
Sie brauchen keine Liste (NULL), NULL ist ausreichend. Beispiel: dat [, 4] = NULL
CousinCocaine
8
Die Frage von OP war, wie mehrere Spalten gelöscht werden können. dat [, 4: 5] <- NULL funktioniert nicht. Hier kommt die Liste (NULL) ins Spiel. Sie funktioniert für eine oder mehrere Spalten.
Vincent
Dies funktioniert auch nicht, wenn versucht wird, einen doppelten Spaltennamen zu entfernen.
MichaelChirico
@ MichaelChirico Funktioniert gut für mich. Geben Sie entweder eine Bezeichnung an, wenn Sie die erste der gleichnamigen Spalten entfernen möchten, oder geben Sie Indizes für jede Spalte an, die Sie entfernen möchten. Wenn Sie ein Beispiel haben, bei dem es nicht funktioniert, würde es mich interessieren. Vielleicht als neue Frage posten?
Vincent
42

Wenn Sie die Spalten als Referenz entfernen und das damit verbundene interne Kopieren vermeiden möchten, data.frameskönnen Sie das data.tablePaket und die Funktion verwenden:=

Sie können Zeichenvektornamen an die linke Seite des :=Operators und NULLals RHS übergeben.

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]

Wenn Sie die Namen als Zeichenvektor außerhalb des Aufrufs an vordefinieren möchten [, wickeln Sie den Namen des Objekts in das aufrufende Objekt ein ()oder {}erzwingen Sie, dass es im aufrufenden Bereich ausgewertet wird, nicht als Name im Bereich von 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.   

Sie können auch verwendet werden set, was den Aufwand für das vermeidet [.data.table, und arbeitet auch für 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
quelle
41

Es gibt eine potenziell leistungsfähigere Strategie, die auf der Tatsache basiert, dass grep () einen numerischen Vektor zurückgibt. Wenn Sie eine lange Liste von Variablen haben, wie ich es in einem meiner Datensätze tue, einige Variablen, die mit ".A" enden, und andere, die mit ".B" enden, und Sie möchten nur diejenigen, die mit ".A" enden (entlang Gehen Sie bei allen Variablen, die keinem der beiden Muster entsprechen, wie folgt vor:

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

Für den vorliegenden Fall ist es am Beispiel von Joris Meys möglicherweise nicht so kompakt, aber es wäre:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
IRTFM
quelle
1
Wenn wir dropsin erster Linie als definieren paste0("^", drop_cols, "$"), wird dies viel schöner (lesen: kompakter) mit sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico
30

Eine andere dplyrAntwort. Wenn Ihre Variablen eine gemeinsame Namensstruktur haben, können Sie es versuchen starts_with(). Zum Beispiel

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

Wenn Sie eine Folge von Variablen im Datenrahmen ablegen möchten, können Sie verwenden :. Zum Beispiel , wenn Sie fallen wollte var2, var3und alle Variablen dazwischen, dann würden Sie nur mit gelassen werden var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268
Pat W.
quelle
1
Nicht zu vergessen all die anderen Möglichkeiten, die sich daraus ergeben select(), wie contains()oder matches(), die auch Regex akzeptieren.
ha_pu
23

Andere Möglichkeit:

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

oder

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
Scentoni
quelle
2
Schade, dass dies nicht mehr positiv bewertet wird, da die Verwendung von setdiffinsbesondere bei einer sehr großen Anzahl von Spalten optimal ist.
Ctbrown
Ein weiterer Winkel dazu: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

Ausgabe:

    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)

Ausgabe:

        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
quelle
23

Dplyr-Lösung

Ich bezweifle, dass dies hier unten viel Aufmerksamkeit erregt, aber wenn Sie eine Liste von Spalten haben, die Sie entfernen möchten, und Sie dies in einer dplyrKette tun möchten, die ich one_of()in der selectKlausel verwende:

Hier ist ein einfaches, reproduzierbares Beispiel:

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

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

Die Dokumentation finden Sie unter Ausführen ?one_ofoder hier:

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

User632716
quelle
22

Aus Interesse weist dies auf eine der seltsamen Mehrfachsyntaxinkonsistenzen von R hin. Zum Beispiel bei einem zweispaltigen Datenrahmen:

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

Dies ergibt einen Datenrahmen

subset(df, select=-y)

aber das ergibt einen Vektor

df[,-2]

Dies wird alles in erklärt, ?[aber es ist nicht genau das erwartete Verhalten. Na zumindest nicht für mich ...

jkeirstead
quelle
18

Hier ist ein dplyrWeg, um es zu tun:

#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()

Ich mag das, weil es intuitiv zu lesen und zu verstehen ist, ohne Anmerkungen zu machen, und robust gegenüber Spalten ist, die ihre Position innerhalb des Datenrahmens ändern. Es folgt auch der vektorisierten Redewendung, mit -der Elemente entfernt werden.

c.gutierrez
quelle
Hinzu kommt, dass (1) Benutzer das ursprüngliche df (2) magrittr ersetzen möchte, um das Eingabeobjekt %<>% zu ersetzen, das vereinfacht werden könntedf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek
1
Wenn Sie eine lange Liste von Spalten zum Löschen haben, dplyrist es möglicherweise einfacher, sie zu gruppieren und nur ein Minus zu setzen:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar
14

Ich denke immer wieder, dass es eine bessere Redewendung geben muss, aber für die Subtraktion von Spalten nach Namen neige ich dazu, Folgendes zu tun:

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
quelle
4
Keine gute Idee, Match zu negieren -df[,-match(c("e","f"),names(df))]
Hadley
. @ JDLong - Was ist, wenn ich eine Spalte löschen möchte, mit der der Spaltenname beginnt -?
Chetan Arvind Patil
12

dropNamed()In Bernd Bischls BBmiscPaket gibt es eine Funktion , die genau dies tut.

BBmisc::dropNamed(df, "x")

Der Vorteil besteht darin, dass das Wiederholen des Datenrahmenarguments vermieden wird und sich somit zum Einleiten eignet magrittr(genau wie bei den dplyrAnsätzen):

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

Eine andere Lösung, wenn Sie @ hadley's oben nicht verwenden möchten: Wenn "COLUMN_NAME" der Name der Spalte ist, die Sie löschen möchten:

df[,-which(names(df) == "COLUMN_NAME")]
Nick Keramaris
quelle
1
(1) Das Problem besteht darin, mehrere Spalten gleichzeitig zu löschen. (2) Es wird nicht funktionieren, wenn COLUMN_NAMEnicht indf (überprüfen Sie sich :) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]ist einfacher und leidet nicht unter (2)
Marek
Können Sie weitere Informationen zu dieser Antwort geben?
Akash Nayak
8

Darüber hinaus select(-one_of(drop_col_names))in früheren Antworten zeigten, gibt es ein paar andere dplyrOptionen für die Spalten fallen mit , select()dass beinhalten nicht alle spezifischen Spaltennamen definieren (mit der dplyr Beispieldaten für etwas Abwechslung in Spaltennamen starwars):

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 

Wenn Sie eine Spalte löschen müssen, die möglicherweise im Datenrahmen vorhanden ist oder nicht, wird hier eine leichte Wendung verwendet select_if(), die im Gegensatz zur Verwendung one_of()keine Unknown columns:Warnung auslöst, wenn der Spaltenname nicht vorhanden ist. In diesem Beispiel ist 'bad_column' keine Spalte im Datenrahmen:

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

Geben Sie den Datenrahmen und eine Zeichenfolge mit durch Kommas getrennten Namen zum Entfernen an:

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

Verwendung :

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

Geben Sie hier die Bildbeschreibung ein

Kybernetisch
quelle
1

Suchen Sie den Index der Spalten, mit denen Sie löschen möchten which. Geben Sie diesen Indizes ein negatives Vorzeichen ( *-1). Unterteilen Sie dann diese Werte, um sie aus dem Datenrahmen zu entfernen. Dies ist ein Beispiel.

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
Mailand
quelle
1

Wenn Sie einen großen data.frameund wenig Speicher haben [ . . . . oder rmund withinum Spalten von adata.frame zu entfernen , wie subsetes derzeit (R 3.6.2) mit mehr Speicher ist - neben dem Hinweis des Handbuchs, interaktiv zu verwendensubset .

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
quelle