如果其他列匹配 100%,则查找一列中值的最接近匹配

Find closest match for values in one column, if additional columns match 100%

我有两个不等长的数据帧。我想将 value2 从 df2 添加到 df1,其中 value1a 和 value1b 之间的差异很小。并非 df1 中的所有行都会收到来自 df 2 的匹配项,因为 df2 更短。在找到最接近的匹配项(value1a 和 value1b)之前,st、year 和 doy 必须在 df1 和 df2 之间匹配,因为数据集包含几年内在同一站点的多次采样。

st <- c("ST1", "ST2", "ST2", "ST2", "ST3")
year <- c(2011, 2011, 2012, 2012, 2013)
doy <- c(20,29,4,4,20)

value1a <- c(200, 250, 240, 250, 260)
value1b <- c(201, 258, 240, 251, 180)
value2 <- c(5,6,7,8.5,10)

df1 <- data.frame(st, year, doy, value1a)
df2 <- data.frame(st, year, doy, value1b, value2); df2 <- df2[1:4,]

我缩短了 df2 以保持相同的列名,但表明它可能具有与 df1 不同的行数和信息。 理想情况下,输出应该是这样的:

     st year    doy value1a value1b value2
1   ST1 2011    20  200      201    5.0
2   ST2 2011    29  250      258    6.0
3   ST2 2012    4   240      240    7.0
4   ST2 2012    4   250      251    8.5
5   ST3 2013    20  260      NA     NA

我研究了 closest.match() 和相关函数,但是当其他列中的先前匹配必须成功时,我无法让它工作。有什么想法吗?

*编辑:我根据下面的建议更改了数据框,希望它能澄清问题。我向所有已经努力回答的人道歉!

为了以防海洋学家在附近的情况下进行实际说明:我正在处理水柱数据。在一年的同一天的同一站点,在第一次采样时根据水中深度的值1a获得某些数据。然后在一年的同一天重复这一点,只是稍晚(因此年和 doy 匹配),但自动记录的 value1b 略有不同。我想将仅在第二次采样中获得的 value2 与第一次采样中获得的数据进行匹配。

这是一个基本的 R 解决方案:

df3 <- merge(df1, df2, by.x = c("st", "year", "doy"), by.y = c("st", "year", "doy2"), all = TRUE)

您也可以使用 dplyr:

df3 <- full_join(df1,df2,by = c("st" = "st", "year" = "year", "doy" = "doy2"))

这给你:

   st year doy value1a value1b value2
1 ST1 2011  20     200      NA     NA
2 ST1 2011  21      NA     201    5.0
3 ST2 2011  29     250     258    6.0
4 ST2 2012   4     240     240    7.0
5 ST2 2013  20     260     180    8.5

这里有一个来自 dplyr 包的长替代品。

  library(dplyr)
    df3 <- data.frame(df1, df2)
    df3 %>% 
      mutate(value1a = ifelse(year == year.1 & st == st.1 & doy == doy2, value1a, NA),
             value1b = ifelse(year == year.1 & st == st.1 & doy == doy2, value1b, NA),
             value2 = ifelse(year == year.1 & st == st.1 & doy == doy2, value2, NA)) %>% 
      select(st, year, doy, value1a, value1b, value2)

#   st year doy value1a value1b value2
#1 ST1 2011  20      NA      NA     NA
#2 ST2 2011  29     250     258      6
#3 ST2 2012   4     240     240      7
#4 ST2 2013  20     260     180    8.5

试图关闭你的 “value1a 和 value1b 之间的差异很小” 语句,这是一个两步操作:merge/join,然后根据区别。

因为当前样本数据没有测试这个要求,我将复制其中一个值略有不同的行,以表明正在选择最小值。

df2 <- rbind(df2, transform(df2[2,], value1b = 300, value2 = 6.1))
df2
#     st year doy2 value1b value2
# 1  ST1 2011   21     201    5.0
# 2  ST2 2011   29     258    6.0
# 3  ST2 2012    4     240    7.0
# 4  ST2 2013   20     180    8.5
# 21 ST2 2011   29     300    6.1

此外,由于我不确定 df1 行相对于 styeardoy 是否完全唯一,我将添加一个每个步骤中的 rn(行号)字段,以便我可以确定正确减少。 (也就是说,如果我不这样做,那三个字段不是唯一的,那么我就会过度减少你的数据。)

dplyr

