R: Эффективный способ слияния + Обновить таблицу со второй таблицей, где значения из одинаковых имен столбцов заполняют NA

Сводка. Я хотел бы объединить две таблицы общим ключом id как all=true (полное внешнее соединение), где вместо столбцов с теми же именами, которые установлены как var1.x var2.y и т.д., они объединяются как один столбец, в котором значения отсутствующих (NA) в левой таблице заполняются значениями из правой таблицы (в дополнение к стандартным поведением слияния, то есть добавлением строк с различными идентификаторами и столбцами с четкими именами).

Детали:

Я хотел бы объединить + обновить table1 с помощью table2 на основе общего столбца ключа id, чтобы:

1) Если table1 и table2 имеют столбцы с тем же именем (кроме id), значение в table1 остается в покое, если оно существует и заменяется значением в table2, если значение в table1 равно NA.

2) Если таблица2 имеет столбцы, которые не имеют таблицы1 (разные имена), они объединяются (по id).

3) Если table1 имеет id, который не соответствует в table2, значения для разных столбцов имен из table2 равны NA

4) Если table2 имеет id, который не совпадает с table1, он добавляется как новая строка, а значения для разных имен столбцов из table1 равны NA.

3 и 4 соответствуют стандарту merge с all=true.

Я обеспокоен тем, что переосмыслил проблему, поскольку я не могу найти простой способ сделать это с помощью merge или join, который не включает создание проверок ifelse для каждого столбца. Реальные данные имеют ~ 1000 столбцов, поэтому было бы невероятно долгое решение для поиска ifelse для каждого из них.

Воспроизводимый приведенный пример:

table1  <- data.table(id  =c("id1", "id2", "id3", "id4", "id5", "id6"),
                      var1=c(1,2,3,4,5, 6),
                      var2=c("a", "b", NA, "d", NA, "f"),
                      var3=c(NA, 12, 13, 14, 15, 16));

table2  <- data.table(id  =c("id1", "id2", "id3", "id4", "id5", "id8"),
                      var1=c(1,2,NA,4,5, 8),
                      var2=c(NA, "b", "c", "d", "e", "h"),
                      var4=c("foo", "bar", "oof", "rab", NA, "sna"));

desired <- data.table(id=c("id1", "id2", "id3", "id4", "id5", "id6", "id8"),
                      var1=c(1,2,3,4,5, 6, 8),
                      var2=c("a", "b", "c", "d", "e", "f", "h"),
                      var3=c(NA, 12, 13, 14, 15, 16, NA),
                      var4=c("foo", "bar", "oof", "rab", NA, NA, "sna"));

table1;
    id var1 var2 var3
1: id1    1    a   NA
2: id2    2    b   12
3: id3    3   NA   13
4: id4    4    d   14
5: id5    5    e   15
6: id6    6    f   16

table2;
    id var1 var2 var4
1: id1    1    a  foo
2: id2    2    b  bar
3: id3   NA    c  oof
4: id4    4    d  rab
5: id5    5    e   NA
6: id8    8    h  sna

desired
    id var1 var2 var3 var4
1: id1    1    a   NA  foo
2: id2    2    b   12  bar
3: id3    3    c   13  oof
4: id4    4    d   14  rab
5: id5    5    e   15   NA
6: id6    6    f   16   NA
7: id8    8    h   NA  sna

Объяснение желаемого результата:

  • Для столбца var1, table1 имели все значения, поэтому он остается один, а NA для id3 в table2 игнорируется (обратите внимание, что это не включает строку слияние для разных идентификаторов, описанных ниже).

  • Для столбца var2 в table отсутствует значение, проиндексированное id3, поэтому оно обновляется с table2 (обратите внимание, что это не включает слияние строк для разных идентификаторов, описанных ниже),

  • Для столбца var3 в столбце table2 отсутствует соответствующий столбец, поэтому он сохраняется как есть.

  • Для столбца var4 в table1 не было столбца var4, поэтому он объединяется из table2 с помощью ключевой переменной id.

  • Для строки с id6 в table1 нет соответствия id6 в table2, поэтому значение для столбца var4, которое находится только в table2, равно NA в desired вывод для строки id6.

  • Для строки с id8 в table2 нет соответствия id8 в table1, поэтому значение для столбца var3, которое находится только в table1, равно NA в desired вывод для строки id8.

