简体   繁体   中英

Match values between R dataframes without using a for-loop

I have the following dataframes:

db1 = data.frame(name = c('a', 'b', 'c', 'd'), age = c('10', '20', '30', '40'), tier = NA)
db2 = data.frame(name = c('a', 'a', 'c', 'b'), age = c('10', '10', '30', '20'), tier = c('1', '3', '4', '2'))

I want to enter the tier values from db2 into the same column in db1 if the name and age variables match.

I can do this with a for-loop but when we're dealing with thousands of rows this takes far too long. Is there a faster way to do this?

for (i in 1:nrow(db1)){
  for (j in 1:nrow(db2)){
    if (db1$name[i] == db2$name[j] & db1$age[i] == db2$age[j]){
      db1$tier[i] = db2$tier[j]
    }
  }
}

When taking the first in case it matches multiple times is also ok (you code takes the last), you can use match and for multiple columns with interaction .

db1$tier <- db2$tier[match(interaction(db1[c("name","age")]),
                           interaction(db2[c("name","age")]))]
db1
#  name age tier
#1    a  10    1
#2    b  20    2
#3    c  30    4
#4    d  40 <NA>

Or taking the last match (like your code is doing) using in addition `rev.

db1$tier <- rev(db2$tier)[match(interaction(db1[c("name","age")]),
                    rev(interaction(db2[c("name","age")])))]
db1
#  name age tier
#1    a  10    3
#2    b  20    2
#3    c  30    4
#4    d  40 <NA>

Drop the tier column and use merge -

db1$tier <- NULL
merge(db1, db2)

#  name age tier
#1    a  10    1
#2    a  10    3
#3    b  20    2
#4    c  30    4

If you want d in the final output use all.x = TRUE -

merge(db1, db2, all.x = TRUE)

#  name age tier
#1    a  10    1
#2    a  10    3
#3    b  20    2
#4    c  30    4
#5    d  40 <NA>

We can use merge + duplicated like below

subset(
  merge(db1, db2, by = c("name", "age"), all.x = TRUE),
  !duplicated(cbind(name, age)),
  select = -tier.x
)

which gives you

  name age tier.y
1    a  10      1
3    b  20      2
4    c  30      4
5    d  40   <NA>

This is a simple join.

library(dplyr)
db3<-full_join(db2,db1, by = c("name" = "name", "age" = "age"), suffix = c("", ".x"))

  name age tier tier.x
1    a  10    1     NA
2    a  10    3     NA
3    c  30    4     NA
4    b  20    2     NA
5    d  40 <NA>     NA

### i am assuming you want to have tier from db2 shown if they are not all NAs otherwise you can just drop before the join ###

db3$tier.x = NULL

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM