R 中的条件合并/替换

Conditional merge/replacement in R

提问人:Mike 提问时间:5/24/2011 最后编辑:C8H10N4O2Mike 更新时间:5/5/2022 访问量:34517

问:

我有两个数据框:

df1
x1  x2
1   a
2   b
3   c
4   d

df2
x1  x2
2   zz
3   qq

我想根据 df1$x1 和 df2$x2 之间的条件匹配将 df1$x2 中的一些值替换为 df1$x2 中的值,以产生:

df1
x1  x2
1   a
2   zz
3   qq
4   d
匹配 R-常见问题

评论


答:

28赞 Joris Meys 5/24/2011 #1

使用 ,假设 DF1 中的值是唯一的。match()

df1 <- data.frame(x1=1:4,x2=letters[1:4],stringsAsFactors=FALSE)
df2 <- data.frame(x1=2:3,x2=c("zz","qq"),stringsAsFactors=FALSE)

df1$x2[match(df2$x1,df1$x1)] <- df2$x2
> df1
  x1 x2
1  1  a
2  2 zz
3  3 qq
4  4  d

如果值不唯一,请使用:

for(id in 1:nrow(df2)){
  df1$x2[df1$x1 %in% df2$x1[id]] <- df2$x2[id]
}

评论

0赞 Aaron left Stack Overflow 5/24/2011
好。我用颠倒的论点写了这场比赛,但想不通为什么它比我想象的要复杂。我也会添加我的答案,因为它可以帮助其他人思考改变匹配中参数的顺序如何使事情变得更容易或更难。
0赞 Mike 5/24/2011
谢谢乔里斯。我正在使用“匹配”,但无法让它工作。
0赞 C8H10N4O2 7/21/2017
我添加了一个解决方案,该解决方案在 df1 中的非唯一值的情况下会表现得更好。
4赞 Aaron left Stack Overflow 5/24/2011 #2

您也可以通过另一种方式进行匹配,但它更复杂。Joris的解决方案更好,但我把它放在这里也是为了提醒你考虑你想匹配的方式。

df1 <- data.frame(x1=1:4, x2=letters[1:4], stringsAsFactors=FALSE)
df2 <- data.frame(x1=2:3, x2=c("zz", "qq"), stringsAsFactors=FALSE)
swap <- df2$x2[match(df1$x1, df2$x1)]
ok <- !is.na(swap)
df1$x2[ok] <- swap[ok]

> df1
  x1 x2
1  1  a
2  2 zz
3  3 qq
4  4  d
5赞 IRTFM 5/24/2011 #3

我看到 Joris 和 Aaron 都选择在没有因素的情况下构建示例。我当然可以理解这种选择。对于已经是因素的列的读者,也可以选择强制“性格”。有一种策略可以避免这种限制,并且还允许可能存在以下可能性:我认为这些索引不会使 Joris Meys 的解决方案无效,但到目前为止发布的 Aaron 的解决方案不会无效:df2df1

df1 <- data.frame(x1=1:4,x2=letters[1:4])
df2 <- data.frame(x1=c(2,3,5), x2=c("zz", "qq", "xx") )

它要求将水平扩展到包括两个因子变量的交集,然后还需要删除 match(df1$x1, df2$x1) 中的不匹配列(= NA 值)

 df1$x2 <- factor(df1$x2 , levels=c(levels(df1$x2), levels(df2$x2)) )
 df1$x2[na.omit(match(df2$x1,df1$x1))] <- df2$x2[which(df2$x1 %in% df1$x1)]
 df1
#-----------
  x1 x2
1  1  a
2  2 zz
3  3 qq
4  4  d

(请注意,最新版本的 R 在函数默认值中没有设置为 TRUE,这与 R 的大部分历史不同。stringsAsFactorsdata.frame

评论

1赞 Aaron left Stack Overflow 5/25/2011
好。因素可能很棘手,扩大水平的建议是有帮助的。不过,您最终会得到一个不需要的级别 (the )。df1$x2xx
0赞 IRTFM 5/25/2011
如果你想删除现在多余的关卡,那么这样做:df1$x2 <- factor(df1$x2)
7赞 C8H10N4O2 7/21/2017 #4

Joris 答案的第一部分很好,但是在 中是非唯一值的情况下,行向 for 循环在大型 data.frames 上不能很好地扩展。df1

您可以使用“更新联接”进行就地修改,这将非常快:data.table

library(data.table)
setDT(df1); setDT(df2)
df1[df2, on = .(x1), x2 := i.x2]

或者,假设你不关心维护行顺序,你可以使用 SQL-inspired :dplyr

library(dplyr)
union_all(
  inner_join( df1["x1"], df2 ), # x1 from df1 with matches in df2, x2 from df2
  anti_join(  df1, df2["x1"] )  # rows of df1 with no match in df2
) # %>% arrange(x1) # optional, won't maintain an arbitrary row order

其中任何一个都比行向 for 循环扩展得更好。

评论

0赞 Frank 7/21/2017
data.table 的习惯用法是 -- 就地修改(“替换 df1$x2 中的一些值”,正如 OP 所要求的那样),并且不需要设置键。它类似于 SQL 中的更新联接。df1[df2, on=.(x1), x2 := i.x2 ]
0赞 C8H10N4O2 7/21/2017
@Frank是的,你打败了我。
1赞 Frank 7/21/2017
还行。 不是一回事,仅供参考。df1[df2, x2 := df2[,x2]]
1赞 C8H10N4O2 7/21/2017
@Frank看起来 Hadley 决定不在 dplyr 中实现更新连接,这对我来说似乎是包中的一个弱点。
1赞 Frank 7/21/2017
是的,我看到了。Hadley 排除它们的理由非常薄弱(说他坚持使用纯 SQL),因为更新联接存在于某些类型的 SQL 中。归根结底,他想出的“语法”不够灵活。
8赞 moodymudskipper 2/26/2019 #5

我们可以使用 {powerjoin},并用coalesce_yx

library(powerjoin)
df1 <- data.frame(x1 = 1:4, x2 = letters[1:4], stringsAsFactors = FALSE)
df2 <- data.frame(x1 = 2:3, x2 = c("zz", "qq"), stringsAsFactors = FALSE)

power_left_join(df1, df2, by = "x1", conflict = coalesce_yx)
#>   x1 x2
#> 1  1  a
#> 2  2 zz
#> 3  3 qq
#> 4  4  d
3赞 mdb_ftl 4/26/2019 #6

这里是新的,但使用以下 dplyr 方法似乎也有效,与上面的答案
之一相似但略有不同

df3 <- anti_join(df1, df2, by = "x1")
df3 <- rbind(df3, df2)
df3
4赞 Wil 4/27/2019 #7

它可以用 .dplyr

library(dplyr)

full_join(df1,df2,by = c("x1" = "x1")) %>% 
  transmute(x1 = x1,x2 = coalesce(x2.y,x2.x))

  x1 x2
1  1  a
2  2 zz
3  3 qq
4  4  d
0赞 Sarah 5/5/2022 #8

从 dplyr 1.0.0 开始,有一个专门用于此的功能:

library(dplyr)
df1 <- data.frame(x1=1:4,x2=letters[1:4],stringsAsFactors=FALSE)
df2 <- data.frame(x1=2:3,x2=c("zz","qq"),stringsAsFactors=FALSE)


rows_update(df1, df2, by = "x1")

查看 https://stackoverflow.com/a/65254214/2738526