Rbind с новыми столбцами и data.table
Мне нужно добавить много больших таблиц в существующую таблицу, поэтому я использую rbind с отличным пакетом data.table. Но некоторые из более поздних таблиц имеют больше столбцов, чем исходные (которые необходимо включить). Есть ли эквивалент rbind.fill для data.table?
library(data.table)
aa <- c(1,2,3)
bb <- c(2,3,4)
cc <- c(3,4,5)
dt.1 <- data.table(cbind(aa, bb))
dt.2 <- data.table(cbind(aa, bb, cc))
dt.11 <- rbind(dt.1, dt.1) # Works, but not what I need
dt.12 <- rbind(dt.1, dt.2) # What I need, doesn't work
dt.12 <- rbind.fill(dt.1, dt.2) # What I need, doesn't work either
Мне нужно запустить rbinding, прежде чем у меня появятся все таблицы, так что вы не сможете узнать, какие будущие новые столбцы будут вызваны. Отсутствующие данные могут быть заполнены NA.
Ответы
Ответ 1
Вот подход, который обновит отсутствующие столбцы в
rbind.missing <- function(A, B) {
cols.A <- names(A)
cols.B <- names(B)
missing.A <- setdiff(cols.B,cols.A)
# check and define missing columns in A
if(length(missing.A) > 0L){
# .. means "look up one level"
class.missing.A <- lapply(B[, ..missing.A], class)
nas.A <- lapply(class.missing.A, as, object = NA)
A[,c(missing.A) := nas.A]
}
# check and define missing columns in B
missing.B <- setdiff(names(A), cols.B)
if(length(missing.B) > 0L){
class.missing.B <- lapply(A[, ..missing.B], class)
nas.B <- lapply(class.missing.B, as, object = NA)
B[,c(missing.B) := nas.B]
}
# reorder so they are the same
setcolorder(B, names(A))
rbind(A, B)
}
rbind.missing(dt.1,dt.2)
## aa bb cc
## 1: 1 2 NA
## 2: 2 3 NA
## 3: 3 4 NA
## 4: 1 2 3
## 5: 2 3 4
## 6: 3 4 5
Это не будет эффективно для многих или больших таблиц data.tables, так как работает только по два одновременно.
Ответ 2
Так как v1.9.2
, data.table
функция rbind получила аргумент fill
. В документации ?rbind.data.table
:
Если TRUE заполняет отсутствующие столбцы с NA. По умолчанию FALSE. когда ИСТИНА, use.names должна быть ИСТИНА, и все элементы входного списка должны имеют ненулевые имена столбцов.
Таким образом, вы можете сделать (до приближения v1.9.6):
data.table::rbind(dt.1, dt.2, fill=TRUE)
# aa bb cc
# 1: 1 2 NA
# 2: 2 3 NA
# 3: 3 4 NA
# 4: 1 2 3
# 5: 2 3 4
# 6: 3 4 5
ОБНОВЛЕНИЕ для v1.9.6:
Теперь это работает напрямую:
rbind(dt.1, dt.2, fill=TRUE)
# aa bb cc
# 1: 1 2 NA
# 2: 2 3 NA
# 3: 3 4 NA
# 4: 1 2 3
# 5: 2 3 4
# 6: 3 4 5
Ответ 3
базовая концепция заключается в том, чтобы добавить отсутствующие столбцы в обоих направлениях: из текущей таблицы master
к newTable
и обратно в другую сторону.
Как заметил в комментариях @menl, просто назначение NA - проблема, потому что это будет
введите весь столбец class
logical
.
Одним из решений является принудительное выполнение всех столбцов одного типа (т.е. as.numeric(NA)
), но это слишком ограничительно.
Вместо этого нам нужно проанализировать каждый новый столбец для своего класса. Затем мы можем использовать as(NA, cc)
_ (cc
, являющийся классом)
как вектор, который мы назначим новому столбцу. Мы завершаем это в инструкции lapply
на RHS
и используем eval(columnName)
на LHS
для назначения.
Затем мы можем обернуть это в функцию и использовать методы S3, чтобы мы могли просто вызвать
rbindFill(A, B)
Ниже приведена функция.
rbindFill.data.table <- function(master, newTable) {
# Append newTable to master
# assign to Master
#-----------------#
# identify columns missing
colMisng <- setdiff(names(newTable), names(master))
# if there are no columns missing, move on to next part
if (!identical(colMisng, character(0))) {
# identify class of each
colMisng.cls <- sapply(colMisng, function(x) class(newTable[[x]]))
# assign to each column value of NA with appropriate class
master[ , eval(colMisng) := lapply(colMisng.cls, function(cc) as(NA, cc))]
}
# assign to newTable
#-----------------#
# identify columns missing
colMisng <- setdiff(names(master), names(newTable))
# if there are no columns missing, move on to next part
if (!identical(colMisng, character(0))) {
# identify class of each
colMisng.cls <- sapply(colMisng, function(x) class(master[[x]]))
# assign to each column value of NA with appropriate class
newTable[ , eval(colMisng) := lapply(colMisng.cls, function(cc) as(NA, cc))]
}
# reorder columns to avoid warning about ordering
#-----------------#
colOrdering <- colOrderingByOtherCol(newTable, names(master))
setcolorder(newTable, colOrdering)
# rbind them!
#-----------------#
rbind(master, newTable)
}
# implement generic function
rbindFill <- function(x, y, ...) UseMethod("rbindFill")
Пример использования
# Sample Data:
#--------------------------------------------------#
A <- data.table(a=1:3, b=1:3, c=1:3)
A2 <- data.table(a=6:9, b=6:9, c=6:9)
B <- data.table(b=1:3, c=1:3, d=1:3, m=LETTERS[1:3])
C <- data.table(n=round(rnorm(3), 2), f=c(T, F, T), c=7:9)
#--------------------------------------------------#
# Four iterations of calling rbindFill
master <- rbindFill(A, B)
master <- rbindFill(master, A2)
master <- rbindFill(master, C)
# Results:
master
# a b c d m n f
# 1: 1 1 1 NA NA NA NA
# 2: 2 2 2 NA NA NA NA
# 3: 3 3 3 NA NA NA NA
# 4: NA 1 1 1 A NA NA
# 5: NA 2 2 2 B NA NA
# 6: NA 3 3 3 C NA NA
# 7: 6 6 6 NA NA NA NA
# 8: 7 7 7 NA NA NA NA
# 9: 8 8 8 NA NA NA NA
# 10: 9 9 9 NA NA NA NA
# 11: NA NA 7 NA NA 0.86 TRUE
# 12: NA NA 8 NA NA -1.15 FALSE
# 13: NA NA 9 NA NA 1.10 TRUE
Ответ 4
Еще один способ вставить недостающие столбцы (с правильным типом и NA) - это merge()
первая data.table A
с пустой data.table A2[0]
, которая имеет структуру вторых данных. Таблица. Это позволяет избежать ошибок в пользовательских функциях (я знаю, что merge()
более надежный, чем мой собственный код;)). Используя таблицы mnel сверху, сделайте что-то вроде кода ниже.
Кроме того, использование rbindlist()
должно быть намного быстрее при работе с data.tables
.
Определите таблицы (такие же, как и код mnel выше):
library(data.table)
A <- data.table(a=1:3, b=1:3, c=1:3)
A2 <- data.table(a=6:9, b=6:9, c=6:9)
B <- data.table(b=1:3, c=1:3, d=1:3, m=LETTERS[1:3])
C <- data.table(n=round(rnorm(3), 2), f=c(T, F, T), c=7:9)
Вставьте отсутствующие переменные в таблицу A: (обратите внимание на использование A2[0]
A <- merge(x=A, y=A2[0], by=intersect(names(A),names(A2)), all=TRUE)
Вставьте отсутствующие столбцы в таблицу A2:
A2 <- merge(x=A[0], y=A2, by=intersect(names(A),names(A2)), all=TRUE)
Теперь A
и A2
должны иметь одинаковые столбцы с одинаковыми типами. Задайте порядок столбцов, на всякий случай (возможно, не обязательно, не уверены, что rbindlist()
связывается между именами столбцов или столбцами):
setcolorder(A2, names(A))
DT.ALL <- rbindlist(l=list(A,A2))
DT.ALL
Повторите для других таблиц... Возможно, было бы лучше включить это в функцию, а не повторять вручную...
DT.ALL <- merge(x=DT.ALL, y=B[0], by=intersect(names(DT.ALL), names(B)), all=TRUE)
B <- merge(x=DT.ALL[0], y=B, by=intersect(names(DT.ALL), names(B)), all=TRUE)
setcolorder(B, names(DT.ALL))
DT.ALL <- rbindlist(l=list(DT.ALL, B))
DT.ALL <- merge(x=DT.ALL, y=C[0], by=intersect(names(DT.ALL), names(C)), all=TRUE)
C <- merge(x=DT.ALL[0], y=C, by=intersect(names(DT.ALL), names(C)), all=TRUE)
setcolorder(C, names(DT.ALL))
DT.ALL <- rbindlist(l=list(DT.ALL, C))
DT.ALL
Результат выглядит так же, как вывод mnels (за исключением случайных чисел и порядка столбцов).
PS1: оригинальный автор не говорит, что делать, если есть соответствующие переменные - действительно ли мы хотим сделать rbind()
или думаем о merge()
?
PS2: (Поскольку у меня недостаточно репутации для комментариев) Суть вопроса кажется дубликатом этого вопроса. Также важно для бенчмаркинга data.table
vs. plyr
с большими наборами данных.
Ответ 5
Ответы потрясающие, но похоже, есть некоторые функции, предлагаемые здесь, такие как plyr:: rbind.fill и gtools:: smartbind, которые, казалось, отлично работали для меня.