You can use a couple merge
calls and bind their outputs:
rbind(transform(merge(df1, df2, by.x = "key1", by.y = "sample"), sample = key1),
transform(merge(df1, df2, by.x = "key2", by.y = "sample"), sample = key2))
# key1 states key2 value sample
# 1 1 wash 2 green 1
# 2 5 oreg 6 blue 5
# 3 9 michi 10 steel 9
# 4 3 mont 4 grey 4
# 5 7 cali 8 gold 8
Another approach:
match.idx <- pmax(match(df1$key1, df2$sample),
match(df1$key2, df2$sample), na.rm = TRUE)
cbind(df1, df2[match.idx, ])
# states key1 key2 sample value
# 5 wash 1 2 1 green
# 4 mont 3 4 4 grey
# 3 oreg 5 6 5 blue
# 2 cali 7 8 8 gold
# 1 michi 9 10 9 steel