R中的条件合并/替换

31
我有两个数据框:
df1
x1  x2
1   a
2   b
3   c
4   d

df2
x1  x2
2   zz
3   qq

我想要用df2$x2中的值替换df1$x2中的一些值,基于df1$x1和df2$x2之间的条件匹配来生成:

df1
x1  x2
1   a
2   zz
3   qq
4   d
8个回答

28

使用match()函数,假设df1中的值是唯一的。

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]
}

不错。我用反向参数编写了匹配,但无法弄清为什么它比我认为的要复杂。我也会添加我的答案,因为它可能帮助其他人思考如何改变匹配中参数的顺序可以使事情变得更容易或更难。 - Aaron left Stack Overflow
谢谢Joris。我一直在使用“match”,但是无法让它正常工作。 - Mike
我已经添加了一个解决方案,可以在df1中存在非唯一值的情况下更好地执行。 - C8H10N4O2

8
我们可以使用 {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

7

Joris的回答前半部分很好,但是对于df1中非唯一值的情况,基于行的for循环在大型数据框中无法很好地扩展。

您可以使用data.table“update join”进行就地修改,这将非常快速:

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

假设您不关心保持行顺序,您可以使用受SQL启发的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

任何一种方法都比逐行循环更具可扩展性。

data.table的惯用语是df1[df2, on=.(x1), x2 := i.x2 ]--在原地修改(如OP所要求的“替换df1$x2中的某些值”),不需要设置键。它类似于SQL中的更新连接。 - Frank
@Frank,没错,你比我先完成了。 - C8H10N4O2
1
好的。df1[df2, x2 := df2[,x2]] 不是同一件事,供您参考。 - Frank
1
@Frank 看起来 Hadley 决定不在 dplyr 中实现 update join,这对我来说似乎是该软件包的一个弱点。 - C8H10N4O2
1
是的,我看到了。Hadley排除它们的原因相当薄弱(他说他坚持使用纯SQL),因为在某些SQL版本中存在更新连接。这只是因为他提出的“语法”不够灵活。 - Frank
显示剩余3条评论

5

我看到Joris和Aaron都选择了不使用因子来构建示例。我完全理解这种选择。对于那些已经是因子的列的读者,还可以选择强制转换为“字符”。有一种策略可以避免这种限制,并且还可以允许df2中可能存在不在df1中的索引,我认为这将使Joris Meys的解决方案无效,但不会影响Aaron迄今为止发布的解决方案:

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 版本在 data.frame 函数默认情况下不再将 stringsAsFactors 设置为 TRUE,这与 R 的大部分历史不同。


1
不错。因子可能会很棘手,扩展级别的建议很有帮助。但是你最终在 df1$x2 中会得到一个不必要的级别(即 xx)。 - Aaron left Stack Overflow
如果您想要删除现在多余的级别,请执行以下操作:df1$x2 <- factor(df1$x2) - IRTFM

4

你也可以通过另一种方式进行匹配,但这更加复杂。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

4

这可以用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

3

我是新来的,但使用以下dplyr方法似乎也可以起作用
与上面的答案类似但略有不同

df3 <- anti_join(df1, df2, by = "x1")
df3 <- rbind(df3, df2)
df3

0

从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://dev59.com/u1UK5IYBdhLWcg3wmw4S#65254214


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