web-dev-qa-db-ja.com

Rでの条件付きマージ/置換

2つのデータフレームがあります。

df1
x1  x2
1   a
2   b
3   c
4   d

そして

df2
x1  x2
2   zz
3   qq

Df1 $ x1とdf2 $ x2の間の条件付き一致に基づいて、df1 $ x2の値の一部をdf2 $ x2の値に置き換えて次のようにします。

df1
x1  x2
1   a
2   zz
3   qq
4   d
23
Mike

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]
}
21
Joris Meys

Jorisの答えの最初の部分は適切ですが、df1の値が一意でない場合、行方向のforループは大きなdata.framesでは適切にスケーリングされません。

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

これらのいずれも、行単位のforループよりもはるかに優れたスケーリングを実現します。

5
C8H10N4O2

JorisとAaronはどちらも、要因なしで例を作成することを選択したようです。私は確かにその選択を理解することができます。すでに要因となっている列を持つ読者の場合、「文字」への強制のオプションもあります。その制約を回避し、df1にないインデックスがdf2に存在する可能性を考慮した戦略があります。これにより、Joris Meysは無効になりますが、これまでに投稿されたAaronsソリューションは無効になりません。

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
4
42-

私のパッケージからeatを使用することができます safejoin 、そしてrhsからの一致を「パッチ」します列が競合する場合のlhs。

# devtools::install_github("moodymudskipper/safejoin")
library(safejoin)
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)

eat(df1, df2, .by = "x1", .conflict = "patch")
#   x1 x2
# 1  1  a
# 2  2 zz
# 3  3 qq
# 4  4  d
3

ここでは新しいですが、次のdplyrアプローチを使用しても機能するようです
上記の回答の1つと似ていますが、わずかに異なります

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

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
1
Wil

他の方法でも一致させることでそれを行うことができますが、それはより複雑です。 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