Чому приєднання X [Y] даних.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?

Ось приклади коду чотирьох типів приєднання:

# 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 на питання Як з'єднати (об'єднати) кадри даних (внутрішні, зовнішні, ліві, праві)? наведено кілька прикладів додаткових типів з'єднання, з якими може працювати обробка даних.table.


4
Ви читали FAQ 1.12 ? Ви завжди можете зателефонувати , Y[X]якщо ви хочете, щоб ліве зовнішнє з'єднання з X[Y]і rbind(Y[X],X[Y])якщо ви хочете повне зовнішнє з'єднання
mnel

Дивіться мою відповідь щодо більш підходящого для підходу даних до повного зовнішнього приєднання
mnel

@mnel, я вважаю, що ваш unique()підхід нижче для повного приєднання є кращим rbind(Y[X],X[Y]), оскільки rbind передбачає копіювання таблиці. Це так?
Дуглас Кларк

наскільки мені відомо, так. Я не перевіряв, чи є три менші унікальні дзвінки швидшими, ніж один великий (наприклад, unique(c(unique(X[,t]), unique(Y[,t]))це має бути більш ефективною пам'яттю, оскільки це лише комбінування двох списків, що будуть меншими або рівними кількості рядків у X та Y .
Менель

2
Ваше запитання настільки хороший опис; Я знайшов відповіді на свої запитання у вашому запитанні. Спасибі
irriss

Відповіді:


71

Цитувати з data.table поширених запитань 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)]]

5
Дякую @mnel. FAQ 1.12 не згадує повне або ліве зовнішнє з'єднання. Ваша повноцінна пропозиція щодо приєднання за допомогою унікального () - чудова допомога. Це повинно бути у FAQ. Я знаю, що Меттью Даул "спроектував це для власного використання, і він хотів саме так". (FAQ 1.9), але я подумав, що це X[Y,all=T]може бути елегантним способом вказати повне зовнішнє з'єднання в синтаксисі X. Y [Y] data.table. Або X[Y,all.x=T]для з’єднання зліва. Мені було цікаво, чому це не було розроблено таким чином. Просто думка.
Дуглас Кларк

1
@DouglasClark Додали відповідь та подали 2302: Додайте синтаксис об'єднання об'єднань mnel до FAQ (із часом) . Чудові пропозиції!
Метт Даул

1
@mnel Дякую за рішення ... зробив свій день ... :)
Ankit

@mnel Чи є спосіб, коли ми можемо замінити NA під час виконання X[Y[J(unique_keys)]]?
Анкіт

11
що мене вражає у документації data.table, це те, що вона може бути такою багатослівною, але залишатися такою
виразною

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

Якщо є інші, будь ласка, продовжуйте їх приймати.

До цієї частини питання:

чому б не використовувати синтаксис злиття для приєднання, а не параметр номат функції відповідності?

Якщо ви віддаєте перевагу merge()синтаксис і його 3 аргументи all, all.xа all.yпотім просто використовуйте це замість X[Y]. Подумайте, він повинен охоплювати всі справи. Або ви мали на увазі, чому аргумент єдинийnomatch в [.data.table? Якщо так, то саме так, здавалося, природним було задано FAQ 2.14: "Чи можете ви пояснити далі, чому data.table надихається синтаксисом A [B] в базі?". Але також, nomatchприймає лише два значення в даний час 0і NA. Це може бути розширено, щоб негативне значення щось означало, або 12 означало б використання значень 12-го ряду для заповнення NA, наприклад, або nomatchв майбутньому може бути вектором або навіть самим собою a data.table.

Гм. Як би без-за- взаємодіяти з об'єднанням = ІСТИНА? Можливо, ми повинні перейняти це на допомогу в даних .


Дякую @Matthew @ mnel відповідь відмінна, але моє питання полягало не в тому, як зробити повне або ліве приєднання, а "Чи є причина, що підтримуються лише два типи приєднань?" Тож тепер це трохи більш філософсько ;-) Насправді я не віддаю перевагу синтаксису злиття, але, схоже, є традиція R будувати на існуючих людях знайомих. Я промальовував join="all", join="all.x", join="all.y" and join="x.and.y"на полях своїх заміток. Не впевнений, чи краще це.
Дуглас Кларк

@DouglasClark Можливо, joinтак, хороша ідея. Я розмістив у довідці даних, тому подивимось. Можливо, дайте data.tableтрохи часу і оселитися. Чи доводиться вам, наприклад, ще без-до , і приєднатися до успадкованої області застосування ?
Метт Даул

Як зазначено в моєму коментарі вище, я пропоную додати 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"- право приєднання, теперішній типовий таблиця даних (за замовчуванням = 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.x" і "all.y" відповідають merge()параметрам. "Правий", "лівий", "внутрішній" і "зовнішній" рядки можуть бути більш прихильними для користувачів SQL.

Рядки "і" і "not.both" - це моя найкраща пропозиція на даний момент, але хтось може мати кращі рядкові пропозиції щодо внутрішнього і ексклюзивного з'єднання. (Я не впевнений, що "ексклюзив" є правильною термінологією, виправте мене, якщо є належний термін для "XOR" приєднання.)

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

Перехресне з'єднання іноді може бути зручним, але воно може не входити в парадигму даних.table.


1
Будь ласка, надішліть це до довідкової таблиці для обговорення.
Метт Даул

3
+1 Але будь-ласка, надішліть довідку з інформаційними даними або надішліть запит на функцію . Я не проти додавати, joinале якщо він не потрапить на трекер, він забудеться.
Метт Даул

1
Я бачу, ви деякий час не входили в SO. Тому я подав це у FR # 2301
Matt Dowle

@MattDowle, +1 для цієї функції. (Спробував це зробити через FR # 2301, але мені надійшло повідомлення про відмову).
adilapapaya

@adilapapaya Ми перейшли з RForge до GitHub. Будь ласка, натисніть тут +1: github.com/Rdatatable/data.table/isissue/614 . Арун переніс питання, щоб вони не були загублені.
Метт Даул
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.