So löschen Sie Spalten nach Namen in einem Datenrahmen

304

Ich habe einen großen Datensatz und möchte bestimmte Spalten lesen oder alle anderen löschen.

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

Ich wähle die Spalten aus, die mich nicht interessieren:

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

und dann möchte ich so etwas machen wie:

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

um alle unerwünschten Spalten zu löschen. Ist das die optimale Lösung?

leroux
quelle
1
Als ich über das Problem geschlafen habe, dachte ich, dass das subset(data, select=c(...))in meinem Fall hilft, Vars fallen zu lassen. Die Frage betraf jedoch hauptsächlich den paste("data$",var.out[i],sep="")Teil für den Zugriff auf interessierende Spalten innerhalb der Schleife. Wie kann ich einen Spaltennamen einfügen oder irgendwie zusammensetzen? Vielen Dank an alle für Ihre Aufmerksamkeit und Ihre Hilfe
Leroux
7
Mögliches Duplikat von Drop-Spalten im R-
Datenrahmen

Antworten:

380

Sie sollten entweder die Indizierung oder die subsetFunktion verwenden. Zum Beispiel :

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

Dann können Sie die whichFunktion und den -Operator bei der Spaltenindizierung verwenden:

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

Oder, viel einfacher, verwenden Sie das selectArgument der subsetFunktion: Sie können den -Operator dann direkt für einen Vektor von Spaltennamen verwenden und sogar die Anführungszeichen um die Namen weglassen!

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

Beachten Sie, dass Sie auch die gewünschten Spalten auswählen können, anstatt die anderen zu löschen:

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
quelle
2
Das selectArgument der subsetFunktion hat den Job perfekt gemacht! Danke juba!
Leroux
2
whichist nicht notwendig, siehe Istas Antwort. Aber die Teilmenge mit -ist schön! Wusste das nicht!
TMS
5
subsetsieht gut aus, aber die Art und Weise, wie fehlende Werte lautlos gelöscht werden, scheint mir ziemlich gefährlich zu sein.
static_rtti
2
subsetist in der Tat sehr praktisch, aber denken Sie daran, es nicht zu verwenden, es sei denn, Sie verwenden R interaktiv. Weitere Informationen finden Sie in der Warnung in der Funktionsdokumentation und in dieser SO-Frage .
Waldir Leoncio
4
"Sie können sogar die Anführungszeichen um die Namen weglassen!", müssen Sie die Anführungszeichen tatsächlich weglassen, sonst erhalten Sie ein ungültiges Argument für den unären Operator. Wenn Ihre Namen bestimmte Zeichen (z. B. "-") enthalten, können Sie diese Methode überhaupt nicht verwenden, da das Löschen von Anführungszeichen dazu führt, dass R Ihren Code nicht richtig analysieren kann.
Oh54
122

Nicht dafür verwenden -which(), es ist extrem gefährlich. Erwägen:

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

Verwenden Sie stattdessen die Teilmenge oder die !Funktion:

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

Ich habe dies aus schmerzhaften Erfahrungen gelernt. Nicht überbeanspruchen which()!

Ista
quelle
31
setdiffist auch nützlich:setdiff(names(dat), c("foo", "bar"))
Hadley
Der setdiffVorschlag von @hadley eignet sich sehr gut für lange Namenslisten.
JASC
48

Erstens können Sie die direkte Indizierung (mit Booleschen Vektoren) verwenden, anstatt erneut auf Spaltennamen zuzugreifen, wenn Sie mit demselben Datenrahmen arbeiten. Wie Ista betont, ist es sicherer und schneller zu schreiben und auszuführen. Sie brauchen also nur:

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

und dann einfach Daten neu zuweisen:

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

Zweitens können Sie schneller zu schreiben den Spalten, die Sie entfernen möchten, direkt NULL zuweisen:

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

Schließlich können Sie subset () verwenden, es kann jedoch nicht wirklich im Code verwendet werden (selbst die Hilfedatei warnt davor). Insbesondere besteht für mich ein Problem darin, dass Sie den Ausdruck, der den Spaltennamen entspricht, ohne Anführungszeichen schreiben müssen, wenn Sie die Drop-Funktion von susbset () direkt verwenden möchten:

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

Als Bonus gibt es hier einen kleinen Benchmark der verschiedenen Optionen, der deutlich zeigt, dass die Teilmenge langsamer und die erste Methode zur Neuzuweisung schneller ist:

                                        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

Microbench-Diagramm

