Soltar colunas do quadro de dados por nome

874

Eu tenho várias colunas que gostaria de remover de um quadro de dados. Eu sei que podemos excluí-los individualmente usando algo como:

df$x <- NULL

Mas eu esperava fazer isso com menos comandos.

Além disso, eu sei que eu poderia descartar colunas usando a indexação inteira como esta:

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

Mas estou preocupado que a posição relativa de minhas variáveis ​​possa mudar.

Dado o quão poderoso é o R, achei que poderia haver uma maneira melhor do que soltar cada coluna uma a uma.

Btibert3
fonte
13
Alguém pode me explicar por que R não tem algo simples como df#drop(var_name), e, em vez disso, precisamos fazer essas soluções complicadas?
usar o seguinte código
2
@ ifly6 A função 'subset ()' em R é tão parcimoniosa quanto a função 'drop ()' em Python, exceto que você não precisa especificar o argumento do eixo ... Concordo que é irritante que não possa ser apenas uma palavra-chave / sintaxe definitiva e fácil implementada em todos os aspectos para algo tão básico quanto soltar uma coluna.
Paul Sochacki

Respostas:

912

Você pode usar uma lista simples de nomes:

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, como alternativa, você pode fazer uma lista daqueles a serem mantidos e se referir a eles pelo nome:

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

EDIT: Para aqueles que ainda não estão familiarizados com o dropargumento da função de indexação, se desejar manter uma coluna como um quadro de dados, faça:

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

drop=TRUE(ou não mencioná-lo) eliminará dimensões desnecessárias e, portanto, retornará um vetor com os valores da coluna y.

Joris Meys
fonte
19
a função subconjunto funciona melhor, uma vez que não irá converter uma trama de dados com uma coluna num vector
mut1na
3
@ mut1na verifique o argumento drop = FALSE da função de indexação.
Joris Meys
4
Isso não deveria ser em DF[,keeps]vez de DF[keeps]?
Lindelof
8
@lindelof Não. Pode, mas é necessário adicionar drop = FALSE para impedir que R converta seu quadro de dados em um vetor se você selecionar apenas uma coluna. Não se esqueça que os quadros de dados são listas; portanto, a seleção de lista (unidimensional como eu) funciona perfeitamente e sempre retorna uma lista. Ou um quadro de dados nesse caso, e é por isso que prefiro usá-lo.
Joris Meys
7
@AjayOhri Sim, seria. Sem vírgula, você usa a maneira de "lista" de seleção, o que significa que, mesmo quando você extrai uma única coluna, ainda é retornado um quadro de dados. Se você usar a maneira "matriz", como deve estar, lembre-se de que, se selecionar apenas uma única coluna, obterá um vetor em vez de um quadro de dados. Para evitar isso, você precisa adicionar drop = FALSE. Como explicado na minha resposta, e no comentário logo acima seu ...
Joris Meys
453

Há também o subsetcomando, útil se você souber quais colunas deseja:

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

ATUALIZADO após o comentário de @hadley: Para eliminar as colunas a, c, você pode:

df <- subset(df, select = -c(a, c))
Prasad Chalasani
fonte
3
Eu realmente gostaria que a subsetfunção R tivesse uma opção como "allbut = FALSE", que "inverte" a seleção quando definida como TRUE, ou seja, mantém todas as colunas, exceto as da selectlista.
Prasad Chalasani
4
@prasad, veja @joris responda abaixo. Um subconjunto sem nenhum critério de subconjunto é um pouco exagerado. Tente simplesmente:df[c("a", "c")]
JD Long
@JD Eu sabia disso, mas eu gosto da conveniência sintática do subsetcomando, onde você não precisa colocar aspas nos nomes das colunas - acho que não me importo de digitar alguns caracteres extras apenas para evitar citar nomes :)
Prasad Chalasani
11
Observe que você não deve usar subsetdentro de outras funções.
Ari B. Friedman
196
within(df, rm(x))

é provavelmente mais fácil ou para várias variáveis:

within(df, rm(x, y))

Ou se você estiver lidando com data.tables (por Como você exclui uma coluna pelo nome em data.table? ):

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

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

ou para várias variáveis

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

