Почему соединение X [Y] таблиц data.tables не допускает полное внешнее соединение или левое соединение?

123

Это немного философский вопрос о синтаксисе соединения data.table. Я нахожу все больше и больше применений для data.tables, но все еще учусь ...

Формат соединения X[Y]для data.tables очень лаконичен, удобен и эффективен, но, насколько я могу судить, он поддерживает только внутренние соединения и правые внешние соединения. Чтобы получить левое или полное внешнее соединение, мне нужно использовать merge:

  • X[Y, nomatch = NA] - все строки в Y - правое внешнее соединение (по умолчанию)
  • X[Y, nomatch = 0] - только строки с совпадениями в X и Y - внутреннее соединение
  • merge(X, Y, all = TRUE) - все строки от X и Y - полное внешнее соединение
  • merge(X, Y, all.x = TRUE) - все строки в X - левое внешнее соединение

Мне кажется, было бы удобно, если бы X[Y] формат соединения поддерживал все 4 типа объединений. Есть ли причина, по которой поддерживаются только два типа объединений?

Для меня, nomatch = 0и nomatch = NAзначения параметров не очень интуитивным для действия выполняются. Это проще для меня , чтобы понять и запомнить mergeсинтаксис: all = TRUE, all.x = TRUEи all.y = TRUE. Поскольку X[Y]операция похожа на mergeгораздо больше match, почему бы не использовать mergeсинтаксис для объединений, а не параметр matchфункции nomatch?

Вот примеры кода для 4 типов соединения:

# sample X and Y data.tables
library(data.table)
X <- data.table(t = 1:4, a = (1:4)^2)
setkey(X, t)
X
#    t  a
# 1: 1  1
# 2: 2  4
# 3: 3  9
# 4: 4 16

Y <- data.table(t = 3:6, b = (3:6)^2)
setkey(Y, t)
Y
#    t  b
# 1: 3  9
# 2: 4 16
# 3: 5 25
# 4: 6 36

# all rows from Y - right outer join
X[Y]  # default
#  t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

X[Y, nomatch = NA]  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

merge(X, Y, by = "t", all.y = TRUE)  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

identical(X[Y], merge(X, Y, by = "t", all.y = TRUE))
# [1] TRUE

# only rows in both X and Y - inner join
X[Y, nomatch = 0]  
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t")  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t", all = FALSE)  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

identical( X[Y, nomatch = 0], merge(X, Y, by = "t", all = FALSE) )
# [1] TRUE

# all rows from X - left outer join
merge(X, Y, by = "t", all.x = TRUE)
#    t  a  b
# 1: 1  1 NA
# 2: 2  4 NA
# 3: 3  9  9
# 4: 4 16 16

# all rows from both X and Y - full outer join
merge(X, Y, by = "t", all = TRUE)
#    t  a  b
# 1: 1  1 NA
# 2: 2  4 NA
# 3: 3  9  9
# 4: 4 16 16
# 5: 5 NA 25
# 6: 6 NA 36

Обновление: data.table v1.9.6 представил on=синтаксис, который позволяет ad hoc соединения для полей, отличных от первичного ключа. Ответ jangorecki на вопрос Как объединить (объединить) фреймы данных (внутренние, внешние, левые, правые)? предоставляет несколько примеров дополнительных типов соединений, которые может обрабатывать data.table.

