Использование data.table для левого объединения с условиями равенства и неравенства и множественных совпадений на строку левой таблицы - PullRequest
0 голосов
/ 01 ноября 2018

Я пытаюсь определить, как объединить два набора данных, используя методы data.table в условии, которое содержит как равенство, так и неравенство в качестве подусловий. Вот некоторые примеры данных:

> A <- data.table(name = c("Sally","Joe","Fred"),age = c(20,25,30))
> B <- data.table(name = c("Sally","Joe","Fred","Fred"),age = c(20,30,35,40),condition = c("deceased","good","good","ailing"))
> A
    name age
1: Sally  20
2:   Joe  25
3:  Fred  30

> B
    name age condition
1: Sally  20  deceased
2:   Joe  30      good
3:  Fred  35      good
4:  Fred  40    ailing

Когда я выполняю A[B,on =.(name = name, age < age), condition := i.condition], я получаю только следующие 3 строки:

> A
    name age condition
1: Sally  20      <NA>
2:   Joe  25      good
3:  Fred  30    ailing

в противовес интуиции типичного пользователя SQL будет возвращать все строки, соответствующие условию соединения (в этом случае их будет 4). Я использую data.table_1.11.8.

Есть ли data.table подход, который позволит мне

  1. Обрабатывать условия, чьи подусловия могут быть смесью равенства и условия неравенства
  2. Назначьте значения существующему набору данных, используя :=, чтобы избежать ненужного использования памяти
  3. Сохранить все строки, которые соответствуют условию соединения, как SQL

Если нет решения для data.table, какова лучшая альтернатива (мои наборы данных довольно большие, и я бы предпочел потребовать как можно меньше пакетов)?

EDIT

Чтобы уточнить, какой вывод я ищу, я приведу код SQL, функциональность которого я пытаюсь эмулировать:

create table #A (
name varchar(50),
age integer
);

insert into #A
values ('Sally',20),
       ('Joe',25),
       ('Fred',30);

create table #B (
name varchar(50),
age integer,
condition varchar(50)
);

insert into #B
values ('Sally',20,'deceased'),
       ('Joe',30,'good'),
       ('Fred',35,'good'),
       ('Fred',40,'ailing');

select
#A.*,
condition
from #A left join #B
on  #A.name = #B.name
and #A.age < #B.age;

Выше приведен следующий набор результатов:

name    age   condition
Sally   20    NULL
Joe     25    good
Fred    30    good
Fred    30    ailing

1 Ответ

0 голосов
/ 29 января 2019

Если требуется левое соединение в стиле SQL (как подробно описано в редактировании), это может быть достигнуто с помощью кода, очень похожего на предложение icecreamtoucan в комментариях:

B[A,on=.(name = name, age > age)]

Примечание: если результирующий набор превышает сумму количества строк элементов объединения, data.table предположит, что вы допустили ошибку (в отличие от механизмов SQL), и выдаст ошибку. Решение (при условии, что вы не совершили ошибку ) заключается в добавлении allow.cartesian = TRUE.

Кроме того, в отличие от SQL, это объединение не возвращает все столбцы из составных таблиц. Вместо этого (и несколько разочаровывающе для тех, кто приходит из фона SQL) значения столбцов из левой таблицы, используемые в условии неравенства объединения, будут возвращены в столбцах с именами столбца правой таблицы по сравнению с ним в условии неравенства соединения!

Решение здесь (которое я нашел некоторое время назад в другом ответе SO, но не могу найти сейчас) состоит в том, чтобы создать дубликаты столбцов соединения, которые вы хотите сохранить, используйте их для условий соединения, а затем укажите столбцы для хранения в них. объединение.

, например

A <- data.table( group = rep("WIZARD LEAGUE",3)
                ,name = rep("Fred",time=3)
                ,status_start = as.Date("2017-01-01") + c(0,370,545)
                ,status_end = as.Date("2017-01-01") + c(369,544,365*3-1) 
                ,status = c("UNEMPLOYED","EMPLOYED","RETIRED"))
A <- rbind(A, data.table( group = "WIZARD LEAGUE"
                         ,name = "Sally"
                         ,status_start = as.Date("2017-01-01")
                         ,status_end = as.Date("2019-12-31")
                         ,status = "CONTRACTED"))
> A
           group  name status_start status_end     status
1: WIZARD LEAGUE  Fred   2017-01-01 2018-01-05 UNEMPLOYED
2: WIZARD LEAGUE  Fred   2018-01-06 2018-06-29   EMPLOYED
3: WIZARD LEAGUE  Fred   2018-06-30 2019-12-31    RETIRED
4: WIZARD LEAGUE Sally   2017-01-01 2019-12-31 CONTRACTED


B <- data.table( group = rep("WIZARD LEAGUE",time=5)
                ,loc_start = as.Date("2017-01-01") + 180*0:4
                ,loc_end = as.Date("2017-01-01") + 180*1:5-1
                , loc = c("US","GER","FRA","ITA","MOR"))

> B
           group  loc_start    loc_end loc
1: WIZARD LEAGUE 2017-01-01 2017-06-29  US
2: WIZARD LEAGUE 2017-06-30 2017-12-26 GER
3: WIZARD LEAGUE 2017-12-27 2018-06-24 FRA
4: WIZARD LEAGUE 2018-06-25 2018-12-21 ITA
5: WIZARD LEAGUE 2018-12-22 2019-06-19 MOR

>#Try to join all rows whose date ranges intersect:

>B[A,on=.(group = group, loc_end >= status_start,  loc_start <= status_end)]

