Suelte las columnas del marco de datos por nombre

874

Tengo varias columnas que me gustaría eliminar de un marco de datos. Sé que podemos eliminarlos individualmente usando algo como:

df$x <- NULL

Pero esperaba hacer esto con menos comandos.

Además, sé que podría soltar columnas usando una indexación de enteros como esta:

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

Pero me preocupa que la posición relativa de mis variables pueda cambiar.

Dado lo poderoso que es R, pensé que podría haber una mejor manera que soltar cada columna una por una.

Btibert3
fuente
13
¿Puede alguien explicarme por qué R no tiene algo simple como df#drop(var_name), y en su lugar, tenemos que hacer estas soluciones complicadas?
ifly6
2
@ ifly6 La función 'subconjunto ()' en R es tan parsimoniosa como la función 'soltar ()' en Python, excepto que no necesita especificar el argumento del eje ... Estoy de acuerdo en que es molesto que no pueda ser solo una palabra clave / sintaxis fácil y definitiva implementada en todos los ámbitos para algo tan básico como soltar una columna.
Paul Sochacki

Respuestas:

912

Puede usar una lista simple de nombres:

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

O, alternativamente, puede hacer una lista de los que desea conservar y hacer referencia a ellos por su nombre:

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

EDITAR: Para aquellos que aún no están familiarizados con el dropargumento de la función de indexación, si desea mantener una columna como marco de datos, debe:

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

drop=TRUE(o no mencionarlo) eliminará dimensiones innecesarias y, por lo tanto, devolverá un vector con los valores de la columna y.

Joris Meys
fuente
19
la función de subconjunto funciona mejor ya que no convertirá un marco de datos con una columna en un vector
mut1na
3
@ mut1na verifique el argumento drop = FALSE de la función de indexación.
Joris Meys
44
¿No debería ser eso en DF[,keeps]lugar de DF[keeps]?
lindelof
8
@lindelof No. Puede, pero luego debe agregar drop = FALSE para evitar que R convierta su marco de datos en un vector si solo selecciona una sola columna. No olvide que los marcos de datos son listas, por lo que la selección de listas (unidimensional como lo hice) funciona perfectamente y siempre devuelve una lista. O un marco de datos en este caso, por eso prefiero usarlo.
Joris Meys
77
@AjayOhri Sí, lo haría. Sin una coma, utiliza la forma de "lista" de selección, lo que significa que incluso cuando extrae una sola columna, aún obtiene un marco de datos devuelto. Si utiliza la forma de "matriz", como lo hace, debe tener en cuenta que si solo selecciona una sola columna, obtendrá un vector en lugar de un marco de datos. Para evitar eso, debe agregar drop = FALSE. Como se explica en mi respuesta, y en el comentario justo encima de los suyos ...
Joris Meys
453

También está el subsetcomando, útil si sabe qué columnas desea:

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

ACTUALIZADO después del comentario de @hadley: Para colocar las columnas a, c, podría hacer:

df <- subset(df, select = -c(a, c))
Prasad Chalasani
fuente
3
Realmente desearía que la subsetfunción R tuviera una opción como "allbut = FALSE", que "invierte" la selección cuando se establece en TRUE, es decir, conserva todas las columnas excepto las de la selectlista.
Prasad Chalasani
44
@prasad, mira la respuesta de @joris a continuación. Un subconjunto sin ningún criterio de subconjunto es un poco exagerado. Pruebe simplemente:df[c("a", "c")]
JD Long
@JD sabía que, pero me gusta la comodidad sintáctico de la subsetorden en el que no hay que poner comillas alrededor de los nombres de columna - supongo que no me importa escribir algunos caracteres adicionales sólo para evitar citar nombres :)
Prasad Chalasani
11
Tenga en cuenta que no debe usar subsetdentro de otras funciones.
Ari B. Friedman
196
within(df, rm(x))

es probablemente la más fácil, o para múltiples variables:

within(df, rm(x, y))

O si está tratando con data.tables (según ¿Cómo elimina una columna por nombre en data.table? ):

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

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

o para múltiples variables

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

