Como soltar colunas por nome em um quadro de dados


304

Eu tenho um grande conjunto de dados e gostaria de ler colunas específicas ou descartar todas as outras.

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

Eu seleciono as colunas nas quais não estou interessado:

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

e do que eu gostaria de fazer algo como:

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

para soltar todas as colunas indesejadas. Essa é a solução ideal?


1
dormindo sobre o problema, eu estava pensando que subset(data, select=c(...))ajuda no meu caso para deixar cair vars. a questão, porém, era principalmente sobre a paste("data$",var.out[i],sep="")parte para acessar colunas de interesse dentro do loop. como colar ou de alguma forma compor um nome de coluna? Obrigado a todos por sua atenção e sua ajuda
leroux

Respostas:


380

Você deve usar a indexação ou a subsetfunção Por exemplo :

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

Então você pode usar a whichfunção e o -operador na indexação de colunas:

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

Ou, muito mais simples, use o selectargumento da subsetfunção: você pode usar o -operador diretamente em um vetor de nomes de colunas e até omitir as aspas ao redor dos nomes!

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

Observe que você também pode selecionar as colunas que deseja, em vez de soltar as outras:

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
o selectargumento da subsetfunção fez o trabalho perfeitamente! Obrigado juba!
leroux

2
whichnão é necessário, veja a resposta de Ista. Mas o subconjunto com -é bom! Não sabia disso!
TMS

5
subsetparece bom, mas a maneira como silenciosamente descarta os valores ausentes parece bastante perigosa para mim.
static_rtti

2
subseté realmente muito conveniente, mas lembre-se de evitar usá-lo, a menos que você esteja usando R interativamente. Consulte o aviso na documentação da função e esta pergunta SO para obter mais.
Waldir Leoncio 13/08

4
"você pode até omitir as aspas em torno dos nomes!", você realmente precisa omitir as aspas, caso contrário, obterá um argumento inválido para o operador unário. Se você possui certos caracteres (por exemplo, "-") em seus nomes, não pode usar esse método, pois a exclusão de aspas fará com que R não consiga analisar corretamente seu código.
oh54

122

Não use -which()para isso, é extremamente perigoso. Considerar:

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

Em vez disso, use subconjunto ou a !função:

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

Eu aprendi isso com uma experiência dolorosa. Não use demais which()!


31
setdifftambém é útil:setdiff(names(dat), c("foo", "bar"))
hadley

A setdiffproposta de @hadley é muito boa para longas listas de nomes.
JASC

48

Primeiro , você pode usar a indexação direta (com vetores booleanos) em vez de acessar novamente os nomes das colunas se estiver trabalhando com o mesmo quadro de dados; será mais seguro, como indicado por Ista, e mais rápido para escrever e executar. Então, o que você só precisará é:

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

e, em seguida, basta reatribuir dados:

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

Segundo , mais rápido de escrever, você pode atribuir NULL diretamente às colunas que deseja remover:

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

Por fim , você pode usar o subconjunto (), mas ele não pode realmente ser usado no código (mesmo o arquivo de ajuda avisa). Especificamente, um problema para mim é que, se você quiser usar diretamente o recurso drop de susbset (), precisará escrever sem aspas a expressão correspondente aos nomes das colunas:

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

Como bônus , aqui está uma pequena referência das diferentes opções, que mostra claramente que o subconjunto é o mais lento e que o primeiro método de reatribuição é o mais rápido:

                                        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

Gráfico Microbench

O código está abaixo:

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
Eu gosto da sua segunda alternativa usando NULL, mas por que quando você coloca mais de dois nomes é necessário atribuí-la list(NULL)? Eu só estou curioso para saber como ele funciona, porque eu tentei com apenas um nome e eu não precisolist()
Darwin PC

3
@DarwinPC Sim. Se você acessar diretamente um elemento vetorial (com $ou [[), o uso <- list(NULL)levará a resultados incorretos. Se você acessar um subconjunto do quadro de dados com uma ou várias colunas, <- list(NULL)é o caminho a seguir, mesmo que não seja necessário para um quadro de dados de uma coluna (porque df['myColumns']será convertido em um vetor, se necessário).
Antoine Lizée

27

Você também pode experimentar o dplyrpacote:

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
Usando dplyr::select(df2, -one_of(c('x','y')))irá ainda trabalho (com um aviso), mesmo que algumas das colunas nomeadas não existem
divibisan

13

Aqui está uma solução rápida para isso. Digamos que você tenha um quadro de dados X com três colunas 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 eu quiser remover uma coluna, digamos B, basta usar grep em colnames para obter o índice da coluna, que você poderá usar para omitir a coluna.

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

Seu novo quadro de dados X se pareceria com o seguinte (desta vez sem a coluna B):

> X
  A C
1 1 5
2 2 6

A beleza do grep é que você pode especificar várias colunas que correspondem à expressão regular. Se eu tivesse X com cinco colunas (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

Retire as colunas B e D:

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

EDIT: Considerando a sugestão grepl de Matthew Lundberg nos comentários abaixo:

> 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 eu tentar soltar uma coluna que não existe, nada deve acontecer:

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

3
X[,-grep("B",colnames(X))]não retornará colunas no caso em que nenhum nome da coluna contenha B, em vez de retornar todas as colunas conforme desejado. Considere com X <- irisum exemplo. Esse é o problema do uso de índices negativos com valores calculados. Considere em greplvez disso.
Matthew Lundberg

6

Tentei excluir uma coluna enquanto usava o pacote data.table e obtive um resultado inesperado. Eu acho que vale a pena postar o seguinte. Apenas uma pequena nota de advertência.

[Editado por 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

Basicamente, a sintaxe para data.tableNÃO é exatamente a mesma que data.frame. De fato, existem muitas diferenças, consulte a FAQ 1.1 e a FAQ 2.17. Você foi avisado!


1
Ou você pode usar DT[,var.out := NULL]para excluir as colunas que deseja fazer.
mnel

O subconjunto (x, selecione = ...) método funciona para ambos data.framee data.tableas classes
momeara

3

Eu mudei o código para:

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

Enfim, a resposta da juba é a melhor solução para o meu problema!


Por que você quer fazer isso em um loop? As respostas A resposta de juba mostra como fazê-lo em uma única etapa. Por que torná-lo mais complicado?
Ista

é claro que eu uso o selectargumento da subsetfunção no meu código. Eu só queria ver como eu poderia acessar colunas arbitrárias em um loop, no caso de eu querer fazer algo mais do que apenas soltar a coluna. o conjunto de dados original tem cerca de 1200 vars e só estou interessado em usar 4 deles sem saber exatamente onde estão.
leroux

2

Aqui está outra solução que pode ser útil para outras pessoas. O código abaixo seleciona um pequeno número de linhas e colunas de um grande conjunto de dados. As colunas são selecionadas como em uma das respostas de juba, exceto pelo uso da função colar para selecionar um conjunto de colunas com nomes numerados sequencialmente:

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

Não posso responder à sua pergunta nos comentários devido à baixa pontuação na reputação.

O próximo código apresentará um erro porque a função colar retorna uma sequência de caracteres

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

Aqui está uma solução possível:

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
}

ou apenas faça:

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

-1
df = mtcars 
remova vs e sou porque são categóricos. No conjunto de dados vs está na coluna número 8, am está na coluna número 9

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.