Объединение таблиц: классифицируйте выходные данные в соответствии с тем, как строки объединяются - PullRequest
2 голосов
/ 05 марта 2019

При объединении (больших, сложных) таблиц в R я обычно изо всех сил стараюсь проверить результаты этой операции.Смотрите здесь минимальный воспроизводимый пример:

library(data.table) 
table1 <- data.table(id=c("A", "B", "C"), price=c(12,11,10))
table2 <- data.table(id=c("A", "C", "C", "D"), wharehouse=c("Colorado","Texas","New York", "Oregon"))


table_join <- merge(table1,table2,
                    by="id",
                    all.x=T,
                    all.y=T)

Ожидаемый результат - это не ракетостроение:

structure(list(id = c("A", "B", "C", "C", "D"), price = c(12, 
                                                          11, 10, 10, NA), wharehouse = c("Colorado", NA, "Texas", "New York", 
                                                                                          "Oregon"), join = c("INNER JOIN", "LEFT JOIN", "INNER JOIN. MULTIPLE RIGHT JOIN", 
                                                                                                              "INNER JOIN. MULTIPLE RIGHT JOIN", "RIGHT JOIN")), row.names = c(NA, 
                                                                                                                                                                               -5L), class = c("data.table", "data.frame"))

Но я хотел бы знать, сколько строк не соответствует другой таблице,есть одно совпадение, несколько совпадений ...

Я хотел бы получить некоторую информацию (возможно, новую строку), описывающую, как результат слияния.Смотрите возможный пример:

table_join[1, join:="INNER JOIN"]
table_join[2, join:="LEFT JOIN"]
table_join[3, join:="INNER JOIN. MULTIPLE RIGHT JOIN"]
table_join[4, join:="INNER JOIN. MULTIPLE RIGHT JOIN"]
table_join[5, join:="RIGHT JOIN"]

Здесь «ожидаемый результат»

structure(list(id = c("A", "B", "C", "C", "D"), price = c(12, 
11, 10, 10, NA), wharehouse = c("Colorado", NA, "Texas", "New York", 
"Oregon"), join = c("INNER JOIN", "LEFT JOIN", "INNER JOIN. MULTIPLE RIGHT JOIN", 
"INNER JOIN. MULTIPLE RIGHT JOIN", "RIGHT JOIN")), row.names = c(NA, 
-5L), class = c("data.table", "data.frame"))

Наверняка, с большими таблицами могут появиться новые ситуации (полные декартовы объединения), совпадения были id существует на другой таблице, но с NAs (в моем примере, скажем, у нас есть идентификатор D, но цена NA).

Кроме того, это поможет мне отслеживать сложные ситуации при объединении нескольких таблиц

Существует ли в R обертка слияния, которая выполняет такую ​​операцию?Я помню, когда был молодым мечтательным научным сотрудником, что Стата была в состоянии сделать что-то связанное, но я не знаю, как сделать это автоматически в R.

Ответы [ 4 ]

1 голос
/ 09 марта 2019

Мой пакет safejoin намеревается решить более широкую проблему проверок соединений. Он не дает вам именно то, что вы просите, но, надеюсь, достаточно близко, возможно, лучше, поскольку он выполняет проверки, которые вы могли бы выполнять в качестве следующего шага.

# devtools::install_github("moodymudskipper/safejoin")
library(safejoin)
safe_full_join(table1, table2, check="uvmn")
  id price wharehouse
1  A    12   Colorado
2  B    11       <NA>
3  C    10      Texas
4  C    10   New York
5  D    NA     Oregon
Warning messages:
1: x has unmatched sets of joining values: 
    id
1:  B 
2: y has unmatched sets of joining values: 
    id
1:  D 
3: y is not unique on id 