dt[, !c("x", "y")]
Max Ghenis
fuente
26
within(df, rm(x))Es, con mucho, la solución más limpia. Dado que esta es una posibilidad, cualquier otra respuesta parece innecesariamente complicada por un orden de magnitud.
Miles Erickson el
2
Tenga en cuenta que within(df, rm(x))se no funciona si hay columnas duplicadas nombradas xen df.
MichaelChirico
2
@MichaelChirico para aclarar, no elimina ninguno pero parece cambiar los valores de los datos. Uno tiene mayores problemas si este es el caso, pero aquí hay un ejemplo: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))devoluciones data.frame(x = 2, x = 2).
Max Ghenis
1
@MilesErickson El problema es que confía en una función within()que es poderosa pero que también usa NSE. La nota en la página de ayuda establece claramente que para la programación se debe tener cuidado suficiente.
Joris Meys
@MilesErickson ¿Con qué frecuencia se encontraría un marco de datos con nombres duplicados?
HSchmale
115

Podrías usar %in%así:

df[, !(colnames(df) %in% c("x","bar","foo"))]
Joshua Ulrich
fuente
1
¿Me estoy perdiendo algo o esta es efectivamente la misma solución que la primera parte de la respuesta de Joris? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher
99
@DanielFletcher: es lo mismo. Mira las marcas de tiempo en las respuestas. Respondimos al mismo tiempo ... hace 5 años. :)
Joshua Ulrich
55
De nuez. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher
54

list (NULL) también 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
fuente
1
¡Brillante! Esto extiende la asignación NULL a una sola columna de forma natural y (aparentemente) evita la copia (aunque no sé qué sucede debajo del capó, por lo que puede no ser más eficiente en el uso de la memoria ... pero me parece claramente más eficiente sintácticamente.)
c-urchin
66
No necesita la lista (NULL), NULL es suficiente. por ejemplo: dat [, 4] = NULL
CousinCocaine
8
La pregunta de OP era cómo eliminar varias columnas. dat [, 4: 5] <- NULL no funcionará. Ahí es donde entra la lista (NULL). Funciona para 1 o más columnas.
Vincent
Esto tampoco funciona cuando se trata de eliminar un nombre de columna duplicado.
MichaelChirico
@MichaelChirico funciona bien para mí. O bien, proporcione una etiqueta si desea eliminar la primera de las columnas con el mismo nombre o proporcione índices para cada columna que desee eliminar. Si tiene un ejemplo donde no funciona, me interesaría verlo. Quizás publicarlo como una nueva pregunta?
Vincent
42

Si desea eliminar las columnas por referencia y evitar la copia interna asociada con data.frames , puede usar el data.tablepaquete y la función:=

Puede pasar los nombres de un vector de caracteres al lado izquierdo del := operador, y NULLcomo el 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]

