为什么使用名称进行子集(即删除)列操作不可行?

18

我非常担心这个问题已经被问过并且会被踩,但我在文档中没有找到答案(?"["), 而且发现很难搜索。

data(wines)
# This is allowed:
alcoholic <- wines[, 1]
alcoholic <- wines[, "alcohol"]
nonalcoholic <- wines[, -1]
# But this is not:
fail <- wines[, -"alcohol"]

我知道有两种解决方案,但因需要它们而感到沮丧。

win <- wines[, !colnames(wines) %in% "alcohol"]  # snappy
win <- wines[, -which(colnames(wines) %in% "alcohol")]  # snappier!

2
“snappy”和“snappier”是正面的还是负面的度量?在这些情况下,我更喜欢使用setdiff。你认为-“alcohol”会返回什么?它本身不起作用,那么在尝试进行子集操作时为什么会起作用呢? - A5C1D2H2I1M1N2O1R2T1
1
也许不是对于你的“为什么”问题的答案,因为它无法解释“为什么有人选择以这种方式实现”,但是从?[来看:“对于[-索引:i、j等可以是逻辑向量(你的!替代方案)[...]也可以是负整数(你的which替代方案)。 - Henrik
1
如果你只是想找一些更短的东西:wines[names(wines)!="alcohol"] - plannapus
4
选择除了"Temp"列以外的所有列,从"airquality"数据集中提取子集。 - Henrik
2
葡萄酒数据集来自哪里?我得到了“未找到”(R 2.15,所以可能是新的)。 - Spacedman
显示剩余7条评论
6个回答

19

当你执行

wines[, -1]

-1 在被 [ 使用之前已经被计算了。如你所知,一元运算符 - 不能用于 character 类型的对象,所以对 "alcohol" 做同样的操作会导致:

Error in -"alcohol" : invalid argument to unary operator

您可以添加以下内容到您的备选项中:
wines[, -match("alcohol", colnames(wines))]
wines[, setdiff(colnames(wines), "alcohol")]

但是你应该了解负索引的风险,例如,如果你打错了"alcool"(sic.),会发生什么。因此,你应该优先考虑这里的第一个建议和最后一个建议(@Ananda's)。你还可以编写一个函数,如果提供的名称不在数据中,则会出现错误。


R> -1 会输出 [1] -1 ,那么这是怎么工作的呢?我对 R 的工作方式不是很熟悉。这就是你的意思吗? - a different ben
我需要写一个删除列的习惯用语手册,感谢你的补充 :) - a different ben
是的,-1 是可以正常计算的,所以你可以将它作为参数传递给 [ 函数,它会知道如何处理它。另一方面,-"alcohol" 不行。这与 [ 的实现方式关系不大,更多的是因为你无法计算 -"alcohol",因此不能将其传递给 [ 或任何函数。 - flodel
我通常会回答这类问题,说“-which()是邪恶的”,然后指向“setdiff”。+1 - A5C1D2H2I1M1N2O1R2T1
抱歉,我理解有点慢。感谢@Ananda和flodel。 - a different ben
1
为了真正的乐趣,请比较foo[-0]和foo[-c(0,1)]。如果我没记错,flodel在几个月前的一个SO问题中讨论了零。 - Carl Witthoft

9

另一种可能性:

subset(wines,select=-alcohol)

您可以甚至执行以下操作

subset(wines,select=-c(alcohol,other_drop))

实际上,如果您要删除一组连续的列,甚至可以使用以下方法:
subset(wines,select=-(first_drop:last_drop))

这可能很方便(虽然在我看来,这取决于列的顺序,这可能是脆弱的:如果有一种方法来识别列,或者提供更明确的分组定义,我可能更喜欢基于 grep 的解决方案)。

在这种情况下,subset 使用非标准的评估方式,在某些情况下可能会存在危险性。但是,由于其可读性,我仍然喜欢它用于简单的、高级的数据操作。


1
subset函数通过一个命名的数字向量将选择表达式转换为数字,这就是为什么":"方法起作用的原因。 - IRTFM
1
@DWin,“?subset”说:“这是一个旨在交互使用的便利函数。对于编程,最好使用标准的子集函数,如[],特别是参数子集的非标准评估可能会产生意想不到的后果。”为什么?它所指的非标准评估是什么?这些是Ben Bolker列出的吗? - a different ben

6
另一种方法是使用数字索引,并且适用于需要删除一堆名称相似的列的情况:
dfrm[ , -grep("^val", names(dfrm) )] #remove columns starting with "val"

我投了flodel一票,因为他的回答描述了为什么“减号”无法起作用。基本上是因为R作者没有为此目的重载“-”运算符。他们也没有像一些语言那样重载“+”以执行连接操作。

那么如果开发人员选择这样做,它们就可能被过载了? - a different ben
@adifferentben 或者你可以自己重载运算符,如果你敢尝试的话 :-)。 - Carl Witthoft
lattice和ggplot2绘图系统的作者已经重载了“+”运算符,因此重载“-”并没有根本性的障碍。 - IRTFM

3

你可以写一个简单的函数并将其放入你的.Rprofile中。例如:

dropcols <- function( df , cols ){
  out <- df[ , !names(df) %in% cols]
  return( out )
}

#  To use it....
data( mtcars )
head( dropcols( mtcars , "mpg" ) )
#                  cyl disp  hp drat    wt  qsec vs am gear carb
#Mazda RX4           6  160 110 3.90 2.620 16.46  0  1    4    4
#Mazda RX4 Wag       6  160 110 3.90 2.875 17.02  0  1    4    4
#Datsun 710          4  108  93 3.85 2.320 18.61  1  1    4    1
#Hornet 4 Drive      6  258 110 3.08 3.215 19.44  1  0    3    1
#Hornet Sportabout   8  360 175 3.15 3.440 17.02  0  0    3    2
#Valiant             6  225 105 2.76 3.460 20.22  1  0    3    1

是的,这是一个有用的解决方法。但对于其他人来说并不是很便携,所以我有点不愿意这样做。总的来说,我一般都避免那种情况,部分原因是因为这个,但也因为我总是忘记将我的工作同步到家里的机器、笔记本电脑等设备上,而且也会忘记我的.Rprofile文件中有什么! - a different ben

3

我在文档中找不到这个,但以下语法适用于 data.table

dt = data.table(wines)

dt[, !"alcohol", with = F]

如果您愿意,您也可以拥有一个列的列表:

dt[, !c("Country", "alcohol"), with = F]

最近在v1.8.4版本的新闻中有记录:

When with=FALSE, "!" may also be a prefix on j, #1384ii. This selects all but the named columns.

DF[,-match("somecol",names(DF))]
# works when somecol exists. If not, NA causes an error.

DF[,-match("somecol",names(DF),nomatch=0)]
# works when somecol exists. Empty data.frame when it doesn't, silently.

DT[,-match("somecol",names(DT)),with=FALSE]
# same issues.

DT[,setdiff(names(DT),"somecol"),with=FALSE]
# works but you have to know order of arguments, and no warning if missing

vs

DT[,!"somecol",with=FALSE]
# works and easy to read. With (helpful) warning if somecol isn't there.
但是以上所有操作都会复制除已删除列之外的所有列。通常更多地是:
DT[,somecol:=NULL]

按名称通过引用删除列。

0

您可以按照以下方式获得所需的行为:

data(iris)
str(iris)
delete <- which(colnames(iris) == "Species")
iris2 <- iris[, -delete]
str(iris2)

这相当于匹配单个字符串,而不是使用%in%来匹配字符串列表。 - a different ben
这可以简化为 deleted <- colnames(iris) == "Species"; iris[!deleted]。当你有逻辑向量时,就不需要使用负索引。 - Marek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接