Come eliminare le colonne per nome in un frame di dati


304

Ho un set di dati di grandi dimensioni e vorrei leggere colonne specifiche o eliminare tutte le altre.

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

Seleziono le colonne che non mi interessano:

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

e poi mi piacerebbe fare qualcosa del tipo:

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

per eliminare tutte le colonne indesiderate. Questa è la soluzione ottimale?


1
addormentato sul problema, stavo pensando che mi subset(data, select=c(...))aiuti nel mio caso a far cadere var. la domanda però riguardava principalmente la paste("data$",var.out[i],sep="")parte per accedere alle colonne di interesse all'interno del ciclo. come posso incollare o in qualche modo comporre un nome di colonna? Grazie a tutti per l'attenzione e il vostro aiuto
leroux

7
Possibile duplicato di colonne di
rilascio

Risposte:


380

È necessario utilizzare l'indicizzazione o la subsetfunzione. Per esempio :

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

Quindi è possibile utilizzare la whichfunzione e l' -operatore nell'indicizzazione delle colonne:

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

O, molto più semplice, usa l' selectargomento della subsetfunzione: puoi quindi usare l' -operatore direttamente su un vettore di nomi di colonna e puoi persino omettere le virgolette attorno ai nomi!

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

Tieni presente che puoi anche selezionare le colonne desiderate invece di eliminare le altre:

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

2
l' selectargomento della subsetfunzione ha fatto perfettamente il lavoro! Grazie Juba!
leroux,

2
whichnon è necessario, vedi la risposta di Ista. Ma il sottoinsieme -è carino! Non lo sapevo!
TMS,

5
subsetsembra buono, ma il modo in cui rilascia silenziosamente i valori mancanti mi sembra piuttosto pericoloso.
static_rtti,

2
subsetè davvero molto conveniente, ma ricorda di evitare di usarlo a meno che tu non stia usando R in modo interattivo. Vedere l'Avvertimento nella documentazione della funzione e questa domanda SO per ulteriori informazioni.
Waldir Leoncio,

4
"puoi persino omettere le virgolette attorno ai nomi!", devi effettivamente omettere le virgolette, altrimenti otterrai un argomento non valido per l'operatore unario. Se hai alcuni caratteri (ad esempio "-") nei tuoi nomi, non puoi affatto usare questo metodo poiché l'eliminazione delle virgolette impedirà a R di analizzare correttamente il tuo codice.
oh54

122

Non usare -which()per questo, è estremamente pericoloso. Tener conto di:

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

Utilizzare invece il sottoinsieme o la !funzione:

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

L'ho imparato da un'esperienza dolorosa. Non abusare which()!


31
setdiffè anche utile:setdiff(names(dat), c("foo", "bar"))
Hadley

La setdiffproposta di @hadley è ottima per lunghe liste di nomi.
JASC

48

Innanzitutto , è possibile utilizzare l'indicizzazione diretta (con vettori booleani) invece di accedere nuovamente ai nomi delle colonne se si lavora con lo stesso frame di dati; sarà più sicuro come sottolineato da Ista e più veloce da scrivere e da eseguire. Quindi ciò di cui avrai solo bisogno è:

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

e quindi, semplicemente riassegnare i dati:

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

In secondo luogo , più veloce da scrivere, puoi assegnare direttamente NULL alle colonne che desideri rimuovere:

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

Infine , puoi usare subset (), ma non può davvero essere usato nel codice (anche il file di aiuto lo avverte). In particolare, un problema per me è che se si desidera utilizzare direttamente la funzione di rilascio di susbset () è necessario scrivere senza virgolette l'espressione corrispondente ai nomi delle 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

Come bonus , ecco un piccolo benchmark delle diverse opzioni, che mostra chiaramente che il sottoinsieme è il più lento e che il primo metodo di riassegnazione è il più veloce:

                                        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

Grafico del microbench

Il codice è sotto:

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)

2
Mi piace usare la tua seconda alternativa NULL, ma perché quando metti più di due nomi è necessario assegnarlo list(NULL)? Sono solo curioso di sapere come funziona, perché ho provato con un solo nome e non ne ho bisognolist()
Darwin PC