library(dplyr)
df1 %>%
  mutate(rn = row_number()) %>%
  left_join(., df2, by = c("st", "year", doy = "doy2")) %>%
  arrange(abs(value1a - value1b)) %>%
  group_by(rn) %>%
  slice(1) %>%
  ungroup()
# # A tibble: 4 x 7
#   st     year   doy value1a    rn value1b value2
#   <chr> <dbl> <dbl>   <dbl> <int>   <dbl>  <dbl>
# 1 ST1    2011    20     200     1      NA   NA  
# 2 ST2    2011    29     250     2     258    6  
# 3 ST2    2012     4     240     3     240    7  
# 4 ST2    2013    20     260     4     180    8.5

data.table

一些注意事项:

  • 我使用 magrittr 的管道 (%>%) 纯粹是为了演示,因为我认为它在视觉上很好地打破了一切;不需要
  • data.table合并事物的方式,doy字段保留table的名称(df2) ,所以我将其重命名为 data.table::setnames
  • data.table 中的一个小 feature/bug 防止在最后一个 [ 操作是 := 赋值时第一次打印结果;这是 FAQ 2.23,并且仅用于美观,如果还有其他操作则不是一个因素;正因为如此,我添加了一个看似空的%>% .[]来打印到这里的控制台
library(data.table)
library(magrittr)
setDT(df1)
setDT(df2)
df1[, rn := .I] %>%
  df2[., on = .(st, year, doy2 = doy)] %>%
  .[ order(abs(value1a - value1b)), ] %>%
  .[, .SD[1,], by = .(rn) ] %>%
  .[, rn := NULL] %>%
  setnames(., old = "doy2", new = "doy") %>%
  .[]
#     st year doy value1b value2 value1a
# 1: ST2 2012   4     240    7.0     240
# 2: ST2 2011  29     258    6.0     250
# 3: ST2 2013  20     180    8.5     260
# 4: ST1 2011  20      NA     NA     200

(当找到 multiple 匹配项时,有一些方法可以告诉 data.table 做什么。我没有测试看是否可以保证顺序,但是如果有办法知道有把握,那么或许可以简化一点。)

SQL-喜欢

(使用 df1df2 的原始非 data.table 版本。)

出于类似的原因,这会创建几个行号计数器,类似于 data.table 解决方案中的 rn

sqldf::sqldf("
  with t1 as (
    select df1.*, row_number() over () as rn1
    from df1
  ),
  t12 as (
    select t1.*, t2.value2,
      row_number() over (partition by t1.rn1) as rn2
    from t1
      left join df2 t2 on t1.st=t2.st and t1.year=t2.year and t1.doy=t2.doy2
    order by abs(value1a - value1b)
  )
  select st, year, doy, value1a, value2 from t12 where rn2 = 1")
#    st year doy value1a value2
# 1 ST1 2011  20     200     NA
# 2 ST2 2012   4     240    7.0
# 3 ST2 2011  29     250    6.0
# 4 ST2 2013  20     260    8.5

所以我将 dplyr 与 left_join() 参数一起使用,但它本身并没有得到正确的答案。然后我使用 group_by() 对行进行分组。将它们分组后,您可以通过比较 value1a 和 value1b 的不同值的绝对值相互减去来过滤掉彼此不接近的值。您在 filter() 中包含了一个额外的逻辑语句,因此在评估其他 NA 语句时它不会自动删除带有 NA 的行。所以它看起来像这样:

library(dplyr)
df3 <- left_join(df1, df2, by = c("st", "year", "doy"))
df3 %>% group_by(st, year, doy, value1a) %>%
  filter(is.na(value1b) | abs(value1a - value1b) == 
           min(abs(value1a - value1b)))

我得到的结果如下:

st     year   doy value1a value1b value2
  <fct> <dbl> <dbl>   <dbl>   <dbl>  <dbl>
1 ST1    2011    20     200     201    5  
2 ST2    2011    29     250     258    6  
3 ST2    2012     4     240     240    7  
4 ST2    2012     4     250     251    8.5
5 ST3    2013    20     260      NA   NA 

这是在 data.table 中使用滚动连接的选项:

library(data.table)
setDT(df1)
setDT(df2)
df2[df1, on=.(st, year, doy, value1b=value1a), roll="nearest",
    c(.(value1a=value1a), mget(names(df2)))]

输出:

   value1a  st year doy value1b value2
1:     200 ST1 2011  20     200    5.0
2:     250 ST2 2011  29     250    6.0
3:     240 ST2 2012   4     240    7.0
4:     250 ST2 2012   4     250    8.5
5:     260 ST3 2013  20     260     NA