Добавить (не объединить!) два фрейма данных с неравными строками и столбцами

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

  • если сопряжение строк / столбцов принадлежит обоим входным кадрам данных Я хочу, чтобы результат включал их сумму
  • если спаривание строки / столбца принадлежит только одному входному фрейму данных, я хочу включить это значение в выход
  • если спаривание строки / столбца не принадлежит ни одной входной матрице, я хочу иметь 0 в этой позиции на выходе.

в качестве примера можно рассмотреть следующие входные данные:

df1 = data.frame(x = c(1,2,3), y = c(4,5,6))
rownames(df1) = c("a", "b", "c")
df2 = data.frame(x = c(7,8), z = c(9,10), w = c(2, 3))
rownames(df2) = c("a", "d")
> df1
  x y
a 1 4
b 2 5
c 3 6
> df2
  x  z  w 
a 7  9  2
d 8 10  3

Я хочу, чтобы конечный результат был

> df2
   x  y   z  w
a  8  4   9  2
b  2  5   0  0
c  3  6   0  0
d  8  0  10  3

что я сделал до сих пор -

bind_rows / bind_cols в dplyr может бросить следующее: "Ошибка: несовместимое количество строк (3, ожидание 2)"

у меня есть дублированные имена столбцов, поэтому "merge" также не работает для моих целей - по какой-то причине возвращает пустой df.

5 ответов


кажется, что вы можете объединить имена строк, а затем позаботиться о суммах и преобразовании NA до нуля с некоторым дополнительным munging:

library(dplyr)

df.new = df1 %>% add_rownames %>%
  full_join(df2 %>% add_rownames, by="rowname") %>%
  mutate_each(funs(replace(., which(is.na(.)), 0))) %>%
  mutate(x = x.x + x.y) %>%
  select(rowname,x,y,z,w)

или с гораздо более элегантным и расширяемым решением @DavidArenburg:

df.new = df1 %>% add_rownames %>% 
  full_join(df2 %>% add_rownames) %>% 
  group_by(rowname) %>% 
  summarise_each(funs(sum(., na.rm = TRUE)))

df.new

  rowname     x     y     z     w
1       a     8     4     9     2
2       b     2     5     0     0
3       c     3     6     0     0
4       d     8     0    10     3

это похоже на какое-то простое слияние общих имен столбцов (+ имена строк), а затем простое агрегирование, вот как я бы справился с этим

library(data.table)
merge(setDT(df1, keep.rownames = TRUE), # Convert to data.table + keep rows
      setDT(df2, keep.rownames = TRUE), # Convert to data.table + keep rows
      by = intersect(names(df1), names(df2)), # merge on common column names
      all = TRUE)[, lapply(.SD, sum, na.rm = TRUE), by = rn] # Sum all columns by group                   
#    rn x y  z w
# 1:  a 8 4  9 2
# 2:  b 2 5  0 0
# 3:  c 3 6  0 0
# 4:  d 8 0 10 3

являются довольно прямым базовым решением R

df1$rn <- row.names(df1)
df2$rn <- row.names(df2)
res <- merge(df1, df2, all = TRUE)
rowsum(res[setdiff(names(res), "rn")], res[, "rn"], na.rm = TRUE)
#   x y  z w
# a 8 4  9 2
# b 2 5  0 0
# c 3 6  0 0
# d 8 0 10 3

во-первых, я бы схватил имена всех строк и столбцов нового объекта:

(all.rows <- unique(c(row.names(df1), row.names(df2))))
# [1] "a" "b" "c" "d"
(all.cols <- unique(c(names(df1), names(df2))))
# [1] "x" "y" "z" "w"

затем я бы построил выходную матрицу с этими строками и именами столбцов (с матричными данными, инициализированными для всех 0s), добавив df1 и df2 к соответствующим частям этой матрицы.

out <- matrix(0, nrow=length(all.rows), ncol=length(all.cols))
rownames(out) <- all.rows
colnames(out) <- all.cols
out[row.names(df1),names(df1)] <- unlist(df1)
out[row.names(df2),names(df2)] <- out[row.names(df2),names(df2)] + unlist(df2)
out
#   x y  z w
# a 8 4  9 2
# b 2 5  0 0
# c 3 6  0 0
# d 8 0 10 3

используя xtabs на расплавленных / сложенных фреймах данных:

out <- rbind(cbind(rn=rownames(df1),stack(df1)), cbind(rn=rownames(df2),stack(df2)))
as.data.frame.matrix(xtabs(values ~ rn + ind, data=out))

#  x y w  z
#a 8 4 2  9
#b 2 5 0  0
#c 3 6 0  0
#d 8 0 3 10

Я не уверен, что принятый (или альтернативный метод слияния) является лучшим. Это даст неправильные результаты, если у вас есть общие строки, они будут объединены, а не суммированы.

Это может быть показано trivialy путем изменения df2 на:

df2 = data.frame(x = c(1,2), y = c(4,5), z = c(9,10), w = c(2, 3))
rownames(df2) = c("a", "d")

ожидаемые результаты:

   rn x y  z w
1:  a 2 8  9 2
2:  b 2 5  0 0
3:  c 3 6  0 0
4:  d 2 5 10 3

фактические результаты

merge(setDT(df1, keep.rownames = TRUE), 
  setDT(df2, keep.rownames = TRUE), 
  by = intersect(names(df1), names(df2)), 
  all = TRUE)[, lapply(.SD, sum, na.rm = TRUE), by = rn]

   rn x y  z w
1:  a 1 4  9 2
2:  b 2 5  0 0
3:  c 3 6  0 0
4:  d 2 5 10 3

вам нужно объединить как внешнее соединение с внутренним соединением (или левое/правое соединение, объединить все=T/all=F). Или альтернативно используя rbind plyr.заполнить :

базовое решение R

res <- rbind.fill(df1,df2)
rowsum(res[setdiff(names(res), "rn")], res[, "rn"], na.rm = TRUE)

таблица данных

as.data.table(rbind.fill(
  setDT(df1, keep.rownames = TRUE),
  setDT(df2, keep.rownames = TRUE)
))[, lapply(.SD, sum, na.rm = TRUE), by = rn]

Я предпочитаю rbind.заполните метод, как вы можете "объединить" > 2 фрейма данных, используя тот же синтаксис.