dt[, !c("x", "y")]
Max Ghenis
fonte
26
within(df, rm(x))é de longe a solução mais limpa. Dado que essa é uma possibilidade, todas as outras respostas parecem desnecessariamente complicadas por uma ordem de magnitude.
Miles Erickson
2
Note que within(df, rm(x))irá não funciona se existem colunas duplicadas nomeados xno df.
22816 MichaelChirico
2
@MichaelChirico para esclarecer, ele remove nenhum, mas parece alterar os valores dos dados. Um tem problemas maiores, se este for o caso, mas aqui está um exemplo: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))retorna data.frame(x = 2, x = 2).
Max Ghenis
1
@MilesErickson O problema é que você depende de uma função within()poderosa, mas que também usa o NSE. A nota na página de ajuda indica claramente que, para a programação, deve-se tomar cuidado suficiente.
Joris Meys
@MilesErickson Com que frequência alguém encontra um quadro de dados com nomes duplicados?
precisa saber é o seguinte
115

Você poderia usar %in%assim:

df[, !(colnames(df) %in% c("x","bar","foo"))]
Joshua Ulrich
fonte
1
Estou faltando alguma coisa, ou essa é efetivamente a mesma solução que a primeira parte da resposta de Joris? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher
9
@ DanielFletcher: é o mesmo. Veja os registros de data e hora nas respostas. Respondemos ao mesmo tempo ... há 5 anos. :)
Joshua Ulrich
5
Noz. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher
54

list (NULL) também funciona:

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
fonte
1
Brilhante! Isso estende a atribuição NULL a uma única coluna de maneira natural e (aparentemente) evita a cópia (embora eu não saiba o que acontece sob o capô, portanto, pode não ser mais eficiente no uso da memória ... mas me parece claramente sintaticamente mais eficiente).
c-ouriço-do-
6
Você não precisa de lista (NULL), NULL é suficiente. por exemplo: dat [, 4] = NULL
CousinCocaine 07/07
8
A pergunta do OP era como excluir várias colunas. dat [, 4: 5] <- NULL não funcionará. É aí que entra a lista (NULL). Funciona para 1 ou mais colunas.
Vincent
Isso também não funciona ao tentar remover um nome de coluna duplicado.
22816 MichaelChirico
@MichaelChirico Funciona bem para mim. Dê um rótulo se desejar remover a primeira das colunas com o mesmo nome ou indique os índices para cada coluna que deseja remover. Se você tem um exemplo em que não funciona, eu estaria interessado em vê-lo. Talvez postá-lo como uma nova pergunta?
Vincent
42

Se você deseja remover as colunas por referência e evitar a cópia interna associada data.frames, poderá usar o data.tablepacote e a função:=

