#r #dplyr #merge #inner-join
#r #dplyr #слияние #внутреннее соединение
Вопрос:
У меня есть два фрейма данных, которые я хочу объединить с помощью общей переменной. Некоторые наблюдения отображаются в одних данных, но не в других, и наоборот. При объединении я хочу сохранить только те наблюдения, которые отображаются в обоих фреймах данных, и поэтому dplyr::inner_join()
это уместно.
Однако есть исключение. Есть некоторые наблюдения, которые я хотел бы включить в объединенные данные, несмотря ни на что. То есть, даже если они не отображаются в обоих исходных фреймах данных. Указание на то, какие из них являются теми «особыми» наблюдениями, которые следует сохранить, задается в виде определенных значений в определенных столбцах.
Пример
Я хочу объединить следующие фреймы данных ( df_population
и df_gdp
)
1. df_population
library(tibble)
library(dplyr)
## helper function
myFun <- function(n = 5000) {
a <- do.call(paste0, replicate(5, sample(LETTERS, n, TRUE), FALSE))
paste0(a, sprintf("d", sample(9999, n, TRUE)), sample(LETTERS, n, TRUE))
}
set.seed(2021)
df_population <-
tribble(~country, ~population,
"australia", 24.99,
"united_kingdom", 66.65,
"france", 66.99,
"spain", 46.94,
"canada", 37.59,
"brazil", 209.5) %>%
mutate(col_of_strings = c(myFun(5), "dont_leave_me_behind"))
## # A tibble: 6 x 3
## country population col_of_strings
## <chr> <dbl> <chr>
## 1 australia 25.0 GLNWN9968R
## 2 united_kingdom 66.6 FTELH3426F
## 3 france 67.0 NFOSZ6335V
## 4 spain 46.9 ZFGRD8875F
## 5 canada 37.6 GFICE2875O
## 6 brazil 210. dont_leave_me_behind
2. df_gdp
df_gdp <-
tribble(~country, ~gdp_growth,
"australia", 2.9,
"united_kingdom", 1.4,
"france", 1.7,
"spain", 2.4,
"canada", 1.9,
"greece", 1.9) %>%
mutate(col_of_strings = sample(c(myFun(5), "dont_leave_me_behind")))
## # A tibble: 6 x 3
## country gdp_growth col_of_strings
## <chr> <dbl> <chr>
## 1 australia 2.9 dont_leave_me_behind
## 2 united_kingdom 1.4 RQHHI9679V
## 3 france 1.7 PFSZX1552L
## 4 spain 2.4 BQTBY7537E
## 5 canada 1.9 OECIK9698V
## 6 greece 1.9 VXDQQ4718J
Моя проблема
Обычно я бы выбрал
dplyr::inner_join(df_population, df_gdp, by = "country")
Но:
Хотя мне нужны только страны, которые являются общими для обоих фреймов данных, я все же хочу включить любую страну, которая имеет col_of_strings == dont_leave_me_behind
Я надеюсь, что есть простое решение для этого. Спасибо!
Комментарии:
1. Я бы
full_join()
, а затемfilter()
результаты.2. Возможно, вы могли бы обновить свои данные, чтобы включить строки, которые неправильно соединяются? В нынешнем виде ваши строки «dont_leave_me_behind» сохраняются в
inner_join
.3. @M.Viking Это был бы один из способов. Однако учтите, что реальные наборы данных, с которыми я имею дело, более сложны и намного больше по размеру, чем здесь. Это
full_join()
будет довольно неэффективно (с точки зрения вычислений).4. Является
country
ли поле уникальным идентификатором?5. Приятно, что вы не используете
set.seed(2020)
… многие предположили, что это ужасная отправная точка любого случайного процесса 😉
Ответ №1:
На ум приходят три способа.
- Согласно предложению @M.Viking,
full_join
сначала, а затем фильтруйте.dplyr::full_join(df_population, df_gdp, by = "country") %>% dplyr::filter( col_of_strings.y == "dont_leave_me_behind" | !is.na(col_of_strings.x), col_of_strings.x == "dont_leave_me_behind" | !is.na(col_of_strings.y) ) # # A tibble: 6 x 5 # country population col_of_strings.x gdp_growth col_of_strings.y # <chr> <dbl> <chr> <dbl> <chr> # 1 australia 25.0 LQMPB3662R 2.9 VKBCE2969H # 2 united_kingdom 66.6 WDXVX4684T 1.4 FMAKF4470M # 3 france 67.0 VJHBH0078U 1.7 dont_leave_me_behind # 4 spain 46.9 XFJPD7687T 2.4 RMPYK2467U # 5 canada 37.6 AQRCR0724P 1.9 JXMMZ3736X # 6 brazil 210. dont_leave_me_behind NA <NA>
- Выполните внутреннее объединение, извлеките недостающие строки из каждого фрейма и
bind_rows
верните их обратно. Эти шаги требуют некоторого переименования из-за.x
/.y
names после объединения.tmp1 <- dplyr::inner_join(df_population, df_gdp, by = "country") missing_pop <- df_population %>% dplyr::filter( col_of_strings == "dont_leave_me_behind", !country %in% tmp1$country ) %>% dplyr::rename(col_of_strings.x = col_of_strings) missing_pop # # A tibble: 1 x 3 # country population col_of_strings.x # <chr> <dbl> <chr> # 1 brazil 210. dont_leave_me_behind missing_gdp <- df_gdp %>% dplyr::filter( col_of_strings == "dont_leave_me_behind", !country %in% tmp1$country ) %>% dplyr::rename(col_of_strings.y = col_of_strings) missing_gdp # # A tibble: 0 x 3 # # ... with 3 variables: country <chr>, gdp_growth <dbl>, col_of_strings.y <chr> out <- dplyr::bind_rows(tmp1, missing_pop, missing_gdp) out # # A tibble: 6 x 5 # country population col_of_strings.x gdp_growth col_of_strings.y # <chr> <dbl> <chr> <dbl> <chr> # 1 australia 25.0 LQMPB3662R 2.9 VKBCE2969H # 2 united_kingdom 66.6 WDXVX4684T 1.4 FMAKF4470M # 3 france 67.0 VJHBH0078U 1.7 dont_leave_me_behind # 4 spain 46.9 XFJPD7687T 2.4 RMPYK2467U # 5 canada 37.6 AQRCR0724P 1.9 JXMMZ3736X # 6 brazil 210. dont_leave_me_behind NA <NA>
- Аналогично 2, но с использованием
anti_join
:tmp1 <- dplyr::inner_join(df_population, df_gdp, by = "country") out <- dplyr::bind_rows( tmp1, dplyr::filter(df_population, col_of_strings == "dont_leave_me_behind") %>% dplyr::anti_join(., tmp1, by = "country") %>% dplyr::rename(col_of_strings.x = col_of_strings), dplyr::filter(df_gdp, col_of_strings == "dont_leave_me_behind") %>% anti_join(., tmp1, by = "country") %>% dplyr::rename(col_of_strings.y = col_of_strings) )
Последние два выполняют примерно то же самое с бенчмаркингом:
bench::mark(full1=..., inner2=..., inner3=...)
# # A tibble: 3 x 13
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory
# <bch:expr> <bch:t> <bch:t> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list>
# 1 full1 5.83ms 7.72ms 127. 1.36KB 4.32 59 2 463ms <tibb~ <Rpro~
# 2 inner2 9.54ms 11.46ms 84.9 11.28KB 2.07 41 1 483ms <tibb~ <Rpro~
# 3 inner3 13.95ms 14.92ms 62.9 11.28KB 4.49 28 2 445ms <tibb~ <Rpro~
# # ... with 2 more variables: time <list>, gc <list>
full_join
в этом случае работает намного лучше. Большие данные могут работать существенно по-разному, я еще не тестировал это.