如果其他列匹配 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
行相对于 st
、year
和 doy
是否完全唯一,我将添加一个每个步骤中的 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
(当找到 mult
iple 匹配项时,有一些方法可以告诉 data.table
做什么。我没有测试看是否可以保证顺序,但是如果有办法知道有把握,那么或许可以简化一点。)
SQL-喜欢
(使用 df1
和 df2
的原始非 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
我有两个不等长的数据帧。我想将 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
行相对于 st
、year
和 doy
是否完全唯一,我将添加一个每个步骤中的 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
(当找到 mult
iple 匹配项时,有一些方法可以告诉 data.table
做什么。我没有测试看是否可以保证顺序,但是如果有办法知道有把握,那么或许可以简化一点。)
SQL-喜欢
(使用 df1
和 df2
的原始非 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