Подтвердить что ты не робот

Преобразование столбцов произвольного класса в класс сопоставимых столбцов в другой таблице данных.

Вопрос:

Я работаю в R. Я хочу, чтобы общие столбцы из 2 data.tables(с общим значением одинакового имени столбца) имели соответствующие классы. Я изо всех сил пытаюсь преобразовать объект неизвестного класса в неизвестный класс другого объекта.


Больше контекста:

Я знаю, как установить класс столбца в data.table, и я знаю о функции as. Кроме того, этот вопрос не совсем специфичен для data.table, но часто возникает, когда я использую data.table s. Далее предположим, что желаемое принуждение возможно.

У меня есть 2 data.tables. Они имеют несколько имен столбцов, и эти столбцы предназначены для представления одной и той же информации. Для имен столбцов, разделяемых таблицей A и таблицей B, я хочу, чтобы классы A соответствовали классам B (или другим способом).


Пример data.table s:

A <- structure(list(year = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L), stratum = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L)), .Names = c("year", "stratum"), row.names = c(NA, -45L), class = c("data.table", "data.frame"))

B <- structure(list(year = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), stratum = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L), bt = c(-9.95187702337873, -9.48946944434626, -9.74178662514147, -5.36167545158338, -4.76405522202426, -5.41964239804882, -0.0807951335119085, 0.520481719699774, 0.0393874225863578, 5.40557402913123, 5.47927931969583, 5.37228402911139, 9.82774396910091, 9.89629694010177, 9.98105260936272, -9.82469892896284, -9.42530210357904, -9.66171049964775, -5.17540952901709, -4.81859082470115, -5.3577146169737, -0.0685310909609001, 0.441383303157166, -0.0105897444321987, 5.24205882775199, 5.65773605162835, 5.40217185632441, 9.90299445851434, 9.78883672575814, 9.98747998379124, -9.69843398105195, -9.31530717395811, -9.77406601252698, -4.83080164375344, -4.89056304189872, -5.3904000267275, -0.121508487954861, 0.493798577602088, -0.118550709142654, 5.23654772583187, 5.87760447006892, 5.22478092346285, 9.90949768116403, 9.85433376398086, 9.91619307289277), yr = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3)), .Names = c("year", "stratum", "bt", "yr"), row.names = c(NA, -45L), class = c("data.table", "data.frame"), sorted = c("year", "stratum"))

Вот как они выглядят:

> A  
    year stratum
 1:    1       1
 2:    1       2
 3:    1       3
 4:    1       4

> B
    year stratum          bt yr
 1:    1       1 -9.95187702  1
 2:    1       2 -9.48946944  1
 3:    1       3 -9.74178663  1
 4:    1       4 -5.36167545  1

Вот классы:

> sapply(A, class)
     year   stratum 
"integer" "integer"

> sapply(B, class)
     year   stratum        bt        yr 
"numeric" "integer" "numeric" "numeric"

Вручную, я могу выполнить желаемую задачу следующим образом:

A[,year:=as.numeric(year)]

Это легко, когда меняется только один столбец, вы знаете этот столбец раньше времени, и вы заранее знаете желаемый класс. При желании также легко преобразовать произвольные столбцы в заданный класс. Я также знаю, как конвертировать произвольные столбцы в любой класс.


Моя неудачная попытка:

(EDIT: Это действительно работает, см. мой ответ)

s2c <- function (x, type = "list") 
{
    as.call(lapply(c(type, x), as.symbol))
}

# In this case, I can assume all columns of A can be found in B
# I am also able to assume that the desired conversion is possible
B.class <- sapply(B[,eval(s2c(names(A)))], class) 
for(col in names(A)){
    set(A, j=col, value=as(A[[col]], B.class[col]))
}

Но это все равно возвращает столбец года как "integer", а не "numeric":

> sapply(A, class)
     year   stratum 
"integer" "integer" 

Проблема в приведенном выше примере состоит в том, что class(as(1L, "numeric")) все еще возвращает "integer". С другой стороны, class(as.numeric(1L)) возвращает "numeric"; однако я не знаю заранее, что требуется as.numeric.


Вопрос, пересчитано:

