基于条件语句(`if`)替换数据框中的值

151
在下面编码的 R 数据框中,我想用 b 替换所有出现的 B
junk <- data.frame(x <- rep(LETTERS[1:4], 3), y <- letters[1:12])
colnames(junk) <- c("nm", "val")

这提供了:

   nm val
1   A   a
2   B   b
3   C   c
4   D   d
5   A   e
6   B   f
7   C   g
8   D   h
9   A   i
10  B   j
11  C   k
12  D   l

我的最初尝试是使用for循环和if语句,代码像这样:

for(i in junk$nm) if(i %in% "B") junk$nm <- "b"

但是,正如你所看到的那样,这将用 b 替换 junk$nm所有值。我能理解为什么会这样,但我似乎无法使其仅替换原始值为 B 的那些 junk$nm。

注意:我使用了 gsub 解决了问题,但出于学习 R 的目的,我仍然想知道如何让我的原始方法工作(如果可能的话)。


1
你可能想在原始数据框的构建中添加 stringsAsFactors = FALSE。 - jimmyb
@jimmyb 为什么?因子在大多数 R 的建模代码中是有用的,也是必要的。正确处理这个问题的方法是承认数据是一个因子。如果您不想/不需要进行转换,那么可以按照您所说的做。如果您确实需要因子,则有简单的方法来执行 @Kenny 想要执行的操作。 - Gavin Simpson
1
因为性能原因,因子曾经更受欢迎,然而现在字符串是不可变的并且被哈希化,因子的价值就不那么明显了,因为大多数基本的R功能会直接将它们转换(尽管会有警告)。我认为因子导致了我在人们的R代码中发现的大量错误。 - jimmyb
10个回答

254
更容易的方法是将 nm 转换为字符,然后进行更改:
junk$nm <- as.character(junk$nm)
junk$nm[junk$nm == "B"] <- "b"

编辑:如果确实需要将nm保持为因子,请在结尾处添加以下内容:

junk$nm <- as.factor(junk$nm)

4
使用as.character()函数在处理因子变量时非常方便。+1 - Brandon Bertelsen
6
如果你有多列呢? - geodex
1
@diliop:谢谢你,如果我想改变变量junk $ nm使其取值为“B”,“Y”,“Z”等怎么办? - simo

48

另一种有用的替换值的方法

library(plyr)
junk$nm <- revalue(junk$nm, c("B"="b"))

30

简短回答:

junk$nm[junk$nm %in% "B"] <- "b"

如果您还没有阅读过,请查看R Introduction中的索引向量(Index vectors)


编辑。如评论中所指出,此解决方案适用于字符向量,因此在处理您的数据时可能失败。

对于因子变量,最好的方法是更改级别:

levels(junk$nm)[levels(junk$nm)=="B"] <- "b"

简短补充:只有在右侧有一个集合,如 c("B","C") 时,使用 %in% 才真正有帮助。使用 junk$nm[junk$nm == "B"] 是更好的方法。 - Thilo
1
哦,另外一个重要的补充:这样做需要先将因子水平“b”添加到因子nm中。如果您想使用字符而不是因子,则diliop的版本实际上更好。 (始终首先考虑变量的类型!) - Thilo
这不适用于由@Kenny创建的数据,因为数据是因子。你是不是忘了一步,或者你有全局设置阻止将字符转换为因子? - Gavin Simpson
4
%in%== 的一个重要区别是处理 NA 值的方式:c(1,2,NA)==1 返回 TRUE, FALSE, NA,但是 c(1,2,NA) %in% 1 返回 TRUE, FALSE, FALSE。是的,我忘记检查这是否有效 :/ - Marek
这对我很有帮助,因为我特别想知道如何对字符向量中的所有值执行此操作。谢谢。 - The_Tams

21

由于您展示的数据是因子类型,这使得事情变得有些复杂。@diliop的答案通过将nm转换为字符变量来解决问题。要回到原始因子,需要进一步操作。

另一种选择是直接操作因子的水平。

> lev <- with(junk, levels(nm))
> lev[lev == "B"] <- "b"
> junk2 <- within(junk, levels(nm) <- lev)
> junk2
   nm val
1   A   a
2   b   b
3   C   c
4   D   d
5   A   e
6   b   f
7   C   g
8   D   h
9   A   i
10  b   j
11  C   k
12  D   l

这很简单,我经常忘记有一个levels()的替换函数。

编辑:如评论中@Seth所指出的,这可以用一行代码实现,而不会失去清晰度:

within(junk, levels(nm)[levels(nm) == "B"] <- "b")

6
好的,我会尽力进行翻译。针对您提出的问题,其中一个方法是使用 levels() 函数进行替换操作,这个方法我之前并不知道。另外,您提到了一行代码 junk <- within(junk, levels(nm)[levels(nm)=="B"] <- "b"),这行代码也可以用来进行替换操作。 - user399470
2
@Marek 拍头 这只是表明当你的睡眠时间已经过去时,不应该回复SO上的评论。让我们再试一次... - Gavin Simpson
@Seth确实不错。不确定为什么我要分开步骤?也许是为了阐述... - Gavin Simpson

12

用一个命令完成这个任务最简单的方法是使用which命令,也无需将因素转换为字符:

junk$nm[which(junk$nm=="B")]<-"b"

5
您在nm中创建了一个因子变量,因此您需要避免这样做或者向因子属性添加额外的级别。您还应该避免在data.frame()的参数中使用<-
选项1:
junk <- data.frame(x = rep(LETTERS[1:4], 3), y =letters[1:12], stringsAsFactors=FALSE)
junk$nm[junk$nm == "B"] <- "b"

选项2:

levels(junk$nm) <- c(levels(junk$nm), "b")
junk$nm[junk$nm == "B"] <- "b"
junk

@DWin感谢您对问题的贡献和需要考虑变量类型的建议。我接受了@diliop的答案,因为它是第一个有效的答案。我知道有很多关于<-与=之间的问题,但(如果可以简要回答)为什么应该在data.frame中使用=? - DQdlM
你不需要添加b作为一个级别,只需将级别为B的更改为b - Gavin Simpson
@KennyPeanuts:列名是一个问题,看看 a <- data.frame(x<-1:10)。它的列名不是 x,而是一个混乱的 x....1.10。最好使用 data.frame(x=1:10)。这样你就知道你的列名是什么了。 - IRTFM
@DWin 取决于您使用的建模函数。对于 lm() 函数,添加或替换一个级别都同样有效。在 rpart() 中,如果级别不是完全相同,则会失败。因此,这取决于我们使用的功能,但我们可以说,如果您在进行任何建模之前将数据结构化为所需的格式,则 predict() 函数始终有效。 - Gavin Simpson
@DWin感谢您对=与<-的澄清,这很有道理。到目前为止,我从评论中学到了很多意想不到的东西...这太棒了。 - DQdlM
显示剩余3条评论

4

您也可以使用 ifelse ,这非常简单易懂

junk$val <- ifelse(junk$nm == "B", "b", junk$val)

如果你仍然想通过“for循环”完成它,正确的方法是这样的。
for(i in 1:nrow(junk)){
  if(junk[i, "nm"] == "B"){
    junk[i, "val"] <- "b"
  }
}

junk
> junk
   nm val
1   A   a
2   B   b
3   C   c
4   D   d
5   A   e
6   B   b
7   C   g
8   D   h
9   A   i
10  B   b
11  C   k
12  D   l

2

如果你正在使用字符变量(请注意这里的 stringsAsFactors 参数为 false),你可以使用 replace 函数:

junk <- data.frame(x <- rep(LETTERS[1:4], 3), y <- letters[1:12], stringsAsFactors = FALSE)
colnames(junk) <- c("nm", "val")

junk$nm <- replace(junk$nm, junk$nm == "B", "b")
junk
#    nm val
# 1   A   a
# 2   b   b
# 3   C   c
# 4   D   d
# ...

这适用于所有变量类型。我只是用它在整数向量中插入NA到特定的索引位置。 - Jonas Lindeløv

1

我遇到了同样的问题,你也可以为每一列执行相同的操作。

 fix_junk <- function(x){
      #x <- as.character(x)
      x[x == "B"] <- "b"
      x
    }
    junk[] <- lapply(junk, fix_junk); junk # junk[] to get a data frame rather than a list
    junk[1:3] <- lapply(junk[1:3], fix_junk); junk

0
stata.replace<-function(data,replacevar,replacevalue,ifs) {
  ifs=parse(text=ifs)
  yy=as.numeric(eval(ifs,data,parent.frame()))
  x=sum(yy)
  data=cbind(data,yy)
  data[yy==1,replacevar]=replacevalue
  message=noquote(paste0(x, " replacement are made"))
  print(message)
  return(data[,1:(ncol(data)-1)])
}

请使用以下代码行调用此函数。
d=stata.replace(d,"under20",1,"age<20")

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