3
@DarwinPC Sì. Se accedi direttamente a un elemento vettoriale (con $o [[), l'utilizzo <- list(NULL)in realtà porterà a risultati errati. Se accedi a un sottoinsieme del frame di dati con una o più colonne, <- list(NULL)è la strada da percorrere, anche se non è necessario per un frame di dati a una colonna (perché df['myColumns']verrà trasmesso su un vettore se necessario).
Antoine Lizée,

27

Puoi anche provare il dplyrpacchetto:

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

4
L'utilizzo dplyr::select(df2, -one_of(c('x','y')))continuerà a funzionare (con un avviso) anche se alcune delle colonne con nome non esistono
divibisan

13

Ecco una soluzione rapida per questo. Supponiamo che tu abbia un frame di dati X con tre colonne A, B e 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

Se voglio rimuovere una colonna, diciamo B, basta usare grep sui nomi di colonna per ottenere l'indice di colonna, che puoi usare per omettere la colonna.

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

Il tuo nuovo frame di dati X sarebbe simile al seguente (questa volta senza la colonna B):

> X
  A C
1 1 5
2 2 6

Il bello di grep è che puoi specificare più colonne che corrispondono all'espressione regolare. Se avessi X con cinque colonne (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

Elimina le colonne B e D:

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

EDIT: Considerando il suggerimento grepl di Matthew Lundberg nei commenti qui sotto:

> 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

Se provo a eliminare una colonna inesistente, non dovrebbe succedere nulla:

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

3
X[,-grep("B",colnames(X))]non restituirà colonne nel caso in cui non sia presente alcun nome di colonna B, anziché restituire tutte le colonne come desiderato. Considerare con X <- irisper un esempio. Questo è il problema con l'utilizzo di indici negativi con valori calcolati. Considerare greplinvece.
Matthew Lundberg,

6

Ho provato a eliminare una colonna durante l'utilizzo del pacchetto data.tablee ho ottenuto un risultato imprevisto. Penso che valga la pena pubblicare quanto segue. Solo un piccolo avvertimento.

[A cura di 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

Fondamentalmente, la sintassi per data.tableNON è esattamente la stessa di data.frame. Esistono infatti molte differenze, vedi FAQ 1.1 e FAQ 2.17. Sei stato avvertito!


1
Oppure puoi usare DT[,var.out := NULL]per cancellare le colonne che vuoi fare.
mnel

Il metodo subset (x, select = ...) funziona per entrambe data.framee per le data.tableclassi
momeara

3

Ho cambiato il codice 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]

Comunque, la risposta di juba è la migliore soluzione al mio problema!


Perché vuoi farlo in un ciclo? Le risposte La risposta di juba ti mostra come farlo in un solo passaggio. Perché renderlo più complicato?
Ista,

ovviamente uso l' selectargomento della subsetfunzione nel mio codice. volevo solo vedere come potevo accedere a colonne arbitrarie in un ciclo nel caso volessi fare qualcos'altro oltre a lasciar cadere la colonna. il set di dati originale ha circa 1200 var e sono interessato a usarne solo 4 senza sapere esattamente dove si trovano.
leroux,

2

Ecco un'altra soluzione che può essere utile per gli altri. Il codice seguente seleziona un numero limitato di righe e colonne da un set di dati di grandi dimensioni. Le colonne sono selezionate come in una delle risposte di juba, tranne per il fatto che utilizzo una funzione incolla per selezionare un set di colonne con nomi numerati in sequenza:

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


-1

Non riesco a rispondere alla tua domanda nei commenti a causa del punteggio di reputazione basso.

Il codice successivo ti darà un errore perché la funzione incolla restituisce una stringa di caratteri

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

Ecco una possibile soluzione:

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
}

o semplicemente fai:

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

-1
df = mtcars 
rimuovi vs e am perché sono categorici. Nel set di dati vs è nella colonna numero 8, am è nella colonna numero 9

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.