按名称删除数据框列


874

我想从数据框中删除许多列。我知道我们可以使用类似的方法分别删除它们:

df$x <- NULL

但是我希望用更少的命令来做到这一点。

另外,我知道我可以使用整数索引删除列,如下所示:

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

但是我担心我的变量的相对位置可能会改变。

考虑到R的强大功能,我认为可能有比逐一删除每一列更好的方法。


13
有人可以向我解释为什么R没有简单的东西df#drop(var_name),而是需要做这些复杂的变通方法吗?
ifly6

2
@ ifly6 R中的'subset()'函数与Python中的'drop()'函数差不多,只是您不需要指定axis参数...我同意这很烦人,只是一个全面的,最终的,简单易用的关键字/语法,用于一些基本的操作,例如删除一列。
Paul Sochacki

Answers:


910

您可以使用简单的名称列表:

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

或者,您也可以列出要保留的内容,并按名称引用它们:

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

编辑:对于那些仍不熟悉drop索引函数参数的用户,如果要保留一列作为数据框,请执行以下操作:

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

drop=TRUE(或不提及它)将删除不必要的尺寸,因此返回带有column值的向量y


19
子集函数效果更好,因为它不会将具有一列的数据帧转换为向量
mut1na 2013年

3
@ mut1na检查索引函数的参数drop = FALSE。
约里斯·梅斯

4
DF[,keeps]不是应该DF[keeps]吗?
lindelof

8
@lindelof不。可以,但是如果仅选择一个列,则必须添加drop = FALSE,以防止R将数据帧转换为向量。不要忘了数据框是列表,因此列表选择(像我所做的那样是一维的)非常好,并且总是返回列表。还是这种情况下的数据帧,这就是为什么我更喜欢使用它。
2014年

7
@AjayOhri是的,会的。如果不使用逗号,则使用“列表”选择方式,这意味着即使提取单个列,也仍然会返回数据框。如果您使用“矩阵”方式,则应注意,如果仅选择单个列,则将获得向量而不是数据框。为了避免这种情况,您需要添加drop = FALSE。正如我的回答以及您上方的评论所解释的那样
Joris Meys 2015年

452

还有subset一条命令,如果您知道需要哪些列,该命令将非常有用:

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

@hadley发表评论后更新:要删除 a,c列,您可以执行以下操作:

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

3
我真的希望R subset函数具有“ allbut = FALSE”之类的选项,该选项在设置为TRUE时“反转”选择,即保留select列表中的所有列之外的所有列。
Prasad Chalasani 2011年

4
@prasad,请参阅下面的@joris答案。没有任何子集条件的子集有点过大。尝试简单:df[c("a", "c")]
JD Long

@JD我知道这一点,但是我喜欢subset命令的语法方便性,在这里您不需要在列名周围加上引号-我想我不介意输入一些额外的字符只是为了避免引述名称:)
Prasad Chalasani 2011年

11
请注意,您不应subset在其他函数内部使用。
阿里·弗里德曼


196
within(df, rm(x))

是最简单的,或者对于多个变量:

within(df, rm(x, y))

或者,如果您要处理data.tables(请参见如何在data.table中按名称删除列?):

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

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

或多个变量

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

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

26
within(df, rm(x))迄今为止最干净的解决方案。鉴于这是可能的,其他所有答案似乎都不必要地复杂一个数量级。
Miles Erickson's 2015年

2
请注意,within(df, rm(x))不会,如果有重复的命名工作列xdf
MichaelChirico

2
@MichaelChirico进行澄清,它既未删除,但似乎更改了数据的值。如果是这种情况,则会遇到更大的问题,但这是一个示例:df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))return data.frame(x = 2, x = 2)
Max Ghenis'Mar

1
@MilesErickson问题是您依赖于功能within()强大但还使用NSE的功能。帮助页面上的注释清楚地表明,在编程时应格外小心。
乔里斯·梅斯

@MilesErickson人们多久会遇到一个名称重复的数据框?
HSchmale,


54

list(NULL)也可以:

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
辉煌!这以一种自然的方式将NULL赋值扩展到单个列,并且(似乎)避免了复制(尽管我不知道幕后发生的事情,因此它在内存使用方面可能没有更高的效率……但是在我看来语法上更有效。)
c-urchin 2014年

6
您不需要list(NULL),NULL就足够了。例如:dat [,4] = NULL
CousinCocaine

8
OP的问题是如何删除多个列。dat [,4:5] <-NULL无效。这就是list(NULL)出现的地方。它适用于1个或更多列。
文森特

尝试删除重复的列名时,这也不起作用。
MichaelChirico

@MichaelChirico对我来说很好。如果要删除具有相同名称的第一列,请给标签,或者为要删除的每一列提供索引。如果您有一个无法正常运行的示例,那么我将很感兴趣地看到它。也许将其发布为新问题?
文森特

