如何从 data.frame 中删除列?
Posted
技术标签:
【中文标题】如何从 data.frame 中删除列?【英文标题】:How do you remove columns from a data.frame? 【发布时间】:2011-10-27 16:20:19 【问题描述】:没有那么多“你怎么……?”但更多的是“你怎么……?”
如果您有一个有人给您的文件,其中包含 200 列,并且您希望将其减少到分析所需的少数几列,您将如何处理?一种解决方案是否比另一种解决方案更有优势?
假设我们有一个包含 col1、col2 到 col200 列的数据框。如果您只想要 1-100,然后是 125-135 和 150-200,您可以:
dat$col101 <- NULL
dat$col102 <- NULL # etc
或
dat <- dat[,c("col1","col2",...)]
或
dat <- dat[,c(1:100,125:135,...)] # shortest probably but I don't like this
或
dat <- dat[,!names(dat) %in% c("dat101","dat102",...)]
我还缺少什么?我知道这显然是主观的,但这是你可能会潜入并开始以一种方式做事并在有更有效的方法时养成习惯的那些细节之一。很像这个关于which 的问题。
编辑:
或者,有没有一种简单的方法来创建一个可行的列名向量? name(dat) 不会在它们之间用逗号打印它们,这是您在上面的代码示例中需要的,所以如果您以这种方式打印出名称,那么您到处都有空格并且必须手动输入逗号......命令将为您提供 "col1","col2","col3",... 作为输出,以便您轻松获取所需内容?
【问题讨论】:
第 6000 个 [r] 问题。 顺便说一句,您的问题的标题是 data.frame 中的“行”,但它询问文件中的列。编辑标题可能是个好主意。 :) 相关:Remove an entire column from a data.frame in R 请注意类似列表的问题;他们倾向于引出 i) 仅列出选项和 ii) 意见的答案,并且难以正确回答 - “您使用什么?”没有公认的答案;每个答案都是正确的。 @nzcoops 关于您的编辑子问题:cat(shQuote(names(iris)), sep=", ")
,因此将其包装到函数中并使用它;)
【参考方案1】:
我使用data.table 的:=
运算符来立即删除列,而不管表的大小。
DT[, coltodelete := NULL]
或
DT[, c("col1","col20") := NULL]
或
DT[, (125:135) := NULL]
或
DT[, (variableHoldingNamesOrNumbers) := NULL]
使用<-
或subset
的任何解决方案都将复制整个 表。 data.table 的 :=
运算符仅修改了指向列的指针的内部向量,就地。因此,该操作(几乎)是即时的。
【讨论】:
对于数据帧我收到此错误:Error: could not find function ":="
。所以我猜这篇文章已经过时了。
@Pio,我希望你在开玩笑吧?这仅适用于data.table
类对象,即,如果您的数据框是df
,您可以使用library(data.table); setDT(df)[,c("col1","col20"):=NULL]
等...【参考方案2】:
要删除单个列,我将使用dat$x <- NULL
。
要删除多列,但少于大约 3-4,我将使用dat$x <- dat$y <- dat$z <- NULL
。
除此之外,我将使用subset
,带有否定名称(!):
subset(mtcars, , -c(mpg, cyl, disp, hp))
【讨论】:
我使用dat[c("x","y","z")] <- list(NULL)
删除列。通常将其分为两个步骤:生成名称以删除to_remove <- c("x","y","z")
,但也可以是例如names(dat)[sapply(dat, function(x) all(is.na(x)))]
然后dat[to_remove] <- list(NULL)
。
dat$x <- NULL
... 警告:将 LHS 强制加入列表?嗯,这不好
奇怪的是我明白了:Error in -c("V2","V3"): invalid argument to unary operator
。当我尝试为子集取消选择变量时【参考方案3】:
为了清楚起见,我经常在subset
中使用select 参数。对于新人,我了解到将他们需要接受的命令数量保持在最低限度有助于采用。随着他们技能的提高,他们的编码能力也会提高。当需要在给定条件内选择数据时,subset 是我向人们展示的第一个命令。
类似:
> subset(mtcars, select = c("mpg", "cyl", "vs", "am"))
mpg cyl vs am
Mazda RX4 21.0 6 0 1
Mazda RX4 Wag 21.0 6 0 1
Datsun 710 22.8 4 1 1
....
我确信这会比大多数其他解决方案测试得慢,但我很少在微秒级产生影响。
【讨论】:
不带引号也可以:subset(mtcars, select = c(mpg, cyl, vs, am))
如果你有一个值为“cyl”的变量mpg怎么办?【参考方案4】:
使用带有“NULL”的 colClasses 实例的 read.table 来避免一开始就创建它们:
## example data and temp file
x <- data.frame(x = 1:10, y = rnorm(10), z = runif(10), a = letters[1:10], stringsAsFactors = FALSE)
tmp <- tempfile()
write.table(x, tmp, row.names = FALSE)
(y <- read.table(tmp, colClasses = c("numeric", rep("NULL", 2), "character"), header = TRUE))
x a
1 1 a
2 2 b
3 3 c
4 4 d
5 5 e
6 6 f
7 7 g
8 8 h
9 9 i
10 10 j
unlink(tmp)
【讨论】:
【参考方案5】:对于我倾向于获取的那种大文件,我通常不会在 R 中执行此操作。我会在 Linux 中使用 cut
命令在数据到达 R 之前对其进行处理。这不是批评R,只是偏爱使用一些非常基本的 Linux 工具,如 grep、tr、cut、sort、uniq,偶尔在需要处理正则表达式时使用 sed 和 awk(或 Perl)。
使用标准 GNU 命令的另一个原因是我可以将它们传递回数据源并要求它们对数据进行预过滤,这样我就不会得到无关的数据。我的大多数同事都精通 Linux,很少有人知道 R。
(已更新)不久之后我想使用的一种方法是将mmap
与文本文件配对并原位检查数据,而不是将其完全读入RAM。我已经用 C 完成了这项工作,而且速度非常快。
【讨论】:
您的“极快”的评论让我想起了 data.table 中的:=
,请参阅我刚刚添加的答案。
很高兴您的回答!我正在寻找一个 data.table 解决方案。极快的速度总是比极快的速度快。 ;-)【参考方案6】:
有时我喜欢使用列 ID 来代替。
df <- data.frame(a=rnorm(100),
b=rnorm(100),
c=rnorm(100),
d=rnorm(100),
e=rnorm(100),
f=rnorm(100),
g=rnorm(100))
as.data.frame(names(df))
names(df)
1 a
2 b
3 c
4 d
5 e
6 f
7 g
删除列“c”和“g”
df[,-c(3,7)]
如果您有很大的 data.frames 或不想输入的长列名称,这将特别有用。或者遵循某种模式的列名,因为这样您就可以使用 seq() 来删除。
RE:您的编辑
您不一定要在字符串周围加上“”,也不必用“,”来创建字符向量。我觉得这个小技巧很方便:
x <- unlist(strsplit(
'A
B
C
D
E',"\n"))
【讨论】:
嗯,远点。我在问题中已经/暗示过。我不喜欢这样,因为如果您的原始数据发生更改,您必须重新编写脚本。如果有人从具有新的第二列的程序中重新导出数据集,那么您的所有引用都将被删除。 您总是可以将删除列的名称保存到向量中,然后这没关系,df[,-c(character_vector)]
是的。已在问题中添加和“附加”位以解决该问题。事后看来,这可能应该是问题所在。【参考方案7】:
来自http://www.statmethods.net/management/subset.html
# exclude variables v1, v2, v3
myvars <- names(mydata) %in% c("v1", "v2", "v3")
newdata <- mydata[!myvars]
# exclude 3rd and 5th variable
newdata <- mydata[c(-3,-5)]
# delete variables v3 and v5
mydata$v3 <- mydata$v5 <- NULL
觉得列一个“不包括”的清单真的很聪明
【讨论】:
【参考方案8】:只是解决编辑问题。
@nzcoops,您不需要以逗号分隔的字符向量中的列名。您正在以错误的方式思考这个问题。当你这样做时
vec <- c("col1", "col2", "col3")
您正在创建一个字符向量。 ,
只是在定义该向量时分隔 c()
函数采用的参数。 names()
和类似函数返回名称的字符向量。
> dat <- data.frame(col1 = 1:3, col2 = 1:3, col3 = 1:3)
> dat
col1 col2 col3
1 1 1 1
2 2 2 2
3 3 3 3
> names(dat)
[1] "col1" "col2" "col3"
从names(dat)
的元素中进行选择比将其输出处理为可以剪切和粘贴的逗号分隔字符串要容易得多且不易出错。
假设我们想要列 col1
和 col2
,子集 names(dat)
,只保留我们想要的:
> names(dat)[c(1,3)]
[1] "col1" "col3"
> dat[, names(dat)[c(1,3)]]
col1 col3
1 1 1
2 2 2
3 3 3
你可以做你想做的事,但 R 总是会在屏幕上用引号打印矢量"
:
> paste('"', names(dat), '"', sep = "", collapse = ", ")
[1] "\"col1\", \"col2\", \"col3\""
> paste("'", names(dat), "'", sep = "", collapse = ", ")
[1] "'col1', 'col2', 'col3'"
所以后者可能更有用。但是,现在您必须从该字符串中剪切和过去。使用返回您想要的对象并使用标准子集例程来保留您需要的对象要好得多。
【讨论】:
我这样做的原因是因为我不喜欢使用数字引用。如果有人回来找您并忘记了一列,因此使用新的第二列重新导出数据文件,那么您必须仔细检查并更改您的 c(1,3) 行。 @nzcoops 我能理解。但是,如果数据发生变化,我已经养成了检查所有代码的习惯,因此更新我想要的列号只是我检查的事情之一。我忘了提到,如果您将两个paste()
调用中的每一个都包装在writeLines()
中,R 将在不包装"
的情况下将字符串写入控制台,这使得第一个paste()
示例最接近地代表了您的要求为。【参考方案9】:
如果您已经有一个名称向量,有多种创建方法,您可以轻松使用子集函数来保留或删除对象。
dat2 <- subset(dat, select = names(dat) %in% c(KEEP))
在这种情况下,KEEP 是预先创建的列名向量。例如:
#sample data via Brandon Bertelsen
df <- data.frame(a=rnorm(100),
b=rnorm(100),
c=rnorm(100),
d=rnorm(100),
e=rnorm(100),
f=rnorm(100),
g=rnorm(100))
#creating the initial vector of names
df1 <- as.matrix(as.character(names(df)))
#retaining only the name values you want to keep
KEEP <- as.vector(df1[c(1:3,5,6),])
#subsetting the intial dataset with the object KEEP
df3 <- subset(df, select = names(df) %in% c(KEEP))
结果:
> head(df)
a b c d
1 1.05526388 0.6316023 -0.04230455 -0.1486299
2 -0.52584236 0.5596705 2.26831758 0.3871873
3 1.88565261 0.9727644 0.99708383 1.8495017
4 -0.58942525 -0.3874654 0.48173439 1.4137227
5 -0.03898588 -1.5297600 0.85594964 0.7353428
6 1.58860643 -1.6878690 0.79997390 1.1935813
e f g
1 -1.42751190 0.09842343 -0.01543444
2 -0.62431091 -0.33265572 -0.15539472
3 1.15130591 0.37556903 -1.46640276
4 -1.28886526 -0.50547059 -2.20156926
5 -0.03915009 -1.38281923 0.60811360
6 -1.68024349 -1.18317733 0.42014397
> head(df3)
a b c e
1 1.05526388 0.6316023 -0.04230455 -1.42751190
2 -0.52584236 0.5596705 2.26831758 -0.62431091
3 1.88565261 0.9727644 0.99708383 1.15130591
4 -0.58942525 -0.3874654 0.48173439 -1.28886526
5 -0.03898588 -1.5297600 0.85594964 -0.03915009
6 1.58860643 -1.6878690 0.79997390 -1.68024349
f
1 0.09842343
2 -0.33265572
3 0.37556903
4 -0.50547059
5 -1.38281923
6 -1.18317733
【讨论】:
【参考方案10】:可以使用setdiff
函数:
如果要保留的列多于要删除的列: 假设您要删除 2 列,例如从 data.frame DT 中的 col1、col2;您可以执行以下操作:
DT<-DT[,setdiff(names(DT),c("col1","col2"))]
如果要删除的列多于保留的列: 假设您只想保留 col1 和 col2:
DT<-DT[,c("col1","col2")]
【讨论】:
【参考方案11】:来自 dplyr 的 select()
函数对于子集列非常强大。有关方法列表,请参阅 ?select_helpers
。
在这种情况下,如果列名有一个共同的前缀和序号,则可以使用num_range
:
library(dplyr)
df1 <- data.frame(first = 0, col1 = 1, col2 = 2, col3 = 3, col4 = 4)
df1 %>%
select(num_range("col", c(1, 4)))
#> col1 col4
#> 1 1 4
更一般地,您可以使用select()
中的减号来删除列,例如:
mtcars %>%
select(-mpg, -wt)
最后,对于您的问题“是否有一种简单的方法可以创建一个可行的列名向量?” - 是的,如果您需要手动编辑名称列表,请使用dput
获取一个逗号分隔的引用列表,您可以轻松操作:
dput(names(mtcars))
#> c("mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am",
#> "gear", "carb")
【讨论】:
【参考方案12】:within
中的rm
可能非常有用。
within(mtcars, rm(mpg, cyl, disp, hp))
# drat wt qsec vs am gear carb
# Mazda RX4 3.90 2.620 16.46 0 1 4 4
# Mazda RX4 Wag 3.90 2.875 17.02 0 1 4 4
# Datsun 710 3.85 2.320 18.61 1 1 4 1
# Hornet 4 Drive 3.08 3.215 19.44 1 0 3 1
# Hornet Sportabout 3.15 3.440 17.02 0 0 3 2
# Valiant 2.76 3.460 20.22 1 0 3 1
# ...
可与其他操作结合使用。
within(mtcars,
mpg2=mpg^2
cyl2=cyl^2
rm(mpg, cyl, disp, hp)
)
# drat wt qsec vs am gear carb cyl2 mpg2
# Mazda RX4 3.90 2.620 16.46 0 1 4 4 36 441.00
# Mazda RX4 Wag 3.90 2.875 17.02 0 1 4 4 36 441.00
# Datsun 710 3.85 2.320 18.61 1 1 4 1 16 519.84
# Hornet 4 Drive 3.08 3.215 19.44 1 0 3 1 36 457.96
# Hornet Sportabout 3.15 3.440 17.02 0 0 3 2 64 349.69
# Valiant 2.76 3.460 20.22 1 0 3 1 36 327.61
# ...
【讨论】:
以上是关于如何从 data.frame 中删除列?的主要内容,如果未能解决你的问题,请参考以下文章