Конечно, есть простой способ сделать это с помощью data.table? Эффективные решения особенно приветствуются, учитывая размер реальных данных. Пакет datamerge, по-видимому, использовался для этого, но он больше не работает на CRAN, и я не могу заставить его работать с R3.2.3 из zip. Был ли еще один пакет для этой задачи? Есть много других потоков, которые сосредоточены на решении этого для одной или нескольких столбцов с известными именами, но для большого количества столбцов они не кажутся практичными.

Ответ 1

Здесь один из способов:

com.cols    = setdiff(intersect(names(table1), names(table2)), "id")
com.cols.x  = paste0(com.cols, ".x")
com.cols.y  = paste0(com.cols, ".y")

# create combined table
DT = setkey(merge(table1, table2, by="id", all=TRUE), NULL)

# edit common columns where NAs are present
for (j in seq_along(com.cols)) 
  DT[is.na(get(com.cols.x[j])), (com.cols.x[j]) := get(com.cols.y[j])]

# remove unneeded columns
DT[, (com.cols.y) := NULL]

# rename kept columns
setnames(DT, com.cols.x, com.cols)

identical(DT, desired) # TRUE

Это довольно беспорядочно создавать и работать со всеми этими векторами имен столбцов.


Относительно исходного вопроса...

Здесь другой способ (без импорта новых строк из table2, как в исходном сообщении):

com.cols    = setdiff(intersect(names(table1), names(table2)), "id")
i.com.cols  = paste0("i.", com.cols)
new.cols    = c(i.com.cols, setdiff(names(table2), c("id", com.cols)))

# grab columns from table2
table1[table2, (new.cols) := mget(new.cols), on="id"]

# edit common columns where NAs are present
for (j in seq_along(com.cols)) 
  table1[is.na(get(com.cols[j])), (com.cols[j]) := get(i.com.cols[j])]

# remove unneeded columns
table1[, (i.com.cols) := NULL]

Таким образом, все этапы являются модификациями table1 по ссылке.

Ответ 2

Вот еще один вариант, который позволяет явно добавлять столбцы i. в исходную таблицу:

com.cols    = setdiff(intersect(names(table1), names(table2)), "id")
i.com.cols  = paste0("i.", com.cols)
# I'm using the same var names as Frank, but new.cols is strictly the new ones here
new.cols    = setdiff(names(table2), names(table1))

# this is easy - the previously absent cols
table1[table2, (new.cols) := mget(new.cols), on = 'id']

# now for the ones that need updating
table1[table2, on = 'id',
       (com.cols) := Map(function(col, i.col) pmin(col, i.col, na.rm = T),
                         mget(com.cols), mget(i.com.cols))]

Я понятия не имею, какой вариант быстрее - OP может проверить это.

Ответ 3

EDIT: Быстрая настройка

Сделайте это сначала, чтобы получить все необходимые строки:

table1 <- table1[.(id = union(id, table2$id)), on = "id"]

Мне не нравится mget, поэтому я предлагаю следующее:

in_common <- parse(text=setdiff(intersect(names(table1), names(table2)), "id"))

for (ii in in_common)
  table1[is.na(eval(ii)), 
         as.character(ii) :=
           table2[.SD, eval(ii), on = "id"]]

Новые столбцы просты:

new_cols <- setdiff(names(table2), names(table1))

for (jj in new_cols)
  table1[table2, (jj) := eval(parse(text = jj)), on = "id"]

Я полагаю, что еще быстрее сделать что-то вроде:

in_common_c <- setdiff(intersect(names(table1), names(table2)), "id"))
in_common_q <- parse(text=in_common_c)

for (ii in seq_along(in_common_q))
  table1[is.na(eval(in_common_q[ii])), 
         in_common_c[ii] :=
           table2[.SD, eval(in_common_q[ii]), on = "id"]]

Но я надеюсь, что разница незначительна.