Проверки обрабатываются одним строковым параметром, т. Е. Последовательностью символов, где заглавные буквы вызывают сбои, строчные буквы вызывают предупреждения, а буквы с префиксом ~ запускают сообщения, здесь используются коды ( подробнее ):

  • "u" как уникальный, чтобы проверить, что соединительные столбцы образуют уникальный ключ в x
  • "v" чтобы убедиться, что объединяемые столбцы образуют уникальный ключ на y
  • "m" как совпадение, чтобы проверить, что все строки x имеют совпадение
  • "n" чтобы убедиться, что все строки y совпадают
1 голос
/ 07 марта 2019

Вот мое решение с использованием dplyr. Как сказал @Gerald T, всю информацию можно получить, посмотрев на объединенную таблицу.

Вы можете получить таблицу частот, используя этот код.

library(tidyverse)
table1 %>% left_join(table2) %>% 
      group_by(id) %>%
      summarise(num_wharehouse = sum(!is.na(wharehouse))) 
Joining, by = "id"
# A tibble: 3 x 2
  id    num_wharehouse
  <chr>          <int>
1 A                  1
2 B                  0
3 C                  2

Тогда вы можете получить статистику, которую вы хотите.

table1 %>% left_join(table2) %>% 
          group_by(id) %>%
          summarise(num_wharehouse = sum(!is.na(wharehouse))) %>%
          summarise(merged = sum(num_wharehouse > 0),
                    not_merged = sum(num_wharehouse == 0), 
                    single_match = sum(num_wharehouse == 1),
                    multi_match = sum(num_wharehouse > 1))
Joining, by = "id"
# A tibble: 1 x 4
  merged not_merged single_match multi_match
   <int>      <int>        <int>       <int>
1      2          1            1           1
1 голос
/ 07 марта 2019

Проблема сама по себе довольно проста, и она решается путем отслеживания частоты идентификатора каждой таблицы. Ниже следует мое решение, но, вероятно, потребуется некоторая оптимизация для больших таблиц.

EDIT1:

Исправление ошибки: категория была перезаписана; eval(track.col) вместо eval(parse(text = track.col)))

Более того, теперь можно присвоить пользовательское имя столбцу join.

library(data.table)

track.merge <- function(x, y, on, track.col){

  x[, N := .N, by = on][]
  y[, N := .N, by = on][]

  table_join <- merge(x, y, by=on, all.x=T, all.y=T)

  x[, N := NULL, by = on][]
  y[, N := NULL, by = on][]

  table_join[N.x > 1 & N.y > 1,                              
             eval(track.col) := "INNER JOIN. MULTIPLE LEFT RIGHT JOIN"][]

  table_join[N.x > 1 & is.na(eval(parse(text = track.col))), 
             eval(track.col) := "INNER JOIN. MULTIPLE LEFT JOIN"][]

  table_join[N.y > 1 & is.na(eval(parse(text = track.col))), 
             eval(track.col) := "INNER JOIN. MULTIPLE RIGHT JOIN"][]

  table_join[is.na(N.x),                                     
             eval(track.col) := "RIGHT JOIN"][]

  table_join[is.na(N.y),                                
             eval(track.col) := "LEFT JOIN"][]

  table_join[is.na(eval(parse(text = track.col))),      
             eval(track.col) := "INNER JOIN"][]

  table_join[, ':=' (N.x = NULL, N.y = NULL)][]
}

РЕДАКТИРОВАТЬ2

Гораздо более читаемая версия той же функции

track.merge2 <- function(x, y, on, track.col){

  x[, N := .N, by = on][]
  y[, N := .N, by = on][]

  table_join <- merge(x, y, by=on, all.x=T, all.y=T)
  track_ids <- character(NROW(table_join))

  x[, N := NULL, by = on][]
  y[, N := NULL, by = on][]

  track_ids[table_join$N.x > 1 & table_join$N.y > 1] <- "INNER JOIN. MULTIPLE LEFT RIGHT JOIN"
  track_ids[table_join$N.x > 1 & track_ids == ""]    <- "INNER JOIN. MULTIPLE LEFT JOIN"
  track_ids[table_join$N.y > 1 & track_ids == ""]    <- "INNER JOIN. MULTIPLE RIGHT JOIN"
  track_ids[is.na(table_join$N.x)]                   <- "RIGHT JOIN"
  track_ids[is.na(table_join$N.y)]                   <- "LEFT JOIN"
  track_ids[track_ids == ""]                         <- "INNER JOIN"

  table_join[[track.col]] <- track_ids
  table_join[, ':=' (N.x = NULL, N.y = NULL)][]
}

TEST:

table1 <- data.table(id=c("A", "C", "C", "B", "F", "H", "H"), price=c(12,11,10,13,10,15,3)) 
table2 <- data.table(id=c("A", "C", "C", "F", "F", "H", "L"), wharehouse=c("Colorado","Texas","New York", "Washington", "Illinois", "Florida", "Kansas")) 

> table1
   id price
1:  A    12
2:  C    11
3:  C    10
4:  B    13
5:  F    10
6:  H    15
7:  H     3

> table2
   id wharehouse
1:  A   Colorado
2:  C      Texas
3:  C   New York
4:  F Washington
5:  F   Illinois
6:  H    Florida
7:  L     Kansas

> track.merge(table1, table2, "id", "join")
    id price wharehouse                                 join
 1:  A    12   Colorado                           INNER JOIN
 2:  B    13       <NA>                            LEFT JOIN
 3:  C    11      Texas INNER JOIN. MULTIPLE LEFT RIGHT JOIN
 4:  C    11   New York INNER JOIN. MULTIPLE LEFT RIGHT JOIN
 5:  C    10      Texas INNER JOIN. MULTIPLE LEFT RIGHT JOIN
 6:  C    10   New York INNER JOIN. MULTIPLE LEFT RIGHT JOIN
 7:  F    10 Washington      INNER JOIN. MULTIPLE RIGHT JOIN
 8:  F    10   Illinois      INNER JOIN. MULTIPLE RIGHT JOIN
 9:  H    15    Florida       INNER JOIN. MULTIPLE LEFT JOIN
10:  H     3    Florida       INNER JOIN. MULTIPLE LEFT JOIN
11:  L    NA     Kansas                           RIGHT JOIN


> all.equal(track.merge2(x, y, on = "id", "join"), track.merge(x, y, on = "id", "join"))
[1] TRUE

При рассмотрении таблиц с 1000 тыс. Строк и двух столбцов слияние происходит примерно в 2,5 раза медленнее:

library(microbenchmark)

table1 <- data.table(id = sample(1e+6, 1e+6, replace = T), price = rnorm(1e+6))
table2 <- data.table(id = sample(1e+6, 1e+6, replace = T), state = sample(LETTERS, 1e+6, replace = T))

microbenchmark(merge        = merge(table1,table2, by="id", all.x=T, all.y=T),
               track.merge  = track.merge(table1, table2, "id", "join"), 
               track.merge2 = track.merge2(table1, table2, "id", "join"), 
               times = 10L)

         expr       min       lq      mean    median        uq       max neval cld
        merge  569.7727  573.433  577.8784  577.2759  581.9219  586.9951    10 a  
  track.merge 1456.4417 1536.982 1545.6427 1556.5226 1563.6150 1623.3078    10   c
 track.merge2 1392.6832 1464.968 1460.2484 1471.0332 1477.2330 1487.1828    10  b 

КОММЕНТАРИЙ О НЕ УКАЗАННЫХ ID

С точки зрения базы данных не имеет большого смысла иметь NA как id. Идентификаторы являются ключом для связи ваших таблиц в реляционной базе данных. Если есть записи с отсутствующими идентификаторами, бессмысленно связывать их с другой таблицей, поэтому я либо отфильтрую их, либо попытаюсь исправить их до объединения таблиц.

0 голосов
/ 07 марта 2019

После объединения можно применить функцию-обертку, чтобы проанализировать ее путь следующим образом.Сценарии приведены в ОП и комментариях:

