如何在数据框中按名称删除列
Posted
技术标签:
【中文标题】如何在数据框中按名称删除列【英文标题】:How to drop columns by name in a data frame 【发布时间】:2011-07-11 04:26:45 【问题描述】:我有一个大型数据集,我想读取特定列或删除所有其他列。
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
删除所有不需要的列。这是最优解吗?
【问题讨论】:
解决了这个问题,我在想subset(data, select=c(...))
在我的情况下有助于删除变量。不过,问题主要是关于paste("data$",var.out[i],sep="")
部分以访问循环内感兴趣的列。我如何粘贴或以某种方式组成列名?感谢大家的关注和帮助
Drop columns in R data frame的可能重复
【参考方案1】:
您应该使用索引或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
或者,更简单的是,使用subset
函数的select
参数:然后您可以直接在列名向量上使用-
运算符,甚至可以省略名称周围的引号!
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
【讨论】:
select
函数的subset
参数完美地完成了这项工作!谢谢朱巴!
which
不是必需的,请参阅 Ista 的回答。但是-
的子集很好!不知道!
subset
看起来不错,但它默默删除缺失值的方式对我来说似乎很危险。
subset
确实非常方便,但请记住避免使用它,除非您以交互方式使用 R。请参阅the Warning in the function's documentation 和this SO question 了解更多信息。
“你甚至可以省略名字周围的引号!”,你实际上必须省略引号,否则你会得到一元运算符的无效参数。如果您的名称中有某些字符(例如“-”),则根本不能使用此方法,因为删除引号会导致 R 无法正确解析您的代码。【参考方案2】:
不要为此使用-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()
!
【讨论】:
setdiff
也很有用:setdiff(names(dat), c("foo", "bar"))
@hadley 的 setdiff
提案非常适合长名单。【参考方案3】:
首先,如果您使用相同的数据框,您可以使用直接索引(使用布尔向量)而不是重新访问列名;正如 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() 的 drop 特性,你需要在不带引号的情况下编写与列名对应的表达式:
subset( data, select = -c("iden", "name", "x_serv", "m_serv") ) # WILL NOT WORK
subset( data, select = -c(iden, name, x_serv, m_serv) ) # WILL
作为奖励,这里是不同选项的小基准,清楚地表明子集更慢,而第一种重新分配方法更快:
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)
【讨论】:
我喜欢您使用NULL
的第二种选择,但是为什么当您输入两个以上的名称时需要使用list(NULL)
来分配它?我只是想知道它是如何工作的,因为我只尝试了一个名字,我不需要list()
@DarwinPC 是的。如果直接访问一个向量元素(使用$
或[[
),使用<- list(NULL)
实际上会导致错误的结果。如果您访问具有一列或多列的数据帧的子集,<- list(NULL)
是可行的方法,即使单列数据帧不需要它(因为如果需要,df['myColumns']
将被转换为向量)。
这种行为改变了吗? NULL
和 list(NULL)
得到相同的结果。【参考方案4】:
你也可以试试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
【讨论】:
即使某些命名的列不存在,使用dplyr::select(df2, -one_of(c('x','y')))
仍然有效(带有警告)
这正是我正在寻找的解决方案@divibisan,谢谢!【参考方案5】:
这里有一个快速解决方案。假设您有一个数据框 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,只需在 colnames 上使用 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
编辑:考虑以下 cmets 中 Matthew Lundberg 的 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
【讨论】:
X[,-grep("B",colnames(X))]
在没有列名包含B
的情况下将不返回任何列,而不是返回所需的所有列。以X <- iris
为例。这是使用带有计算值的负索引的问题。请考虑使用grepl
。【参考方案6】:
我在使用data.table
包时尝试删除一列,但得到了意想不到的结果。我有点认为以下内容可能值得发布。只是一点警告。
[由马修编辑...]
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
并不完全相同。实际上有很多不同之处,请参阅常见问题解答 1.1 和常见问题解答 2.17。您已被警告!
【讨论】:
或者您可以使用DT[,var.out := NULL]
删除您希望这样做的列。
subset(x, select=...) 方法适用于 data.frame
和 data.table
类【参考方案7】:
df2 <- df[!names(df) %in% c("c1", "c2")]
【讨论】:
【参考方案8】:我把代码改成:
# 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 的答案向您展示了如何一步完成。为什么要让它变得更复杂? 当然我在我的代码中使用了subset
函数的select
参数。我只是想看看如何在循环中访问任意列,以防我想做其他事情而不仅仅是删除列。原始数据集有大约 1200 个变量,我只对使用其中的 4 个感兴趣,而不知道它们到底在哪里。【参考方案9】:
这是另一个可能对其他人有帮助的解决方案。下面的代码从大型数据集中选择少量的行和列。除了我使用粘贴函数来选择一组名称按顺序编号的列之外,这些列的选择与 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
【讨论】:
【参考方案10】:如果您确切知道原始数据框中名为“df”的列的名称:
cols_to_drop <- c("A", "B", "C")
df_clean = df[,!(names(df) %in% cols_to_drop)]
源:https://www.listendata.com/2015/06/r-keep-drop-columns-from-data-frame.html
【讨论】:
【参考方案11】:由于声誉得分低,我无法在 cmets 中回答您的问题。
接下来的代码会报错,因为粘贴函数返回一个字符串
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
【讨论】:
【参考方案12】:df = mtcars
删除 vs 和 am,因为它们是分类的。在数据集中
vs 在第 8 列,am 在第 9 列
dfnum = df[,-c(8,9)]
【讨论】:
以上是关于如何在数据框中按名称删除列的主要内容,如果未能解决你的问题,请参考以下文章
使用 NaN 在 pandas 中按列对数据进行 Winsorizing