Rilasciare le colonne del frame di dati per nome


874

Ho un numero di colonne che vorrei rimuovere da un frame di dati. So che possiamo eliminarli singolarmente usando qualcosa come:

df$x <- NULL

Ma speravo di farlo con meno comandi.

Inoltre, so che potrei eliminare le colonne usando l'indicizzazione di numeri interi in questo modo:

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

Ma sono preoccupato che la posizione relativa delle mie variabili possa cambiare.

Data la potenza della R, ho pensato che potrebbe esserci un modo migliore che far cadere ogni colonna una per una.


13
Qualcuno può spiegarmi perché R non ha qualcosa di semplice df#drop(var_name)e invece dobbiamo fare queste complicate soluzioni?
ifly6,

2
@ ifly6 La funzione 'subset ()' in R è parsimoniosa quanto la funzione 'drop ()' in Python, tranne per il fatto che non è necessario specificare l'argomento dell'asse ... Sono d'accordo che è fastidioso che non ci sia essere solo una, ultima, semplice parola chiave / sintassi implementata su tutta la linea per qualcosa di così semplice come far cadere una colonna.
Paul Sochacki,

Risposte:


912

Puoi usare un semplice elenco di nomi:

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

Oppure, in alternativa, puoi creare un elenco di quelli da conservare e fare riferimento ad essi per nome:

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

EDIT: per coloro che non hanno ancora familiarità con l' dropargomento della funzione di indicizzazione, se si desidera mantenere una colonna come frame di dati, è necessario:

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

drop=TRUE(o non menzionarlo) eliminerà le dimensioni non necessarie e quindi restituirà un vettore con i valori di colonna y.


19
la funzione del sottoinsieme funziona meglio in quanto non converte un frame di dati con una colonna in un vettore
mut1na,

3
@ mut1na controlla l'argomento drop = FALSE della funzione di indicizzazione.
Joris Meys,

4
Non dovrebbe essere DF[,keeps]invece di DF[keeps]?
Lindelof

8
@lindelof No. Può, ma poi devi aggiungere drop = FALSE per impedire a R di convertire il tuo frame di dati in un vettore se selezioni solo una singola colonna. Non dimenticare che i frame di dati sono elenchi, quindi la selezione di elenchi (unidimensionale come ho fatto io) funziona perfettamente e restituisce sempre un elenco. O un frame di dati in questo caso, motivo per cui preferisco usarlo.
Joris Meys,

7
@AjayOhri Sì, lo sarebbe. Senza una virgola, si utilizza il modo di "elenco" per selezionare, il che significa che anche quando si estrae una singola colonna, viene comunque restituito un frame di dati. Se si utilizza il modo "matrice", come si fa, è necessario tenere presente che se si seleziona solo una singola colonna, si ottiene un vettore anziché un frame di dati. Per evitarlo, devi aggiungere drop = FALSE. Come spiegato nella mia risposta e nel commento proprio sopra il tuo ...
Joris Meys

453

C'è anche il subsetcomando, utile se sai quali colonne vuoi:

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

AGGIORNATO dopo il commento di @hadley: per eliminare le colonne a, c potresti fare:

df <- subset(df, select = -c(a, c))

3
Vorrei davvero che la subsetfunzione R avesse un'opzione come "allbut = FALSE", che "inverte" la selezione quando è impostata su TRUE, ovvero mantiene tutte le colonne tranne quelle selectnell'elenco.
Prasad Chalasani,

4
@prasad, vedi la risposta @joris di seguito. Un sottoinsieme senza alcun criterio di sottoinsieme è un po 'eccessivo. Prova semplicemente:df[c("a", "c")]
JD Long

@JD Lo sapevo, ma mi piace la comodità sintattica del subsetcomando in cui non è necessario inserire virgolette attorno ai nomi delle colonne - immagino che non mi dispiaccia digitare alcuni caratteri extra solo per evitare di citare i nomi :)
Prasad Chalasani,

11
Nota che non dovresti usare subsetall'interno di altre funzioni.
Ari B. Friedman,


196
within(df, rm(x))

è probabilmente il più semplice o per più variabili:

within(df, rm(x, y))

O se hai a che fare con data.tables (per Come si elimina una colonna per nome in data.table? ):

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

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

o per più variabili

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

dt[, !c("x", "y")]