#Scenario 1
table1 <- data.table(id=c("A", "B", "C"), price=c(12,11,10)); table2 <- data.table(id=c("A", "C", "C", "D"), wharehouse=c("Colorado","Texas","New York", "Oregon"));

#Scenario 2
table1 <- data.table(id=c("C", "C", "C"), price=c(12,11,10)); table2 <- data.table(id=c("A", "C", "C", "D"), wharehouse=c("Colorado","Texas","New York", "Oregon"));

#Scenario 3
table1 <- data.table(id=c(NA, "C", "C"), price=c(12,11,10)); table2 <- data.table(id=c("A", "C", "C", NA), wharehouse=c("Colorado","Texas","New York", "Oregon")) 

#Scenario 4
table1 <- data.table(id=c("A", "A", "C"), price=c(12,11,10)); table2 <- data.table(id=c("B", "C", "C","D"), wharehouse=c("Colorado","Texas","New York", "Oregon")) 

setkeyv(table1,"id")
setkeyv(table2,"id")
table_join  <- merge(table1,table2,by="id",all.x=T,all.y=T)

write_description <- function(p,w,n) { 
  inners <- (!is.na(p) & !is.na(w))
  lefts <-  (!is.na(p) & is.na(w))
  rights <- ((is.na(p) & !is.na(w))) | (n > 1 & !is.na(w))
  multis <- n > 1

  paste0(ifelse(inners,"INNER JOIN ",""),
         ifelse(multis,"MULTIPLE ",""),
         ifelse(lefts,"LEFT JOIN ",""),
         ifelse(rights,"RIGHT JOIN ",""))
}


table_join[,description:=write_description(price,wharehouse,.N),by="id"]

Результаты СЦЕНАРИЙ 1:

> table_join
   id price wharehouse                     description
1:  A    12   Colorado                     INNER JOIN 
2:  B    11         NA                      LEFT JOIN 
3:  C    10      Texas INNER JOIN MULTIPLE RIGHT JOIN 
4:  C    10   New York INNER JOIN MULTIPLE RIGHT JOIN 
5:  D    NA     Oregon                     RIGHT JOIN 

Результаты: СЦЕНАРИЙ 2

> table_join
   id price wharehouse                     description
1:  A    NA   Colorado                     RIGHT JOIN 
2:  C    12      Texas INNER JOIN MULTIPLE RIGHT JOIN 
3:  C    12   New York INNER JOIN MULTIPLE RIGHT JOIN 
4:  C    11      Texas INNER JOIN MULTIPLE RIGHT JOIN 
5:  C    11   New York INNER JOIN MULTIPLE RIGHT JOIN 
6:  C    10      Texas INNER JOIN MULTIPLE RIGHT JOIN 
7:  C    10   New York INNER JOIN MULTIPLE RIGHT JOIN 
8:  D    NA     Oregon                     RIGHT JOIN 

Результаты: СЦЕНАРИЙ 3

> table_join
   id price wharehouse                     description
1: NA    12     Oregon                     INNER JOIN 
2:  A    NA   Colorado                     RIGHT JOIN 
3:  C    11      Texas INNER JOIN MULTIPLE RIGHT JOIN 
4:  C    11   New York INNER JOIN MULTIPLE RIGHT JOIN 
5:  C    10      Texas INNER JOIN MULTIPLE RIGHT JOIN 
6:  C    10   New York INNER JOIN MULTIPLE RIGHT JOIN 

Результаты: СЦЕНАРИЙ 4

> table_join
   id price wharehouse                     description
1:  A    12         NA             MULTIPLE LEFT JOIN 
2:  A    11         NA             MULTIPLE LEFT JOIN 
3:  B    NA   Colorado                     RIGHT JOIN 
4:  C    10      Texas INNER JOIN MULTIPLE RIGHT JOIN 
5:  C    10   New York INNER JOIN MULTIPLE RIGHT JOIN 
6:  D    NA     Oregon                     RIGHT JOIN 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...