Ошибка в vecseq (f__, len__, if (allow.cartesian || notjoin || ! anyDuplicated (f__,: объединить результаты в 12 строк; более 9 = nrow (х) + nrow (я). Проверьте наличие дублированных значений ключа в i, каждое из которых присоединяться к той же группе в х снова и снова. Если это нормально, попробуйте by = .EACHI для запуска j для каждой группы, чтобы избежать большого выделения. Если Вы уверены, что хотите продолжить, повторите с allow.cartesian = TRUE. В противном случае, пожалуйста, найдите это сообщение об ошибке в FAQ, Wiki, Переполнение стека и отслеживание проблем data.table для совета.

>#Try the join with allow.cartesian = TRUE
>#this succeeds but messes up column names

> B[A,on=.(group = group, loc_end >= status_start,  loc_start <= status_end), allow.cartesian = TRUE]
            group  loc_start    loc_end loc  name     status
 1: WIZARD LEAGUE 2018-01-05 2017-01-01  US  Fred UNEMPLOYED
 2: WIZARD LEAGUE 2018-01-05 2017-01-01 GER  Fred UNEMPLOYED
 3: WIZARD LEAGUE 2018-01-05 2017-01-01 FRA  Fred UNEMPLOYED
 4: WIZARD LEAGUE 2018-06-29 2018-01-06 FRA  Fred   EMPLOYED
 5: WIZARD LEAGUE 2018-06-29 2018-01-06 ITA  Fred   EMPLOYED
 6: WIZARD LEAGUE 2019-12-31 2018-06-30 ITA  Fred    RETIRED
 7: WIZARD LEAGUE 2019-12-31 2018-06-30 MOR  Fred    RETIRED
 8: WIZARD LEAGUE 2019-12-31 2017-01-01  US Sally CONTRACTED
 9: WIZARD LEAGUE 2019-12-31 2017-01-01 GER Sally CONTRACTED
10: WIZARD LEAGUE 2019-12-31 2017-01-01 FRA Sally CONTRACTED
11: WIZARD LEAGUE 2019-12-31 2017-01-01 ITA Sally CONTRACTED
12: WIZARD LEAGUE 2019-12-31 2017-01-01 MOR Sally CONTRACTED

>#Create aliased duplicates of the columns in the inequality condition
>#and specify the columns to keep

> keep_cols <- c(names(A),setdiff(names(B),names(A)))
> A[,start_dup := status_start]
> A[,end_dup := status_end]
> B[,start := loc_start]
> B[,end := loc_end]
>
>#Now the join works as expected (by SQL convention)
>
> B[ A
    ,..keep_cols
    ,on=.( group = group
          ,end >= start_dup
          ,start <= end_dup)
          ,allow.cartesian = TRUE]
            group  name status_start status_end     status  loc_start    loc_end loc
 1: WIZARD LEAGUE  Fred   2017-01-01 2018-01-05 UNEMPLOYED 2017-01-01 2017-06-29  US
 2: WIZARD LEAGUE  Fred   2017-01-01 2018-01-05 UNEMPLOYED 2017-06-30 2017-12-26 GER
 3: WIZARD LEAGUE  Fred   2017-01-01 2018-01-05 UNEMPLOYED 2017-12-27 2018-06-24 FRA
 4: WIZARD LEAGUE  Fred   2018-01-06 2018-06-29   EMPLOYED 2017-12-27 2018-06-24 FRA
 5: WIZARD LEAGUE  Fred   2018-01-06 2018-06-29   EMPLOYED 2018-06-25 2018-12-21 ITA
 6: WIZARD LEAGUE  Fred   2018-06-30 2019-12-31    RETIRED 2018-06-25 2018-12-21 ITA
 7: WIZARD LEAGUE  Fred   2018-06-30 2019-12-31    RETIRED 2018-12-22 2019-06-19 MOR
 8: WIZARD LEAGUE Sally   2017-01-01 2019-12-31 CONTRACTED 2017-01-01 2017-06-29  US
 9: WIZARD LEAGUE Sally   2017-01-01 2019-12-31 CONTRACTED 2017-06-30 2017-12-26 GER
10: WIZARD LEAGUE Sally   2017-01-01 2019-12-31 CONTRACTED 2017-12-27 2018-06-24 FRA
11: WIZARD LEAGUE Sally   2017-01-01 2019-12-31 CONTRACTED 2018-06-25 2018-12-21 ITA
12: WIZARD LEAGUE Sally   2017-01-01 2019-12-31 CONTRACTED 2018-12-22 2019-06-19 MOR

Я, конечно, не первый, кто указывает на эти отклонения от соглашения SQL или на то, что воспроизвести эту функциональность довольно громоздко (как показано выше), и я верю, что улучшения активно рассматриваются .

Всем, кто рассматривает альтернативные стратегии (например, пакет sqldf), я скажу, что, хотя есть достойные альтернативы data.table, я изо всех сил пытался найти какое-либо решение, которое сравнивается со скоростью data.table, когда оно очень велико. наборы данных участвуют как в соединениях, так и в других операциях. Излишне говорить, что есть много других преимуществ, которые делают этот пакет незаменимым для меня и многих других. Поэтому для тех, кто работает с большими наборами данных, я бы посоветовал не оставлять соединения data.table, если вышеприведенное выглядит громоздким и вместо этого либо привыкнуть проходить через эти движения, либо написать вспомогательную функцию, которая копирует последовательность действий до улучшения синтаксис приходит.

Наконец, я не упомянул здесь дизъюнктивные объединения, но, насколько я могу судить, это еще один недостаток подхода data.table (и еще одна область, где sqldf полезен). Я обходил их с помощью специальных «хаков», но я был бы признателен за полезные советы о том, как лучше всего их лечить в data.table.

...