26
within(df, rm(x))è di gran lunga la soluzione più pulita. Dato che questa è una possibilità, ogni altra risposta sembra inutilmente complicata da un ordine di grandezza.
Miles Erickson,

2
Si noti che within(df, rm(x))sarà non funziona se ci sono colonne duplicati di nome xin df.
MichaelChirico,

2
@MichaelChirico per chiarire, non rimuove nessuno dei due ma sembra cambiare i valori dei dati. Uno ha problemi più grandi se questo è il caso, ma ecco un esempio: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))ritorna data.frame(x = 2, x = 2).
Max Ghenis,

1
@MilesErickson Il problema è che ti affidi a una funzione within()che è potente ma utilizza anche NSE. La nota nella pagina di aiuto afferma chiaramente che per la programmazione è necessario usare cure sufficienti.
Joris Meys,

@MilesErickson Ogni quanto tempo si incontra un frame di dati con nomi duplicati?
HSchmale

115

Puoi usare %in%così:

df[, !(colnames(df) %in% c("x","bar","foo"))]

1
Mi sto perdendo qualcosa o questa è effettivamente la stessa soluzione della prima parte della risposta di Joris? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher,

9
@DanielFletcher: è lo stesso. Guarda i timestamp sulle risposte. Abbiamo risposto allo stesso tempo ... 5 anni fa. :)
Joshua Ulrich,

5
Nutty. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher,

54

list (NULL) funziona anche:

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"

1
Brillante! Ciò estende l'assegnazione NULL a una singola colonna in modo naturale e (apparentemente) evita la copia (anche se non so cosa succede sotto il cofano, quindi potrebbe non essere più efficiente nell'uso della memoria ... ma mi sembra chiaramente sintatticamente più efficiente.)
c-urchin

6
Non è necessario l'elenco (NULL), NULL è sufficiente. ad esempio: dat [, 4] = NULL
CousinCocaine

8
La domanda di OP era come eliminare più colonne. dat [, 4: 5] <- NULL non funzionerà. È qui che entra in elenco (NULL). Funziona per 1 o più colonne.
Vincent,

Anche questo non funziona quando si tenta di rimuovere un nome di colonna duplicato.
MichaelChirico,

@MichaelChirico Funziona bene per me. O dare un'etichetta se si desidera rimuovere la prima delle colonne con lo stesso nome o fornire indici per ogni colonna che si desidera rimuovere. Se hai un esempio in cui non funziona sarei interessato a vederlo. Forse pubblicarlo come una nuova domanda?
Vincent,

42

Se si desidera rimuovere le colonne per riferimento ed evitare la copia interna associata, data.framesè possibile utilizzare il data.tablepacchetto e la funzione:=

È possibile passare i nomi di un vettore di caratteri sul lato sinistro :=dell'operatore e NULLcome 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]

Se si desidera predefinire i nomi come vettore di caratteri all'esterno della chiamata [, inserire il nome dell'oggetto in ()o {}forzare la valutazione di LHS nell'ambito di chiamata non come un nome nell'ambito di 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.   

Puoi anche usare set, il che evita il sovraccarico di [.data.table, e funziona anche per 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)

41

Esiste una strategia potenzialmente più potente basata sul fatto che grep () restituirà un vettore numerico. Se hai una lunga lista di variabili come faccio in uno dei miei set di dati, alcune variabili che finiscono in ".A" e altre che finiscono in ".B" e vuoi solo quelle che finiscono in ".A" (insieme con tutte le variabili che non corrispondono a nessuno dei due pattern, procedere come segue:

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

Per il caso in esame, usando l'esempio di Joris Meys, potrebbe non essere così compatto, ma sarebbe:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]

1
Se definiamo dropsin primo luogo come paste0("^", drop_cols, "$"), questo diventa molto più bello (leggi: più compatto) con sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico

30

Un'altra dplyrrisposta Se le tue variabili hanno una struttura di denominazione comune, potresti provare starts_with(). Per esempio

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

Se si desidera eliminare una sequenza di variabili nel frame di dati, è possibile utilizzare :. Ad esempio, se si desidera eliminare var2, var3e tutte le variabili tra, rimarrebbe solo con var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268

1
Da non dimenticare tutte le altre opportunità che ne derivano select(), come contains()o matches(), che accetta anche regex.
ha_pu

23

Un'altra possibilità:

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

o

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]

