在 Dataframe 2中从 Dataframe 1查找接近匹配

eqoofvh9  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(89)

我有两个 Dataframe ,对于 Dataframe 1中的每个特定行,我试图根据定义的重要标准(偏移、年龄、级别)从 Dataframe 2中找到最接近的匹配。举个例子,假设我有 Dataframe 1:

shift_1 <- c(1,1,0,2)
length_1 <- c(100,120,5,70)
level_1<- c(1,3,5,4)
age_1 <- c(4.5,3.2,3,2.5)

df_1 <- data.frame(shift_1,level_1,age_1,length_1)

  shift_1 level_1 age_1 length_1
1       1       1   4.5      100
2       1       3   3.2      120
3       0       5   3.0        5
4       2       4   2.5       70

对于该 Dataframe 的每一行,我想在 Dataframe 2中找到最接近的匹配,如下所示:

shift_2 <- c(1,1,2,1,0)
length_2 <- c(100,200,40,180,10)
level_2<- c(3,4,4,3,5)
age_2 <- c(2.5,5.5,2.2,3.1,5)

df_2 <- data.frame(shift_2,level_2,age_2,length_2)

  shift_2 level_2 age_2 length_2
1       1       3   2.5      100
2       1       4   5.5      200
3       2       4   2.2       40
4       1       3   3.1      180
5       0       5   5.0       10

根据这些标准:shift必须完全匹配。级别必须完全匹配。年龄**将接受20%**差异。

如果找到匹配:我们要加上匹配行的索引号匹配信息,否则我们将放NA。所以预期的结果是这样的:

shift_r level_r age_r length_r index shift_match level_match age_match length_match
1       1       1   4.5      100    NA          NA          NA        NA           NA
2       1       3   3.2      120     4           1           3       3.1          180
3       0       5   3.0        5    NA          NA          NA        NA           NA
4       2       4   2.5       70     3           2           4       2.2           40

你能建议我该如何处理这件事吗?有没有什么库可以让这个任务更容易完成?

woobm2wo

woobm2wo1#

您需要一个“非等”或“范围”联接。这在R的fuzzyjoindata.table包中实现。由于SQL也支持它,因此也可以使用sqldf
遗憾的是,dplyr本机不支持此功能。由于SQL支持此操作,因此如果您的数据位于数据库中,则dbplyr将允许它使用其sql_on,但不是原生的。

  • 编辑 *:添加dplyr及其最近添加的join_by

首先,我们需要添加20%的公差:

df_1$age_1_start <- df_1$age_1 * 0.8
df_1$age_1_end <- df_1$age_1 * 1.2
df_1
#   shift_1 level_1 age_1 length_1 age_1_start age_1_end
# 1       1       1   4.5      100        3.60      5.40
# 2       1       3   3.2      120        2.56      3.84
# 3       0       5   3.0        5        2.40      3.60
# 4       2       4   2.5       70        2.00      3.00

模糊连接

fuzzyjoin::fuzzy_left_join(
  df_1, df_2,
  by = c("shift_1" = "shift_2", "level_1" = "level_2",
         "age_1_start" = "age_2", "age_1_end" = "age_2"),
  match_fun = list(`==`, `==`, `<=`, `>=`))
#   shift_1 level_1 age_1 length_1 age_1_start age_1_end shift_2 level_2 age_2 length_2
# 1       1       1   4.5      100        3.60      5.40      NA      NA    NA       NA
# 2       1       3   3.2      120        2.56      3.84       1       3   3.1      180
# 3       0       5   3.0        5        2.40      3.60      NA      NA    NA       NA
# 4       2       4   2.5       70        2.00      3.00       2       4   2.2       40

data.table

library(data.table)
DT_1 <- as.data.table(df_1) # must include age_1_start and age_1_end from above
DT_2 <- as.data.table(df_2)