Как мне сопоставить классы столбцов, когда раньше не известны ни столбцы, ни классы to/from?


Дополнительные мысли:

В некотором смысле вопрос в основном связан с произвольным сопоставлением классов. Я часто сталкиваюсь с этой проблемой с data.table, потому что он очень вокал о сопоставлении классов. Например, я сталкиваюсь с аналогичными проблемами, когда необходимо вставить NA соответствующего типа (NA_real_vs NA_character_ и т.д.), В зависимости от класса столбца (см. Соответствующий вопрос/вопрос в Этот вопрос).

Опять же, этот вопрос можно рассматривать как общую проблему преобразования между произвольными классами, которые заранее не известны. Раньше я писал функции, используя switch, чтобы сделать что-то вроде switch(class(x), double = as.numeric(...), character = as.character(...), ..., но это кажется большим уродливым. Единственная причина, по которой я это делаю в контексте data.table, - это то, где я чаще всего сталкиваюсь с необходимостью такого типа функций.

4b9b3361

Ответ 1

Не очень элегантный, но вы можете "построить" вызов as.* следующим образом:

for (x in colnames(A)) { A[,x] <- eval( call( paste0("as.", class(B[,x])), A[,x]) )}

Ответ 2

Это очень грубый способ обеспечить общие классы:

library(magrittr)

cols = intersect(names(A), names(B))
r    = rbindlist(list(A = A, B = B[,cols,with=FALSE]), idcol = TRUE)
r[, (cols) := lapply(.SD, . %>% as.character %>% type.convert), .SDcols=cols]
B[, (cols) := r[.id=="B", cols, with=FALSE]]
A[, (cols) := r[.id=="A", cols, with=FALSE]]

sapply(A, class); sapply(B, class)
#      year   stratum 
# "integer" "integer" 
#      year   stratum        yr 
# "integer" "integer" "numeric" 

Мне не нравится это решение:

  • Я обычно использую целые коды для идентификаторов (например, "00001", "02995"), и это будет приводить их к фактическим целям, что плохо.
  • Кто знает, что это будет делать с такими классами, как Date или factor? Это не имеет большого значения, если вы выполняете нормализацию классов, как только вы читаете данные, я полагаю.

Данные:

# slightly tweaked from OP
A <- setDT(structure(list(year = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L), stratum = c(1L, 2L, 
3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 2L, 3L, 4L, 5L, 
6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 
9L, 10L, 11L, 12L, 13L, 14L, 15L)), .Names = c("year", "stratum"), row.names = 
c(NA, -45L), class = c("data.frame")))

B <- setDT(structure(list(year = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 
3, 3, 3, 3), stratum = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 
14L, 15L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 1L, 
2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L), yr = c(1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3)), .Names = c("year", "stratum", 
"yr"), row.names = c(NA, -45L), class = c("data.frame")))

Комментарий. Если у вас есть что-то против magrittr, используйте function(x) type.convert(as.character(x)) вместо бит . %>%.

Ответ 3

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

Обратите внимание, что класс не изменяется, но техничность заключается в том, что это не имеет значения (для моего конкретного случая использования, вызвавшего вопрос). Ниже я показываю свой "неудачный подход", но, перейдя к слиянию и классам столбцов в объединенном data.table, мы можем понять, почему работает подход: целые числа будут только продвигаться.

s2c <- function (x, type = "list") 
{
    as.call(lapply(c(type, x), as.symbol))
}

# In this case, I can assume all columns of A can be found in B
# I am also able to assume that the desired conversion is possible
B.class <- sapply(B[,eval(s2c(names(A)))], class)
for(col in names(A)){
    set(A, j=col, value=as(A[[col]], B.class[col]))
}

# Below here is new from what I tried in question
AB <- data.table:::merge.data.table(A, B, all=T, by=c("stratum","year"))

sapply(AB, class)
  stratum      year        bt        yr 
"integer" "numeric" "numeric" "numeric" 

Хотя проблема в вопросе не решена этим ответом, я решил, что опубликую, чтобы указать, что отказ преобразовать "integer" в "numeric" может не быть проблемой во многих ситуациях, так что это прямое, хотя и косвенное, решение.