Você pode passar os nomes de vetores de caracteres para o lado esquerdo do :=operador e NULLcomo o 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 você quiser predefinir os nomes como vetor de caracteres fora da chamada [, coloque o nome do objeto em ()ou {}forçar o LHS a ser avaliado no escopo da chamada e não como um nome no escopo 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.   

Você também pode usar set, o que evita a sobrecarga de [.data.table, e também funciona para 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
fonte
41

Existe uma estratégia potencialmente mais poderosa baseada no fato de que grep () retornará um vetor numérico. Se você possui uma lista longa de variáveis ​​como eu em um dos meus conjuntos de dados, algumas variáveis ​​terminam em ".A" e outras terminam em ".B" e você deseja apenas as que terminam em ".A" (junto com todas as variáveis ​​que não correspondem a nenhum padrão, faça o seguinte:

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

Para o caso em questão, usando o exemplo de Joris Meys, pode não ser tão compacto, mas seria:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
IRTFM
fonte
1
Se definirmos drops, em primeiro lugar, como paste0("^", drop_cols, "$"), isso se torna muito mais agradável (leia-se: mais compacto) com sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico
30

Outra dplyrresposta. Se suas variáveis ​​tiverem uma estrutura de nomenclatura comum, você pode tentar starts_with(). Por exemplo

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 você deseja soltar uma sequência de variáveis ​​no quadro de dados, você pode usar :. Por exemplo, se você queria deixar var2, var3e todas as variáveis no meio, você tinha acabado de ser deixado com var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268
Pat W.
fonte
1
Para não esquecer todas as outras oportunidades que acompanham select(), como contains()or matches(), que também aceita regex.
ha_pu
23

Outra possibilidade:

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

ou

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
scentoni
fonte
2
Pena que isso não seja mais votado porque o uso de setdiffé o ideal, especialmente no caso de um número muito grande de colunas.
ctbrown
Outro ponto de vista é o seguinte: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

Resultado:

    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)

Resultado:

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

Solução Dplyr

Duvido que isso receba muita atenção aqui em baixo, mas se você tiver uma lista de colunas que deseja remover e quiser fazê-lo em uma dplyrcadeia, utilizo one_of()a selectcláusula:

Aqui está um exemplo simples e reproduzível:

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

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

A documentação pode ser encontrada em execução ?one_ofou aqui:

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

Usuário632716
fonte
22

Fora de interesse, isso sinaliza uma das inconsistências múltiplas e estranhas de sintaxe de R. Por exemplo, dado um quadro de dados de duas colunas:

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

Isso fornece um quadro de dados

subset(df, select=-y)

mas isso dá um vetor

df[,-2]

Isso tudo é explicado, ?[mas não é exatamente o comportamento esperado. Bem, pelo menos não para mim ...

jkeirstead
fonte
18

Aqui está uma dplyrmaneira de fazer isso:

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

Gosto disso porque é intuitivo ler e entender sem anotações e robusto para as colunas mudarem de posição dentro do quadro de dados. Também segue o idioma vetorizado usando -para remover elementos.

c.gutierrez
fonte
Adicionando a isso que (1) o usuário deseja substituir o original df (2), o magrittr possui um %<>% operador para substituir o objeto de entrada que pode ser simplificado paradf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek
1
Se você tiver uma longa lista de colunas para soltar, dplyrpode ser mais fácil agrupá-las e colocar apenas um menos:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar
14

Continuo pensando que deve haver um idioma melhor, mas, para subtração de colunas por nome, costumo fazer o seguinte:

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
fonte
4
Não é uma boa idéia negar a partida - #df[,-match(c("e","f"),names(df))]
hadley
. @ JDLong - E se eu quiser soltar a coluna onde o nome da coluna começa -?
Chetan Arvind Patil
12

Há uma função chamada dropNamed()no BBmiscpacote de Bernd Bischl que faz exatamente isso.

BBmisc::dropNamed(df, "x")

A vantagem é que evita repetir o argumento do quadro de dados e, portanto, é adequado para a inserção magrittr(assim como as dplyrabordagens):

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

Outra solução, se você não quiser usar o @ hadley's acima: Se "COLUMN_NAME" for o nome da coluna que você deseja excluir:

df[,-which(names(df) == "COLUMN_NAME")]
Nick Keramaris
fonte
1
(1) O problema é eliminar várias colunas ao mesmo tempo. (2) Não funcionará se COLUMN_NAMEnão estiver dentro df(verifique você mesmo df<-data.frame(a=1,b=2):). (3) df[,names(df) != "COLUMN_NAME"]é mais simples e não sofre (2)
Marek
Você pode dar mais informações sobre esta resposta?
Akash Nayak
8

Além das select(-one_of(drop_col_names))demonstradas nas respostas anteriores, existem algumas outras dplyropções para descartar as colunas select()que não envolvem a definição de todos os nomes de colunas específicos (usando os dados de exemplo do dplyr starwars para alguma variedade nos nomes das colunas):

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 você precisar soltar uma coluna que pode ou não existir no quadro de dados, aqui está uma ligeira reviravolta select_if()que, ao contrário de usar one_of(), não emitirá um Unknown columns:aviso se o nome da coluna não existir. Neste exemplo, 'bad_column' não é uma coluna no quadro de dados:

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

Forneça o quadro de dados e uma sequência de nomes separados por vírgula para remover:

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

Uso :

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

insira a descrição da imagem aqui

Cibernético
fonte
1

Encontre o índice das colunas que você deseja soltar usando which. Atribua um sinal negativo a esses índices ( *-1). Em seguida, subconfigure esses valores, que os removerão do quadro de dados. Isto é um exemplo.

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
milão
fonte
1

Se você tem um grande data.framee está com pouco uso de memória [ . . . . ou rmewithin para remover colunas de adata.frame , como subsetestá atualmente (R 3.6.2), usando mais memória - ao lado da dica do manual para usar subsetinterativamente .

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
fonte