DT_2[DT_1, on = .(shift_2 == shift_1, level_2 == level_1, age_2 >= age_1_start, age_2 <= age_1_end)]
#    shift_2 level_2 age_2 length_2 age_2.1 age_1 length_1
# 1:       1       1  3.60       NA    5.40   4.5      100
# 2:       1       3  2.56      180    3.84   3.2      120
# 3:       0       5  2.40       NA    3.60   3.0        5
# 4:       2       4  2.00       40    3.00   2.5       70

这个包倾向于根据右边的名称重命名左边的(DT_1)连接,这可能会让人感到沮丧。为此,您需要在之后进行一些清理。

sqldf

sqldf::sqldf(
  "select t1.*, t2.*
   from df_1 t1
     left join df_2 t2 on t1.shift_1 = t2.shift_2 and t1.level_1 = t2.level_2
       and t1.age_1_start <= t2.age_2 and t1.age_1_end >= t2.age_2")
#   shift_1 level_1 age_1 length_1 age_1_start age_1_end shift_2 level_2 age_2 length_2
# 1       1       1   4.5      100        3.60      5.40      NA      NA    NA       NA
# 2       1       3   3.2      120        2.56      3.84       1       3   3.1      180
# 3       0       5   3.0        5        2.40      3.60      NA      NA    NA       NA
# 4       2       4   2.5       70        2.00      3.00       2       4   2.2       40

dplyr

library(dplyr)
df_1 %>%
  mutate(
    age_1_start = age_1 * 0.8,
    age_1_end = age_1 * 1.2
  ) %>%
  left_join(df_2, join_by(shift_1 == shift_2, level_1 == level_2, 
                          age_1_start <= age_2, age_1_end >= age_2))
#   shift_1 level_1 age_1 length_1 age_1_start age_1_end age_2 length_2
# 1       1       1   4.5      100        3.60      5.40    NA       NA
# 2       1       3   3.2      120        2.56      3.84   3.1      180
# 3       0       5   3.0        5        2.40      3.60    NA       NA
# 4       2       4   2.5       70        2.00      3.00   2.2       40

如果您了解SQL,那么最后一个可能是最直观和最容易吸收的。但是请记住,对于较大的帧,它会将整个帧复制到内存存储SQLite示例中。这不是“免费的”。
fuzzyjoin实现给了您很大的权力,它的参数似乎(对我来说)很容易遵循。结果正如我所期望的那样命名。然而,它是三种实现中最慢的(使用此数据)。(只有当您的真实的数据“非常”大时,才应该关注这一点。)
如果你还不知道data.table,尽管它的速度极快,但它的R方言对不知情的人来说可能有点模糊。我相信它和fuzzyjoin一样强大,尽管我还没有测试所有的极端情况,看看一个是否支持另一个不支持的东西。

bench::mark(
  fuzzyjoin = fuzzyjoin::fuzzy_left_join(
    df_1, df_2,
    by = c("shift_1" = "shift_2", "level_1" = "level_2",
           "age_1_start" = "age_2", "age_1_end" = "age_2"),
    match_fun = list(`==`, `==`, `<=`, `>=`)),
  data.table = DT_2[DT_1, on = .(shift_2 == shift_1, level_2 == level_1, age_2 >= age_1_start, age_2 <= age_1_end)],
  sqldf = sqldf::sqldf(
    "select t1.*, t2.*
     from df_1 t1
       left join df_2 t2 on t1.shift_1 = t2.shift_2 and t1.level_1 = t2.level_2
         and t1.age_1_start <= t2.age_2 and t1.age_1_end >= t2.age_2"),
  check = FALSE
)
# # A tibble: 3 x 13
#   expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory        time      gc       
#   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list>        <list>    <list>   
# 1 fuzzyjoin  134.12ms 143.24ms      6.98     107KB     6.98     2     2      286ms <NULL> <Rprofmem[,3~ <bch:tm ~ <tibble ~
# 2 data.table   2.14ms   2.63ms    335.       114KB     2.06   163     1      487ms <NULL> <Rprofmem[,3~ <bch:tm ~ <tibble ~
# 3 sqldf       21.14ms  22.72ms     42.9      184KB     4.52    19     2      442ms <NULL> <Rprofmem[,3~ <bch:tm ~ <tibble ~

相关问题