Дуглас Кларк
источник
4
Вы читали FAQ 1.12 ? Вы всегда можете позвонить , Y[X]если вы хотите, чтобы левое внешнее соединение из X[Y]и rbind(Y[X],X[Y])если вы хотите полное внешнее соединение
mnel
См. Мой ответ для получения более подробной информации о подходе к полному внешнему соединению
data.table
@mnel, я предполагаю, что ваш unique()подход, описанный ниже, для полного соединения предпочтительнее rbind(Y[X],X[Y]), поскольку rbind будет включать копирование таблицы. Это правильно?
Дуглас Кларк
насколько мне известно, да. Я не проверял, являются ли три меньших уникальных вызова быстрее, чем один большой (например, unique(c(unique(X[,t]), unique(Y[,t]))это должно быть более эффективным с точки зрения памяти, поскольку оно объединяет только два списка, которые будут меньше или равны количеству строк в X и Y .
mnel
2
Ваш вопрос такое хорошее описание; Я нашел ответы на свои вопросы в вашем вопросе. Спасибо
irriss

Ответы:

72

Цитата из data.table FAQ 1.11 В чем разница между X[Y]и merge(X, Y)?

X[Y] является соединением, ищущим строки X с использованием Y (или ключа Y, если он есть) в качестве индекса.

Y[X] - соединение, поиск строк Y с помощью X (или ключа X, если он есть)

merge(X,Y)делает оба способа одновременно. Количество строк X[Y]и Y[X]обычно различается, тогда как количество строк, возвращаемых merge(X,Y)иmerge(Y,X) одинаково.

НО это упускает из виду главное. Для большинства задач требуется что-то сделать с данными после объединения или слияния. Зачем объединять все столбцы данных только для того, чтобы впоследствии использовать их небольшое подмножество? Вы можете предложить merge(X[,ColsNeeded1],Y[,ColsNeeded2]), но для этого программист должен решить, какие столбцы необходимы. X[Y,j] в data.table делает все это за вас за один шаг. Когда вы пишете X[Y,sum(foo*bar)], data.table автоматически проверяет jвыражение, чтобы увидеть, какие столбцы оно использует. Это только подмножество этих столбцов; остальные игнорируются. Память создается только для столбцов, которые jиспользуются, а для столбцов применяются Yстандартные правила повторного использования R в контексте каждой группы. Допустим, fooесть X, а бар находится вY (вместе с 20 другими столбцами Y). неX[Y,sum(foo*bar)] быстрее программировать и быстрее запускать, чем расточительное объединение всего, за которым следует подмножество?


Если вам нужно левое внешнее соединение X[Y]

le <- Y[X]
mallx <- merge(X, Y, all.x = T)
# the column order is different so change to be the same as `merge`
setcolorder(le, names(mallx))
identical(le, mallx)
# [1] TRUE

Если вам нужно полное внешнее соединение

# the unique values for the keys over both data sets
unique_keys <- unique(c(X[,t], Y[,t]))
Y[X[J(unique_keys)]]
##   t  b  a
## 1: 1 NA  1
## 2: 2 NA  4
## 3: 3  9  9
## 4: 4 16 16
## 5: 5 25 NA
## 6: 6 36 NA

# The following will give the same with the column order X,Y
X[Y[J(unique_keys)]]
mnel
источник
5
Спасибо @mnel. В FAQ 1.12 не упоминается полное или левое внешнее соединение. Ваше предложение полного внешнего соединения с unique () очень поможет. Это должно быть в FAQ. Я знаю, что Мэтью Доул «разработал его для себя, и он так и хотел». (FAQ 1.9), но я подумал, X[Y,all=T]что это элегантный способ указать полное внешнее соединение в синтаксисе data.table X [Y]. Или X[Y,all.x=T]для левого соединения. Я задавался вопросом, почему это не так. Просто мысль.
Дуглас Кларк
1
@DouglasClark Добавил ответ и подал 2302: Добавить синтаксис соединения слиянием mnel в FAQ (с таймингами) . Отличные предложения!
Мэтт Доул
1
@mnel Спасибо за решение ... сделал мой день ... :)
Ankit
@mnel Можно ли каким-либо образом приписать НС 0 во время выступления X[Y[J(unique_keys)]]?
Ankit
11
Что меня впечатляет в документации data.table, так это то, что она может быть такой многословной, но оставаться такой загадочной ...
NiuBiBang
24

Ответ @mnel верен, так что примите этот ответ. Это просто продолжение, слишком длинное для комментариев.

Как говорит mnel, левое / правое внешнее соединение получается заменой Yи X: Y[X]-vs-X[Y] . Таким образом, в этом синтаксисе поддерживаются 3 из 4 типов соединения, а не 2, iiuc.

Добавление 4-го кажется хорошей идеей. Допустим, мы добавляем full=TRUEили both=TRUEили merge=TRUE(не уверены, какое имя является лучшим аргументом?), Тогда мне не приходило в голову, что X[Y,j,merge=TRUE]это будет полезно по причинам, указанным после НО в FAQ 1.12. Запрос на новую функцию теперь добавлен и связан здесь, спасибо:

FR # 2301: Добавить аргумент merge = TRUE для объединения X [Y] и Y [X], как это делает merge ().

Последние версии ускорились merge.data.table(например, за счет создания внутренней копии для более эффективной установки ключей). Таким образом , мы пытаемся принести merge()и X[Y]ближе, и предоставить все возможности для пользователя , для полной гибкости. У обоих есть плюсы и минусы. Еще один выдающийся запрос функции:

FR # 2033: Добавить by.x и by.y в таблицу merge.data.table

Если есть другие, пожалуйста, продолжайте их приходить.

По этой части вопроса:

почему бы не использовать синтаксис слияния для объединений, а не параметр nomatch функции сопоставления?

Если вы предпочитаете merge()синтаксис и его 3 -х аргументов all, all.xа all.yзатем просто использовать , что вместо X[Y]. Думаю, он должен охватывать все случаи. Или вы имеете в виду , почему это аргумент один nomatchв [.data.table? Если так, то это казалось естественным, учитывая FAQ 2.14: «Не могли бы вы объяснить, почему data.table вдохновлен синтаксисом A [B] в base?». Но также в nomatchнастоящее время принимает только два значения 0и NA. Это можно было бы расширить так, чтобы отрицательное значение что-то означало, или 12 означало бы использование значений 12-й строки для заполнения, например, NA, или nomatchв будущем могло бы быть вектором или даже самим a data.table.

Гектометр Как бы by-without-by взаимодействовал с merge = TRUE? Возможно, нам следует передать это в справку по данным .

Мэтт Доул
источник
Спасибо @Matthew. Ответ @mnel отличный, но мой вопрос заключался не в том, как выполнить полное или левое соединение, а в том, "Есть ли причина, по которой поддерживаются только два типа объединений?" Так что теперь это немного более философски ;-) На самом деле я не предпочитаю синтаксис слияния, но, похоже, в R есть традиция строить на уже знакомых людям. Я сделал join="all", join="all.x", join="all.y" and join="x.and.y"пометки на полях своих заметок. Не уверен, что это лучше.
Дуглас Кларк
@DouglasClark Может быть joinтак, хорошая идея. Я отправил в справку по данным, так что давайте посмотрим. Может быть data.table, тоже дадим немного времени, чтобы осесть. Вы, например, уже успели перейти в режим by-without-by и присоединиться к унаследованной области ?
Мэтт Доул
Как указано в моем комментарии выше, я предлагаю добавить joinключевое слово к, когда я это DataTable: X[Y,j,join=string]. Предлагаются следующие возможные строковые значения для соединения: 1) "all.y" и "right" -
Дуглас Кларк
1
Привет, Мэтт, библиотека data.table просто великолепна; Спасибо вам за это; хотя я думаю, что поведение соединения (которое по умолчанию является правым внешним соединением) должно быть подробно объяснено в основной документации; Мне потребовалось 3 дня, чтобы понять это.
Тимоти Генри
1
@tucson Просто для ссылки, теперь проблема №709 .
Мэтт Доул
17