Code ist unten:

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
quelle
2
Ich mag Ihre zweite Alternative NULL, aber warum ist es notwendig, wenn Sie mehr als zwei Namen eingeben, um sie zuzuweisen list(NULL)? Ich bin nur neugierig zu wissen, wie es funktioniert, weil ich es mit nur einem Namen versucht habe und es nicht brauchelist()
Darwin PC
3
@DarwinPC Ja. Wenn Sie direkt auf ein Vektorelement (mit $oder [[) zugreifen , führt die Verwendung <- list(NULL)tatsächlich zu falschen Ergebnissen. Wenn Sie mit einer oder mehreren Spalten auf eine Teilmenge des Datenrahmens zugreifen, <- list(NULL)ist dies der richtige Weg, auch wenn dieser für einen einspaltigen Datenrahmen nicht benötigt wird (da er df['myColumns']bei Bedarf in einen Vektor umgewandelt wird).
Antoine Lizée
27

Sie können das dplyrPaket auch ausprobieren :

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
quelle
4
Die Verwendung dplyr::select(df2, -one_of(c('x','y')))funktioniert weiterhin (mit einer Warnung), auch wenn einige der genannten Spalten nicht vorhanden sind
Divibisan
13

Hier ist eine schnelle Lösung dafür. Angenommen, Sie haben einen Datenrahmen X mit drei Spalten A, B und 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

Wenn ich eine Spalte entfernen möchte, z. B. B, verwenden Sie einfach grep für Spaltennamen, um den Spaltenindex abzurufen, mit dem Sie die Spalte weglassen können.

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

Ihr neuer X-Datenrahmen würde folgendermaßen aussehen (diesmal ohne die B-Spalte):

> X
  A C
1 1 5
2 2 6

Das Schöne an grep ist, dass Sie mehrere Spalten angeben können, die dem regulären Ausdruck entsprechen. Wenn ich X mit fünf Spalten hätte (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

Nehmen Sie die Spalten B und D heraus:

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

EDIT: In Anbetracht des Grepl-Vorschlags von Matthew Lundberg in den Kommentaren unten:

> 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

Wenn ich versuche, eine nicht vorhandene Spalte zu löschen, sollte nichts passieren:

> X<-X[,!grepl("G",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10
Joben R. Ilagan
quelle
3
X[,-grep("B",colnames(X))]gibt keine Spalten zurück, wenn kein Spaltenname enthalten ist B, anstatt alle Spalten wie gewünscht zurückzugeben. Betrachten Sie mit X <- irisfür ein Beispiel. Dies ist das Problem bei der Verwendung negativer Indizes mit berechneten Werten. Betrachten Sie greplstattdessen.
Matthew Lundberg
6

Ich habe versucht, eine Spalte zu löschen, während ich das Paket verwendet habe, data.tableund habe ein unerwartetes Ergebnis erhalten. Ich denke, das Folgende könnte es wert sein, veröffentlicht zu werden. Nur ein kleiner Warnhinweis.

[Herausgegeben von 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

Grundsätzlich ist die Syntax für data.tableNICHT genau die gleiche wie data.frame. Tatsächlich gibt es viele Unterschiede, siehe FAQ 1.1 und FAQ 2.17. Du wurdest gewarnt!

Mark Miller
quelle
1
Oder Sie können die DT[,var.out := NULL]gewünschten Spalten löschen.
mnel
Die Teilmenge (x, select = ...) Methode funktioniert sowohl für data.frameals auch für data.tableKlassen
Momeara
3

Ich habe den Code geändert 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]

Wie auch immer, Jubas Antwort ist die beste Lösung für mein Problem!

leroux
quelle
Warum möchten Sie dies in einer Schleife tun? Die Antwort von juba zeigt Ihnen, wie es in einem Schritt geht. Warum es komplizierter machen?
Ista
Natürlich benutze ich das selectArgument der subsetFunktion in meinem Code. Ich wollte nur sehen, wie ich auf beliebige Spalten in einer Schleife zugreifen kann, falls ich etwas anderes tun möchte, als nur die Spalte zu löschen. Der ursprüngliche Datensatz hat ungefähr 1200 Vars und ich bin nur daran interessiert, 4 davon zu verwenden, ohne zu wissen, wo genau sie sich befinden.
Leroux
2

Hier ist eine andere Lösung, die für andere hilfreich sein kann. Der folgende Code wählt eine kleine Anzahl von Zeilen und Spalten aus einem großen Datensatz aus. Die Spalten werden wie in einer der Antworten von juba ausgewählt, außer dass ich eine Einfügefunktion verwende, um eine Reihe von Spalten mit Namen auszuwählen, die fortlaufend nummeriert sind:

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

Ich kann Ihre Frage in den Kommentaren aufgrund der geringen Reputation nicht beantworten.

Der nächste Code gibt einen Fehler aus, da die Einfügefunktion eine Zeichenfolge zurückgibt

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

Hier ist eine mögliche Lösung:

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
}

oder einfach machen:

for(i in 1:length(var.out)) {
  data[var.out[i]] <- NULL
}
Andriy T.
quelle
-1
df = mtcars 
entferne vs und bin, weil sie kategorisch sind. Im Datensatz befindet sich vs in Spalte 8, am in Spalte 9

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

Abhilash Ponnam
quelle