如何在数据框中按名称删除列


304

我的数据集很大,我想阅读特定的列或删除所有其他列。

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

我选择我不感兴趣的列:

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

而且我想做些类似的事情:

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

删除所有不需要的列。这是最佳解决方案吗?


1
睡在这个问题上,我认为这subset(data, select=c(...))对我删除var很有帮助。但是,问题主要是关于paste("data$",var.out[i],sep="")在循环内访问感兴趣的列的部分。如何粘贴或以某种方式组成列名?感谢大家的关注和帮助
leroux 2011年

Answers:


380

您应该使用索引或subset函数。例如 :

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

然后,您可以在列索引中使用which函数和-运算符:

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

或者,更简单地说,使用函数的select参数subset:然后您可以-直接在列名称的向量上使用运算符,甚至可以省略名称周围的引号!

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

请注意,您也可以选择所需的列,而不用删除其他列:

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
函数的select参数subset完美地完成了工作!谢谢你吧!
leroux 2011年

2
which不是必需的,请参阅Ista的答案。但是带有的子集-很好!不知道!
TMS

5
subset看起来不错,但是它默默地丢失缺失值的方式对我来说似乎很危险。
static_rtti 2014年

2
subset确实非常方便,但请记住避免使用它,除非您以交互方式使用R。有关更多信息,请参见函数文档中的警告该SO问题
Waldir Leoncio

4
“甚至可以省略名称周围的引号!”,实际上您必须省略引号,否则您将获得一元运算符的无效参数。如果名称中包含某些字符(例如“-”),则根本不能使用此方法,因为引号引起R无法正确解析您的代码。
oh54

122

请勿-which()用于此用途,这非常危险。考虑:

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

而是使用子集或!函数:

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

我从痛苦的经历中学到了这一点。不要过度使用which()


31
setdiff也很有用:setdiff(names(dat), c("foo", "bar"))
hadley 2011年

setdiff@hadley 的建议非常适合一长串名称。
JASC

48

首先,如果使用相同的数据框,则可以使用直接索引(带有布尔向量),而不是重新访问列名。正如Ista所指出的那样,它会更安全,并且编写和执行起来也会更快。因此,您只需要:

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

然后,只需重新分配数据即可:

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

其次,编写起来更快,您可以将NULL直接分配给要删除的列:

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

最后,您可以使用subset(),但实际上不能在代码中使用它(即使帮助文件也对此有所警告)。具体地说,对我来说,一个问题是,如果您想直接使用susbset()的放置功能,则需要编写不带引号的与列名称相对应的表达式:

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

另外,这是各种选项的小型基准测试,它清楚地表明subset较慢,而第一种重新分配方法则更快:

                                        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

微台图

代码如下:

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
我喜欢您的第二个替代方法NULL,但是为什么要使用两个以上的名称来分配它list(NULL)呢?我只是想知道它是如何工作的,因为我只用了一个名字试过了,不需要了list()
Darwin PC

3
@DarwinPC是的。如果直接访问一个向量元素(使用$[[),则使用<- list(NULL)实际上会导致错误的结果。如果您访问具有一列或多列的数据帧的子集,那么<- list(NULL)即使对于一列数据帧来说,它也是不需要的(因为df['myColumns']如果需要,它将被强制转换为向量)。
AntoineLizée,2015年

27

您也可以尝试该dplyr软件包:

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
dplyr::select(df2, -one_of(c('x','y')))即使某些命名的列不存在,使用仍将有效(带有警告)
divibisan

13

这是一个快速的解决方案。假设您有一个数据框X,其中包含三列A,B和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

如果要删除列(例如B),只需对列名使用grep即可获取列索引,然后可以使用该索引来省略列。

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

您的新X数据框将如下所示(这次没有B列):

> X
  A C
1 1 5
2 2 6

grep的优点在于,您可以指定多个与正则表达式匹配的列。如果我的X具有五列(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

取出列B和D:

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

编辑:在下面的评论中考虑马修·伦德伯格的grepl建议:

> 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

如果我尝试删除不存在的列,则不会发生任何事情:

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

3
X[,-grep("B",colnames(X))]在不包含任何列名的情况下,将不返回任何列B,而不是根据需要返回所有列。考虑X <- iris一个例子。这是将负索引与计算值一起使用的问题。考虑grepl代替。
马修·伦德伯格

6

我尝试在使用软件包时删除一列 data.table得到了意外结果。我认为以下内容可能值得发布。只是一些注意事项。

[由马修(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

基本上,的语法与data.table不完全相同data.frame。实际上存在很多差异,请参阅FAQ 1.1和FAQ 2.17。你被警告了!


1
或者,您可以DT[,var.out := NULL]用来删除想要删除的列。
mnel

子集(x,select = ...)方法适用于data.framedata.table
-momeara

3

我将代码更改为:

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

无论如何,juba的答案是解决我的问题的最好方法!


为什么要循环执行此操作?答案juba的答案向您展示了如何一步完成。为什么要使其更复杂?
伊斯塔

当然,我在代码中使用了函数的select参数subset。我只是想看看如何在循环中访问任意列,以防我想做其他事情而不只是删除该列。原始数据集大约有1200个var,而我只想使用其中的4个而不知道它们到底在哪里。
leroux 2011年

2

这是另一种可能对他人有用的解决方案。下面的代码从大型数据集中选择少量的行和列。列的选择与juba的答案之一相同,只是我使用粘贴功能选择了一组名称按顺序编号的列:

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

由于信誉得分低,我无法在评论中回答您的问题。

下一个代码将给您一个错误,因为粘贴函数返回一个字符串

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

这是一个可能的解决方案:

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
}

或只是做:

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

-1
df = mtcars 
删除vs和am,因为它们是绝对的。在数据集中,vs在第8列中,am在第9列中

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

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.