Это «ответ» это предложение для обсуждения: Как указано в моем комментарии, я предлагаю добавить joinпараметр [.data.table () , чтобы включить дополнительные типы соединений, то есть: X[Y,j,join=string]. В дополнение к 4 типам обычных объединений я также предлагаю поддерживать 3 типа эксклюзивных объединений и перекрестное соединение.

Предлагаются следующие joinстроковые значения (и псевдонимы) для различных типов соединений:

  1. "all.y"и "right"- правое соединение, настоящее значение data.table default (nomatch = NA) - все Y строк с NA, где нет совпадений X;
  2. "both"и "inner" - внутреннее соединение (nomatch = 0) - только строки, в которых совпадают X и Y;

  3. "all.x"и "left" - левое соединение - все строки из X, NA, где нет совпадений Y:

  4. "outer"и "full" - полное внешнее соединение - все строки из X и Y, NA, где нет совпадений

  5. "only.x"и "not.y"- несоединение или антисоединение, возвращающее X строк, где нет соответствия Y

  6. "only.y" и "not.x"- несоединение или антисоединение, возвращающее Y строк, где нет совпадений X
  7. "not.both" - эксклюзивное соединение, возвращающее строки X и Y, в которых нет совпадения с другой таблицей, то есть исключающее ИЛИ (XOR)
  8. "cross"- перекрестное соединение или декартово произведение с каждой строкой X, соответствующей каждой строке Y

Значение по умолчанию join="all.y"соответствует текущему значению по умолчанию.

Строковые значения «all», «all.x» и «all.y» соответствуют merge()параметрам. «Правая», «левая», «внутренняя» и «внешняя» строки могут быть более удобными для пользователей SQL.

Строки "both" и "not.both" - мое лучшее предложение на данный момент, но у кого-то могут быть лучшие строковые предложения для внутреннего соединения и эксклюзивного соединения. (Я не уверен, что «эксклюзивный» - правильная терминология, поправьте меня, если есть подходящий термин для соединения «XOR».)

Использование join="not.y"является альтернативой X[-Y,j]или X[!Y,j]не присоединиться синтаксис и , может быть , более ясно (для меня), хотя я не уверен , если они одинаковы (новая функция в data.table версии 1.8.3).

Перекрестное соединение иногда может быть удобно, но оно может не соответствовать парадигме data.table.

Дуглас Кларк
источник
1
Пожалуйста, отправьте это справку данным для обсуждения.
Мэтт Доул
3
+1 Но, пожалуйста, отправьте в datatable-help или отправьте запрос функции . Я не против добавить, joinно если он не попадет на трекер, он забудется.
Мэтт Доул
1
Я вижу, вы какое-то время не заходили в SO. Итак, я подал это в FR # 2301
Мэтт Доул
@MattDowle, +1 за эту функцию. (Пытался сделать это через FR # 2301, но получаю сообщение об отказе в разрешениях).
adilapapaya
@adilapapaya Мы перешли с RForge на GitHub. Пожалуйста, отметьте +1 здесь: github.com/Rdatatable/data.table/issues/614 . Арун перенес задачи, чтобы они не потерялись.
Мэтт Доул