2015-12-04 10 views
13

Domanda:Convertire colonne di classe arbitraria alla classe di colonne corrispondenti in un altro data.table

sto lavorando in R. Voglio le colonne condivise di 2 data.tables (comune che significa lo stesso nome di colonna) per avere classi corrispondenti. Sto lottando con un modo per convertire genericamente un oggetto di classe sconosciuta alla classe sconosciuta di un altro oggetto.


Altro contesto:

so come impostare la classe di una colonna in un data.table, e so sulla la funzione as. Inoltre, questa domanda non è interamente specifica data.table, ma si presenta spesso quando utilizzo data.table s. Inoltre, supponiamo che la coercizione desiderata sia possibile.

Ho 2 data.tables. Condividono alcuni nomi di colonne e tali colonne sono destinate a rappresentare le stesse informazioni. Per i nomi di colonna condivisi dalla tabella A e dalla tabella B, voglio che le classi di A corrispondano a quelle di B (o altro).


Esempio 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")) 

Ecco quello che sembrano:

> 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 

Qui ci sono le classi:

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

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

Manualmente, posso realizzare il compito desiderato attraverso il seguente:

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

Questo è facile quando c'è solo 1 delle colonne per cambiare, si sa che di colonna prima del tempo, e si conosce la classe desiderata prima del tempo. Se lo si desidera, è anche abbastanza facile convertire colonne arbitrarie in una determinata classe. So anche come convertire colonne arbitrarie in una data classe.


Il mio tentativo è fallito:

(EDIT: Questo funziona in realtà, vedi la mia risposta)

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])) 
} 

Ma questo ancora restituisce la colonna anno come "integer", non "numeric":

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

Il problema nell'esempio precedente è che class(as(1L, "numeric")) restituisce ancora "integer". D'altra parte, class(as.numeric(1L)) restituisce "numeric"; tuttavia, non so in anticipo che sia necessario il numero as.numeric.


domanda, Restated:

Come faccio a fare la partita classi colonna, quando né colonne né i to/fromclassi sono noti prima del tempo?


Pensieri supplementari:

In un certo senso, la questione è per lo più sulla corrispondenza di classe arbitraria. Mi imbatto in questo problema spesso con data.table perché è molto vocale sulla corrispondenza delle classi. Ad esempio, si verificano problemi simili quando è necessario inserire NA del tipo appropriato (NA_real_ rispetto a NA_character_, ecc.), A seconda della classe della colonna (vedere domanda/problema correlato in This Question).

Ancora una volta, questa domanda può essere vista come un problema generale di conversione tra classi arbitrarie che non sono conosciute in anticipo. In passato, ho scritto delle funzioni usando switch per fare qualcosa come switch(class(x), double = as.numeric(...), character = as.character(...), ..., ma questo mi sembra un po 'brutto. L'unica ragione per cui sto portando tutto questo nel contesto di data.table è perché è il posto dove più spesso incontro la necessità di questo tipo di funzionalità.

+0

Forse fare 'lapply (A,.%>% As.character%>% type.convert)' o simili su ognuno di essi. (Senza libreria (magrittr), questo è 'lapply (A, function (x) type.convert (as.character (x)))'). Questo è un modo molto crudo, però, e fallirà con classi di fantasia. – Frank

+0

@Frank Non voglio necessariamente che entrambi condividano una classe arbitraria (carattere), ho bisogno di A per abbinare la classe di B. Questo è diverso dal tuo suggerimento, giusto? – rbatt

+0

L'idea alla base del mio suggerimento era di dare loro la stessa classe, non necessariamente il carattere. 'type.convert' è la funzione utilizzata durante la lettura dei dati da un file di testo per determinare la classe di ciascuna colonna (ad esempio, in' fread' di data.table). Ho una variante di questa idea, ma è più lunga quindi la metterò in una risposta – Frank

risposta

5

Non molto elegante, ma si può 'costruire' il as.* chiamata in questo modo:

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

approccio 'dati.table' (?):' Per (nome col in (A)) set (A, j = col, value = eval (call (paste0 ("come.", B.class [col]), A [[col]]))) '. Vedere la mia domanda per le definizioni di 'B.class' e la funzione' s2c' – rbatt

+0

FWIW, tratterei questo come lavoro preliminare sul set di dati, non ho assolutamente alcun indizio se farlo con una chiamata DT sarebbe molto più veloce (nessun oggetto cresce lì e, a meno che tu non abbia una quantità enorme di osservazioni, non dovrebbe impiegare troppo tempo). Ma potrei essere assolutamente sbagliato :) – Tensibai

5

Questo è un modo molto grezzo per garantire classi comuni:

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" 

non mi piace questa soluzione:

  • I routine utilizzino tutti interi codici per gli ID (come "00001", "02995"), e questo li costringerebbe a interi effettivi, il che è male.
  • Chi sa cosa farà per classi di classe come Date o factor? Questo non avrà molta importanza se si esegue questa normalizzazione col-classes non appena si leggono i dati, suppongo.

dati:

# 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"))) 

commento. In caso di problemi con magrittr, utilizzare function(x) type.convert(as.character(x)) al posto del bit . %>%.

+0

Quindi, io sono in attesa di vedere un'idea migliore. Sto semplicemente espandendo il mio commento originale sulla domanda. – Frank

+1

Per riferimento, [ecco la fonte C sottostante 'type.convert'] (https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/library/utils/src/io.c#L550- L759), chiunque dovrebbe cercare ispirazione – rbatt

+1

Il carattere intermedio "intermedio" mi rende un po 'a disagio. Ho una situazione particolare che richiede A per abbinare perfettamente B, e so che dovrebbero * corrispondere (A è indirettamente derivato da B, in qualche modo); quindi il tuo punto sulle classi "strane" suona vero per me qui. Ma ci proverò, perché mi piace molto 'type.convert', e non ne sapevo nulla in precedenza; forse funzionerà. – rbatt

1

Sulla base della discussione in this question, e commenti in this answer, sto pensando forse ho avuto modo giusto, e appena sbarcato su una strana eccezione.

Si noti che la classe non cambia, ma la tecnicità è che non importa (per il mio caso particolare d'uso che ha spinto la domanda). Qui sotto mostro il mio "approccio fallito", ma seguendo l'unione e le classi delle colonne nel data.table unite, possiamo vedere perché l'approccio funziona: gli interi verranno semplicemente promossi.

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" 

Anche se il problema in questione non si risolve questa risposta, ho pensato che avrei posto a sottolineare che la mancata conversione "integer"-"numeric" potrebbe non essere un problema in molte situazioni, quindi questo è una soluzione semplice, seppure circostanziale.

Problemi correlati