#r #date #join #merge #data.table
Вопрос:
Я хотел бы объединить два фрейма данных (df1 и df2) на основе ближайшей даты, но только в том случае, если даты из двух фреймов данных находятся в пределах 3 месяцев друг от друга.
Я хотел бы «полностью соединить» два фрейма данных (т. Е. Включить все строки как из df1, так и из df2).
Кроме того, я не хочу, чтобы строка дублировалась из одного фрейма данных, если в другом фрейме данных в течение 3-месячного окна имеется несколько строк.
Как бы я хотел, чтобы данные выглядели
См. также df3 в ПРИМЕРЕ ДАННЫХ.
ID Date.df1 Date.df2 V1_df1 V2_df1 V1_df2 V2_df2
100 NA 07/11/2015 NA NA 93.3 93.3
100 01/11/2015 03/11/2015 9.3 10.6 93.3 95.5
100 23/12/2016 27/12/2016 8 10.3 97.78 97.78
100 04/11/2017 13/11/2017 9.3 11 98.9 98.89
100 09/11/2018 NA 10.3 9.6 NA NA
101 07/11/2015 07/11/2015 7 6.6 97.78 97.78
101 21/01/2017 19/12/2016 6 7.3 95.7 95.5
101 18/11/2017 NA 7.6 6.6 NA NA
101 22/01/2019 NA 6.5 7 NA NA
102 27/09/2017 26/08/2017 5 7 94.8 94.8
102 01/10/2018 NA 8.6 7.3 NA NA
102 15/09/2019 NA 9 8 NA NA
103 NA 14/11/2015 NA NA 97.7 97
Что я пробовал
Я попробовал следующее (основываясь на ответе crestor ниже), но когда я запускаю его с моими полными наборами данных, некоторые строки не включаются. Я думаю, что для строк во фрейме данных, когда есть соответствующие данные в другом наборе данных, но это превышает три месяца… Как это можно преодолеть?
df1 %>%
full_join(df2, by = "ID") %>%
mutate(diff = abs(Date.df2 - Date.df1)) %>%
filter(
(!is.na(Date.df2) amp;
(is.na(Date.df1) | (diff < months(3)))) |
(!is.na(Date.df1) amp;
(is.na(Date.df2) | (diff < months(3))))
) %>%
arrange(ID, Date.df1, Date.df2) %>%
group_by(ID, Date.df1) %>%
filter(row_number() == 1) %>%
ungroup()
Я также попробовал ниже, но не уверен, как это изменить, чтобы строки соединялись только в том случае, если они находятся в течение трех месяцев друг от друга.
library(data.table)
# coerce to data.table and append join columns to preserve the original columns
setDT(df1)[, join_date := Date.df1]
setDT(df2)[, join_date := Date.df2]
# rolling join, ordered by ID and date
df1[df2, on = .(ID, join_date), roll = "nearest"] [order(ID, join_date)]
Приведенный выше код выдает этот вывод:
ID Date.df1 V1_df1 V2_df1 join_date Date.df2 V1_df2 V2_df2
100 01/11/2015 9.3 10.6 03/11/2015 03/11/2015 93.3 95.5
100 01/11/2015 9.3 10.6 07/11/2015 07/11/2015 93.3 93.3
100 23/12/2016 8 10.3 27/12/2016 27/12/2016 97.78 97.78
100 04/11/2017 9.3 11 13/11/2017 13/11/2017 98.9 98.89
101 07/11/2015 7 6.6 07/11/2015 07/11/2015 97.78 97.78
101 21/01/2017 6 7.3 19/12/2016 19/12/2016 95.7 95.5
102 27/09/2017 5 7 26/08/2017 26/08/2017 94.8 94.8
103 NA NA NA 14/11/2015 14/11/2015 97.7 97
ПРИМЕРЫ ДАННЫХ
df1 <- structure(list(ID = c(100L, 100L, 100L, 100L, 101L, 101L, 101L,
101L, 102L, 102L, 102L), Date.df1 = structure(c(16740, 17158,
17474, 17844, 16746, 17187, 17488, 17918, 17436, 17805, 18154
), class = "Date"), V1_df1 = c(9.3, 8, 9.3, 10.3, 7, 6, 7.6,
6.5, 5, 8.6, 9), V2_df1 = c(10.6, 10.3, 11, 9.6, 6.6, 7.3, 6.6,
7, 7, 7.3, 8)), row.names = c(NA, -11L), class = "data.frame")
df2 <- structure(list(ID = c(100L, 100L, 100L, 100L, 101L, 101L, 102L,
103L), Date.df2 = structure(c(16742, 16746, 17162, 17483, 16746,
17154, 17404, 16753), class = "Date"), V1_df2 = c(93.3, 93.3,
97.78, 98.9, 97.78, 95.7, 94.8, 97.7), V2_df2 = c(95.5, 93.3,
97.78, 98.89, 97.78, 95.5, 94.8, 97)), row.names = c(NA, -8L), class = "data.frame")
Ответ №1:
Что-то похожее на это:
library(lubridate)
library(tidyverse)
df1 <- df1 %>%
as_tibble() %>%
mutate(across(starts_with("Date."), dmy))
df2 <- df2 %>%
as_tibble() %>%
mutate(across(starts_with("Date."), dmy))
df3 <- df3 %>%
as_tibble() %>%
mutate(across(starts_with("Date."), dmy)) %>%
arrange(ID, Date.df1, Date.df2)
df1 %>%
left_join(df2, by = "ID") %>%
mutate(diff = abs(Date.df2 - Date.df1)) %>%
filter(
!is.na(Date.df2) amp;
(is.na(Date.df1) | (diff < months(3)))
) %>%
arrange(ID, Date.df1, Date.df2) %>%
group_by(ID, Date.df1) %>%
filter(row_number() == 1) %>%
ungroup()
#> # A tibble: 8 x 8
#> ID Date.df1 V1_df1 V2_df1 Date.df2 V1_df2 V2_df2 diff
#> <int> <date> <dbl> <dbl> <date> <dbl> <dbl> <drtn>
#> 1 100 2015-11-01 9.3 10.6 2015-11-03 93.3 95.5 2 days
#> 2 100 2016-12-23 8 10.3 2016-12-27 97.8 97.8 4 days
#> 3 100 2017-11-04 9.3 11 2017-11-13 98.9 98.9 9 days
#> 4 100 NA NA NA 2015-11-03 93.3 95.5 NA days
#> 5 101 2015-11-07 7 6.6 2015-11-07 97.8 97.8 0 days
#> 6 101 2017-01-21 6 7.3 2016-12-19 95.7 95.5 33 days
#> 7 102 NA 5 7 2017-08-26 94.8 94.8 NA days
#> 8 103 NA NA NA 2015-11-14 97.7 97 NA days
Комментарии:
1. Спасибо за все вышесказанное. Я хочу включить все строки из df1 и df2 (я обновил вопрос, чтобы сделать его более понятным после вашего ответа). Используя предложенный вами код, я отредактировал left_join до full_join, а также обновил фильтр, однако не все строки включены (см. Мой отредактированный код, о котором идет речь выше).
2. Спецификации не соответствуют результату примера «df3». Если вы притворяетесь, что получили разумное предложение, вам нужно построить правильный пример.
Ответ №2:
Вероятно, есть более краткий способ сделать это, но ниже дан ответ на мой вопрос. Об этом сообщил ответ от крестора.
library(lubridate)
library(tidyverse)
# dates in date format
df1$Date.df1 <- as.Date(df1$Date.df1, "%d/%m/%Y")
df2$Date.df2 <- as.Date(df2$Date.df2, "%d/%m/%Y")
#join rows in df1 and df2 that are nearest in submission date and within two months of each other
df1 <- df1 %>%
as_tibble() %>%
mutate(across(starts_with("Date."), ymd))
df2 <- df2 %>%
as_tibble() %>%
mutate(across(starts_with("Date."), ymd))
df_join <- df1 %>%
inner_join(df2, by = "ID") %>%
mutate(timediffvar = abs(time_length(difftime(Date.df1, Date.df2),"months"))) %>%
filter(
(timediffvar <= 3)
) %>%
arrange(timediffvar) %>%
group_by(ID, Date.df1) %>%
filter(row_number() == 1) %>%
ungroup() %>%
arrange(timediffvar) %>%
group_by(ID, Date.df2) %>%
filter(row_number() == 1) %>%
ungroup()
# identify entries not in the joined above
df1_notjoined <- anti_join(df1, df_join, by=c("ID", "Date.df1"))
df2_notjoined <- anti_join(df2, df_join, by=c("ID", "Date.df2"))
# join all entries together
mergevars_df1 <- names(df1)
mergevars_df2 <- names(df2)
df3 <- df_join %>%
full_join(df1_notjoined, by = mergevars_df1) %>%
full_join(df2_notjoined, by = mergevars_df2) %>%
arrange(ID, Date.df1, Date.df2) %>%
select("ID", "Date.df1", "Date.df2", "V1_df1","V2_df1","V1_df2","V2_df2")