Si desea predefinir los nombres como vector de caracteres fuera de la llamada a [, ajuste el nombre del objeto en ()o{} forzar la evaluación del LHS en el alcance de la llamada, no como un nombre dentro del alcance 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.   

También puede usar set, lo que evita la sobrecarga de [.data.table, y también 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
fuente
41

Existe una estrategia potencialmente más poderosa basada en el hecho de que grep () devolverá un vector numérico. Si tiene una larga lista de variables como yo en uno de mi conjunto de datos, algunas variables que terminan en ".A" y otras que terminan en ".B" y solo desea las que terminan en ".A" (junto con todas las variables que no coinciden con ninguno de los patrones, haga esto:

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

Para el caso en cuestión, utilizando el ejemplo de Joris Meys, podría no ser tan compacto, pero sería:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
IRTFM
fuente
1
Si definimos dropsen primer lugar como paste0("^", drop_cols, "$"), esto se vuelve mucho más agradable (leer: más compacto) con sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico
30

Otra dplyrrespuesta Si sus variables tienen alguna estructura de nombres común, puede intentarlo starts_with(). Por ejemplo

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

Si desea soltar una secuencia de variables en el marco de datos, puede usar :. Por ejemplo, si quisiera eliminar var2, var3y todas las variables intermedias, simplemente le quedaría var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268
Pat W.
fuente
1
Sin olvidar todas las otras oportunidades que vienen select(), como contains()o matches(), que también acepta expresiones regulares.
ha_pu
23

Otra posibilidad:

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

o

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
Scentoni
fuente
2
Lástima que esto no se vota más porque el uso de setdiffes el óptimo, especialmente en el caso de una gran cantidad de columnas.
ctbrown
Otro ángulo sobre esto: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

Salida:

    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)

Salida:

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

Solución Dplyr

Dudo que esto reciba mucha atención aquí, pero si tiene una lista de columnas que desea eliminar y desea hacerlo en una dplyrcadena que uso one_of()en la selectcláusula:

Aquí hay un ejemplo simple y reproducible:

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

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

La documentación se puede encontrar ejecutando ?one_ofo aquí:

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

Usuario632716
fuente
22

Por interés, esto señala una de las extrañas inconsistencias de sintaxis múltiple de R. Por ejemplo, dado un marco de datos de dos columnas:

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

Esto da un marco de datos

subset(df, select=-y)

pero esto da un vector

df[,-2]

Todo esto se explica ?[pero no es exactamente el comportamiento esperado. Bueno, al menos no para mí ...

jkeirstead
fuente
18

Aquí hay una dplyrmanera de hacerlo:

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

Me gusta esto porque es intuitivo para leer y comprender sin anotaciones y robusto para que las columnas cambien de posición dentro del marco de datos. También sigue el idioma vectorizado que se usa -para eliminar elementos.

c.gutierrez
fuente
Agregando a esto que (1) el usuario quiere reemplazar df original (2) magrittr tiene %<>% operador para reemplazar el objeto de entrada que podría simplificarsedf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek
1
Si tiene una larga lista de columnas para soltar, dplyrpodría ser más fácil agruparlas y poner solo un menos:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar
14

Sigo pensando que debe haber un idioma mejor, pero para restar columnas por nombre, tiendo a hacer lo siguiente:

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
fuente
44
No es una buena idea negar el partido -df[,-match(c("e","f"),names(df))]
hadley
. @ JDLong: ¿qué sucede si deseo colocar la columna donde comienza el nombre de la columna -?
Chetan Arvind Patil
12

Hay una función llamada dropNamed()en el BBmiscpaquete de Bernd Bischl que hace exactamente esto.

BBmisc::dropNamed(df, "x")

La ventaja es que evita repetir el argumento del marco de datos y, por lo tanto, es adecuado para conectar magrittr(al igual que los dplyrenfoques):

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

Otra solución si no desea utilizar @ hadley's arriba: si "COLUMN_NAME" es el nombre de la columna que desea colocar:

df[,-which(names(df) == "COLUMN_NAME")]
Nick Keramaris
fuente
1
(1) El problema es soltar varias columnas a la vez. (2) No funcionará si COLUMN_NAMEno está en df(compruebe usted mismo:) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]es más simple y no sufre de (2)
Marek
¿Puedes dar más información sobre esta respuesta?
Akash Nayak
8

Más allá de lo select(-one_of(drop_col_names))demostrado en respuestas anteriores, hay un par de otras dplyropciones para colocar columnas select()que no implican la definición de todos los nombres de columna específicos (usando los datos de muestra dplyr starwars para alguna variedad en los nombres de columna):

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 

Si necesita descartar una columna que puede o no existir en el marco de datos, aquí hay un pequeño giro select_if()que, a diferencia del uso one_of(), no arrojará una Unknown columns:advertencia si el nombre de la columna no existe. En este ejemplo, 'bad_column' no es una columna en el marco de datos:

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

Proporcione el marco de datos y una cadena de nombres separados por comas para eliminar:

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

ingrese la descripción de la imagen aquí

Cibernético
fuente
1

Encuentre el índice de las columnas que desea soltar usando which. Dé a estos índices un signo negativo ( *-1). Luego subconjunto en esos valores, lo que los eliminará del marco de datos. Esto es un ejemplo.

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án
fuente
1

Si tiene una data.framememoria grande y tiene poco uso de memoria [ . . . . o rmywithin para eliminar columnas de unadata.frame , como subsetes actualmente (R 3.6.2) usando más memoria - al lado de la pista del manual para utilizar subsetde forma interactiva .

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
fuente