42

如果要通过引用删除列并避免与之关联的内部复制,data.frames则可以使用data.table包和函数:=

您可以将字符向量名称传递给:=运算符的左侧,并NULL作为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]

如果要在调用外部将名称预先定义为字符向量,请在范围内[包装对象的名称,(){}强制在调用范围内对LHS进行求值,而不要将其作为范围内的名称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.   

您也可以使用set,它避免了的开销[.data.table也可以使用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

基于grep()将返回数字矢量这一事实,有一种可能更强大的策略。如果您像我在一个数据集中那样拥有一长串变量,则某些变量以“ .A”结尾,而另一些变量以“ .B”结尾,而您只想要以“ .A”结尾的变量(以及对于所有与任一模式都不匹配的变量,请执行以下操作:

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

对于当前情况,以Joris Meys为例,它可能不那么紧凑,但可能是:

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

1
如果我们drops首先将定义为paste0("^", drop_cols, "$"),则使用以下命令会变得更好(阅读:更紧凑)sapplyDF[ , -sapply(drops, grep, names(DF))]
MichaelChirico

30

另一个dplyr答案。如果您的变量具有某些通用的命名结构,则可以尝试starts_with()。例如

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

如果要在数据框中删除一系列变量,可以使用:。例如,如果您想删除var2var3以及介于两者之间的所有变量,则只剩下var1

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

1
不要忘记附带的所有其他机会select(),例如contains()matches(),它们也接受正则表达式。
ha_pu

23

另一种可能性:

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

要么

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

2
遗憾的是,最好不要使用它,因为使用setdiff最佳,尤其是在列数非常多的情况下。
ctbrown 2014年

另一个角度:df <- df[ , -which(grepl('a|c', names(df)))]

23
DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
DF

输出:

    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)

输出:

        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

Dplyr解决方案

我怀疑这会在这里引起很多关注,但是如果您有要删除的列列表,并且想在dplyr链中使用one_of()它,select子句中:

这是一个简单的可复制示例:

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

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

可以通过运行?one_of或此处找到文档:

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


22

出于兴趣,这标记了R奇怪的多个语法不一致之一。例如,给定一个两列的数据帧:

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

这给出了一个数据框

subset(df, select=-y)

但这给出了一个向量

df[,-2]

所有这些都在其中进行了解释,?[但这并不是完全预期的行为。好吧至少对我来说...


18

这是dplyr解决方法:

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

我之所以喜欢它,是因为它无需注释即可阅读和理解,并且对更改数据框内的列的鲁棒性很直观。它还遵循矢量化习惯用法,-用于删除元素。


加上(1)用户想要替换原始df(2)magrittr具有%<>% 运算符来替换输入对象,可以简化为df %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek

1
如果您要删除的列很长,请使用dplyr,将它们分组并仅放置一个减号会更容易:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar

14

我一直认为必须有一个更好的习惯用法,但是为了按名称减去列,我倾向于执行以下操作:

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
否定比赛不是一个好主意df[,-match(c("e","f"),names(df))]
hadley 2011年

。@ JDLong-如果要删除以列名开头的列-怎么办?
Chetan Arvind Patil

12

dropNamed()Bernd Bischl的BBmisc程序包中有一个函数可以完全做到这一点。

BBmisc::dropNamed(df, "x")

优点是它避免了重复数据帧参数,因此适用于管道传输magrittr(就像dplyr方法一样):

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

9

如果您不想使用上面的@hadley,则可以采用另一种解决方案:如果“ COLUMN_NAME”是您要删除的列的名称:

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

1
(1)问题是一次删除多列。(2)如果COLUMN_NAME不在,则无法使用df(请检查您的:)df<-data.frame(a=1,b=2)。(3)df[,names(df) != "COLUMN_NAME"]更简单,不会遭受(2)
Marek

您能否提供有关此答案的更多信息?
Akash Nayak '18

8

除了select(-one_of(drop_col_names))前面的答案中展示的以外,还有其他一些dplyr用于删除列的选项,这些选项select()不涉及定义所有特定的列名称(使用dplyr starwars示例数据来获取某些列名称):

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 

如果您需要删除数据帧中可能存在或可能不存在的列,则使用select_if()此方法one_of()会稍有不同,与之不同的是,Unknown columns:如果列名不存在,则使用using 不会发出警告。在此示例中,“ bad_column”不是数据框中的列:

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

4

提供数据框和用逗号分隔的字符串以删除:

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

用法

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

在此处输入图片说明


1

使用查找要删除的列的索引which。给这些索引加上负号(*-1)。然后是这些值的子集,这会将它们从数据框中删除。这是一个例子。

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

如果您的data.frame内存很大而内存不足[ rmwithin,如目前(R 3.6.2)所述,使用更多内存来删除a的列data.framesubset -在手册的提示下可以subset交互使用

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.