2
Peccato che questo non sia più votato perché l'uso di setdiffè ottimale soprattutto nel caso di un numero molto elevato di colonne.
ctbrown,

Un'altra prospettiva su questo: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

Produzione:

    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)

Produzione:

        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

23

Soluzione Dplyr

Dubito che questo attirerà molta attenzione quaggiù, ma se hai un elenco di colonne che vuoi rimuovere e vuoi farlo in una dplyrcatena che uso one_of()nellaselect clausola:

Ecco un esempio semplice e riproducibile:

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

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

La documentazione può essere trovata eseguendo ?one_ofo qui:

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


22

Per interesse, questo segnala una delle strane incoerenze della sintassi multipla di R. Ad esempio, dato un frame di dati a due colonne:

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

Questo dà un frame di dati

subset(df, select=-y)

ma questo dà un vettore

df[,-2]

Tutto questo è spiegato ?[ma non è esattamente il comportamento previsto. Beh, almeno non per me ...


18

Ecco un dplyrmodo per farlo:

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

Mi piace perché è intuitivo da leggere e comprendere senza annotazioni e robusto per le colonne che cambiano posizione all'interno del frame di dati. Segue anche l'idioma vettorializzato usando -per rimuovere elementi.


Aggiungendo a ciò che (1) l'utente desidera sostituire l'originale df (2) magrittr ha un %<>% operatore per sostituire l'oggetto di input che potrebbe essere semplificatodf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek,

1
Se hai un lungo elenco di colonne da eliminare, con dplyr, potrebbe essere più semplice raggrupparle e metterne solo uno meno:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar

14

Continuo a pensare che ci debba essere un linguaggio migliore, ma per sottrarre le colonne per nome, tendo a fare quanto segue:

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

4
Non è una buona idea negare la partita -df[,-match(c("e","f"),names(df))]
Hadley,

. @ JDLong - Cosa succede se desidero rilasciare la colonna da cui inizia il nome della colonna -?
Chetan Arvind Patil,

12

C'è una funzione chiamata dropNamed()nel BBmiscpacchetto di Bernd Bischl che fa esattamente questo.

BBmisc::dropNamed(df, "x")

Il vantaggio è che evita di ripetere l'argomento del frame di dati ed è quindi adatto per il piping magrittr(proprio come gli dplyrapprocci):

df %>% BBmisc::dropNamed("x")

9

Un'altra soluzione se non si desidera utilizzare @ hadley sopra: Se "COLUMN_NAME" è il nome della colonna che si desidera eliminare:

df[,-which(names(df) == "COLUMN_NAME")]

1
(1) Il problema è eliminare più colonne contemporaneamente. (2) Non funzionerà se COLUMN_NAMEnon è in df(controlla te stesso:) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]è più semplice e non soffrire di (2)
Marek,

Puoi dare qualche informazione in più su questa risposta?
Akash Nayak,

8

Oltre a quello select(-one_of(drop_col_names))dimostrato nelle risposte precedenti, ci sono un paio di altre dplyropzioni per eliminare le colonne usando select()che non implicano la definizione di tutti i nomi specifici delle colonne (usando i dati di esempio dplyr starwars per una certa varietà nei nomi delle colonne):

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 

Se è necessario eliminare una colonna che potrebbe o non potrebbe esistere nel frame di dati, ecco una leggera modifica usando select_if()che a differenza dell'uso one_of()non genererà un Unknown columns:avviso se il nome della colonna non esiste. In questo esempio 'bad_column' non è una colonna nel frame di dati:

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

4

Fornire il frame di dati e una stringa di nomi separati da virgola per rimuovere:

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

Utilizzo :

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

inserisci qui la descrizione dell'immagine


1

Trova l'indice delle colonne che desideri eliminare utilizzando which. Dai a questi indici un segno negativo ( *-1). Quindi sottoinsieme di quei valori, che li rimuoveranno dal frame di dati. Questo è un esempio

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

1

Se hai una data.framememoria grande e hai poca memoria [ . . . .oppure rmewithin per rimuovere le colonne di adata.frame , come subsetè attualmente (R 3.6.2) usando più memoria, oltre al suggerimento del manuale da usare in modo subsetinterattivo .

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