Cómo soltar columnas por nombre en un marco de datos

304

Tengo un gran conjunto de datos y me gustaría leer columnas específicas o descartar todas las demás.

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

Selecciono las columnas que no me interesan:

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

y de lo que me gustaría hacer algo como:

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

para soltar todas las columnas no deseadas. ¿Es esta la solución óptima?

leroux
fuente
1
durmiendo sobre el problema, estaba pensando que eso subset(data, select=c(...))ayuda en mi caso por dejar caer vars. Sin embargo, la pregunta era principalmente sobre la paste("data$",var.out[i],sep="")parte para acceder a las columnas de interés dentro del ciclo. ¿Cómo puedo pegar o de alguna manera componer el nombre de una columna? Gracias a todos por su atención y su ayuda
leroux
77
Posible duplicado de columnas Drop en el marco de datos R
jangorecki

Respuestas:

380

Debe usar la indexación o la subsetfunción. Por ejemplo :

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

Luego puede usar la whichfunción y el -operador en la indexación de columna:

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, mucho más simple, use el selectargumento de la subsetfunción: luego puede usar el -operador directamente en un vector de nombres de columna, ¡e incluso puede omitir las comillas alrededor de los nombres!

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

Tenga en cuenta que también puede seleccionar las columnas que desee en lugar de soltar las otras:

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
fuente
2
¡El selectargumento de la subsetfunción hizo el trabajo perfectamente! Gracias juba!
leroux
2
whichno es necesario, ver la respuesta de Ista. ¡Pero el subconjunto con -es bueno! ¡No lo sabía!
TMS
55
subsetse ve bien, pero la forma en que elimina silenciosamente los valores perdidos me parece bastante peligroso.
static_rtti
2
subsetde hecho es muy conveniente, pero recuerde evitar usarlo a menos que esté usando R interactivamente. Consulte la Advertencia en la documentación de la función y esta pregunta SO para obtener más información.
Waldir Leoncio
44
"¡incluso puede omitir las comillas alrededor de los nombres!", en realidad debe omitir las comillas, de lo contrario obtendrá un argumento no válido para el operador unario. Si tiene ciertos caracteres (por ejemplo, "-") en sus nombres, no puede utilizar este método en absoluto, ya que si elimina las comillas, R no podrá analizar su código correctamente.
oh54
122

No lo use -which()para esto, es extremadamente peligroso. 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...

En su lugar, use el subconjunto o la !función:

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

He aprendido esto por experiencia dolorosa. ¡No lo uses en exceso which()!

Ista
fuente
31
setdifftambién es útil:setdiff(names(dat), c("foo", "bar"))
hadley
La setdiffpropuesta de @hadley es muy buena para largas listas de nombres.
JASC
48

Primero , puede usar la indexación directa (con vectores booleanos) en lugar de volver a acceder a los nombres de columna si está trabajando con el mismo marco de datos; será más seguro según lo indicado por Ista, y más rápido escribir y ejecutar. Entonces, lo que solo necesitará es:

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

y luego, simplemente reasignar datos:

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

En segundo lugar , más rápido de escribir, puede asignar directamente NULL a las columnas que desea eliminar:

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

Finalmente , puede usar el subconjunto (), pero en realidad no se puede usar en el código (incluso el archivo de ayuda lo advierte). Específicamente, un problema para mí es que si desea utilizar directamente la función de caída de susbset (), debe escribir sin comillas la expresión correspondiente a los nombres de columna:

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 beneficio adicional , aquí hay un pequeño punto de referencia de las diferentes opciones, que muestra claramente que el subconjunto es el más lento y que el primer método de reasignación es el más 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 de Microbench

