Rbindlist два data.tables, где один имеет коэффициент, а другой - тип символа для столбца

Я только что обнаружил это предупреждение в моем script, что было немного странно.

# Warning message:
# In rbindlist(list(DT.1, DT.2)) : NAs introduced by coercion

Наблюдение 1: Здесь воспроизводимый пример:

require(data.table)
DT.1 <- data.table(x = letters[1:5], y = 6:10)
DT.2 <- data.table(x = LETTERS[1:5], y = 11:15)

# works fine
rbindlist(list(DT.1, DT.2))
#     x  y
#  1: a  6
#  2: b  7
#  3: c  8
#  4: d  9
#  5: e 10
#  6: A 11
#  7: B 12
#  8: C 13
#  9: D 14
# 10: E 15

Однако теперь, если я конвертирую столбец x в factor (упорядоченный или нет) и делаю то же самое:

DT.1[, x := factor(x)]
rbindlist(list(DT.1, DT.2))
#      x  y
#  1:  a  6
#  2:  b  7
#  3:  c  8
#  4:  d  9
#  5:  e 10
#  6: NA 11
#  7: NA 12
#  8: NA 13
#  9: NA 14
# 10: NA 15
# Warning message:
# In rbindlist(list(DT.1, DT.2)) : NAs introduced by coercion

Но rbind выполняет эту работу красиво!

rbind(DT.1, DT.2) # where DT.1 has column x as factor
# do.call(rbind, list(DT.1, DT.2)) # also works fine
#     x  y
#  1: a  6
#  2: b  7
#  3: c  8
#  4: d  9
#  5: e 10
#  6: A 11
#  7: B 12
#  8: C 13
#  9: D 14
# 10: E 15

Такое же поведение можно воспроизвести, если столбец x также является ordered factor. Поскольку справочная страница ?rbindlist говорит: Same as do.call("rbind",l), but much faster., я предполагаю, что это не желаемое поведение?


Здесь моя информация о сеансе:

# R version 3.0.0 (2013-04-03)
# Platform: x86_64-apple-darwin10.8.0 (64-bit)
# 
# locale:
# [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
# 
# attached base packages:
# [1] stats     graphics  grDevices utils     datasets  methods   base     
# 
# other attached packages:
# [1] data.table_1.8.8
# 
# loaded via a namespace (and not attached):
# [1] tools_3.0.0

Изменить:

Наблюдение 2: Следуя @AnandaMahto еще одно интересное наблюдение, изменив порядок:

# column x in DT.1 is still a factor
rbindlist(list(DT.2, DT.1))
#     x  y
#  1: A 11
#  2: B 12
#  3: C 13
#  4: D 14
#  5: E 15
#  6: 1  6
#  7: 2  7
#  8: 3  8
#  9: 4  9
# 10: 5 10

Здесь столбец из DT.1 беззвучно принуждается к numeric.
Изменить: Просто для того, чтобы уточнить, это то же поведение, что и для rbind(DT2, DT1), когда столбец DT1 х является фактором. rbind, похоже, сохраняет класс первого аргумента. Я оставлю эту часть здесь и упомянул, что в этом случае это желаемое поведение, поскольку rbindlist является более быстрой реализацией rbind.

Наблюдение 3: Если теперь оба столбца преобразуются в факторы:

# DT.1 column x is already a factor
DT.2[, x := factor(x)]
rbindlist(list(DT.1, DT.2))
#     x  y
#  1: a  6
#  2: b  7
#  3: c  8
#  4: d  9
#  5: e 10
#  6: a 11
#  7: b 12
#  8: c 13
#  9: d 14
# 10: e 15

Здесь столбец x из DT.2 теряется (/заменяется столбцом DT.1). Если порядок отменен, то происходит обратное (столбец x из DT.1 заменяется символом DT.2).

Как правило, проблема с обработкой столбцов factor в rbindlist.

Ответ 1

ОБНОВЛЕНИЕ - эта ошибка (# 2650) была исправлена ​​17 мая 2013 г. в версии 1.8.9


Я считаю, что rbindlist при применении к факторам объединяет числовые значения факторов и использует только уровни, связанные с первым элементом списка.

Как и в этом отчете об ошибке: http://r-forge.r-project.org/tracker/index.php?func=detail&aid=2650&group_id=240&atid=975


# Temporary workaround: 

levs <- c(as.character(DT.1$x), as.character(DT.2$x))

DT.1[, x := factor(x, levels=levs)]
DT.2[, x := factor(x, levels=levs)]

rbindlist(list(DT.1, DT.2))

В качестве другого представления о том, что происходит:

DT3 <- data.table(x=c("1st", "2nd"), y=1:2)
DT4 <- copy(DT3)

DT3[, x := factor(x, levels=x)]
DT4[, x := factor(x, levels=x, labels=rev(x))]

DT3
DT4

# Have a look at the difference:
rbindlist(list(DT3, DT4))$x
# [1] 1st 2nd 1st 2nd
# Levels: 1st 2nd

do.call(rbind, list(DT3, DT4))$x
# [1] 1st 2nd 2nd 1st
# Levels: 1st 2nd

Редактировать в соответствии с комментариями:

как для наблюдения 1, то, что происходит, похоже на:

x <- factor(LETTERS[1:5])

x[6:10] <- letters[1:5]
x

# Notice however, if you are assigning a value that is already present
x[11] <- "S"  # warning, since `S` is not one of the levels of x
x[12] <- "D"  # all good, since `D` *is* one of the levels of x

Ответ 2

rbindlist является сверхбыстрой, поскольку он не выполняет проверку rbindfill или do.call(rbind.data.frame,...)

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

DT.1 <- data.table(x = factor(letters[1:5]), y = 6:10)
DT.2 <- data.table(x = LETTERS[1:5], y = 11:15)


for(ii in seq_along(DDL)){
  ff <- Filter(function(x) is.factor(DDL[[ii]][[x]]), names(DDL[[ii]]))
  for(fn in ff){
    set(DDL[[ii]], j = fn, value = as.character(DDL[[ii]][[fn]]))
    }
  }
 rbindlist(DDL)

или (менее эффективно память)

rbindlist(rapply(DDL, classes = 'factor', f= as.character, how = 'replace'))