El código está abajo:

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
fuente
2
Me gusta usar tu segunda alternativa NULL, pero ¿por qué cuando pones más de dos nombres es necesario asignarla list(NULL)? Solo tengo curiosidad por saber cómo funciona, porque lo intenté con un solo nombre y no necesitolist()
PC Darwin
3
@DarwinPC Sí. Si accede directamente a un elemento vectorial (con $o [[), el uso en <- list(NULL)realidad conducirá a resultados incorrectos. Si accede a un subconjunto del marco de datos con una o varias columnas, <- list(NULL)es el camino a seguir, incluso si no es necesario para un marco de datos de una columna (porque df['myColumns']se convertirá en un vector si es necesario).
Antoine Lizée
27

También puedes probar el dplyrpaquete:

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
fuente
44
El uso dplyr::select(df2, -one_of(c('x','y')))seguirá funcionando (con una advertencia) incluso si algunas de las columnas con nombre no existen
divibisan
13

Aquí hay una solución rápida para esto. Digamos que tiene un marco de datos X con tres columnas A, B y 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

Si quiero eliminar una columna, digamos B, solo use grep en colnames para obtener el índice de columna, que luego puede usar para omitir la columna.

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

Su nuevo marco de datos X se vería así (esta vez sin la columna B):

> X
  A C
1 1 5
2 2 6

La belleza de grep es que puede especificar varias columnas que coincidan con la expresión regular. Si tuviera X con cinco columnas (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

Saque las columnas B y D:

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

EDITAR: Teniendo en cuenta la sugerencia grepl de Matthew Lundberg en los comentarios a continuación:

> 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

Si trato de soltar una columna que no existe, no debería pasar nada:

> X<-X[,!grepl("G",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10
Joben R. Ilagan
fuente
3
X[,-grep("B",colnames(X))]no devolverá ninguna columna en el caso de que no contenga ningún nombre de columna B, en lugar de devolver todas las columnas como se desearía. Considere con X <- irispor ejemplo. Este es el problema con el uso de índices negativos con valores calculados. Considere en su grepllugar.
Matthew Lundberg
6

Intenté eliminar una columna mientras usaba el paquete data.tabley obtuve un resultado inesperado. Creo que vale la pena publicar lo siguiente. Solo una pequeña nota de advertencia.

[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

Básicamente, la sintaxis para data.tableNO es exactamente la misma que data.frame. De hecho, hay muchas diferencias, consulte las preguntas frecuentes 1.1 y 2.17. ¡Usted ha sido advertido!

Mark Miller
fuente
1
O puede utilizar DT[,var.out := NULL]para eliminar las columnas que desea hacerlo.
mnel
El subconjunto (x, seleccionar = ...) funciona el método para tanto data.framey data.tableclases
momeara
3

Cambié el código a:

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

De todos modos, ¡la respuesta de juba es la mejor solución para mi problema!

leroux
fuente
¿Por qué quieres hacer esto en un bucle? Las respuestas La respuesta de juba te muestra cómo hacerlo en un solo paso. ¿Por qué hacerlo más complicado?
Ista
Por supuesto, uso el selectargumento de la subsetfunción en mi código. Solo quería ver cómo podía acceder a columnas arbitrarias en un bucle en caso de que quisiera hacer algo más que simplemente soltar la columna. el conjunto de datos original tiene aproximadamente 1200 vars y solo estoy interesado en usar 4 de ellos sin saber exactamente dónde están.
leroux
2

Aquí hay otra solución que puede ser útil para otros. El siguiente código selecciona un pequeño número de filas y columnas de un gran conjunto de datos. Las columnas se seleccionan como en una de las respuestas de juba, excepto que uso una función de pegar para seleccionar un conjunto de columnas con nombres numerados secuencialmente:

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

No puedo responder a su pregunta en los comentarios debido a la baja puntuación de reputación.

El siguiente código le dará un error porque la función pegar devuelve una cadena de caracteres

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

Aquí hay una posible solución:

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 simplemente hacer:

for(i in 1:length(var.out)) {
  data[var.out[i]] <- NULL
}
Andriy T.
fuente
-1
df = mtcars 
eliminar vs y am porque son categóricos. En el conjunto de datos vs está en la columna número 8, am está en la columna número 9

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

Abhilash Ponnam
fuente