2012-05-14 17 views
8

Chcę określić klasy kolumn dużej tabeli danych.R: pętla nad kolumnami w data.table

colClasses <- sapply(DT, FUN=function(x)class(x)[1]) 

działa, ale najwyraźniej lokalne kopie są przechowywane w pamięci:

> memory.size() 
[1] 687.59 
> colClasses <- sapply(DT, class) 
> memory.size() 
[1] 1346.21 

Pętla nie wydaje się możliwe, ponieważ data.table „z = FAŁSZ” zawsze skutkuje data.table.

Szybkie i bardzo brudna metoda jest:

DT1 <- DT[1, ] 
colClasses <- sapply(DT1, FUN=function(x)class(x)[1]) 

Co jest najbardziej eleganckimi i skuteczny sposób to zrobić?

+1

Nie jestem pewien, czy podążam. Dlaczego nie tylko "sapply (DT, klasa)"? –

+0

czasy dodane w tekście powyżej –

+3

@MatthewDowle: Myślę, że OP oznacza, że ​​sapply tworzy tymczasowe zmienne z podzbiorami danych.tabela, aby przejść do FUN dla każdej kolumny. Ponieważ jego data.table jest naprawdę duża i ma wiele kolumn, nie jest wydajna. Z tego powodu jego obejście polega na zmniejszeniu danych najpierw do jednego rzędu, a potem zadzwoń do ... – digEmAll

Odpowiedz

10

Zrobiłem krótką analizę i wygląda na to, że wystąpił błąd data.table.

> DT = data.table(a=1:1e6,b=1:1e6,c=1:1e6,d=1:1e6) 
> Rprofmem() 
> sapply(DT,class) 
     a   b   c   d 
"integer" "integer" "integer" "integer" 
> Rprofmem(NULL) 
> noquote(readLines("Rprofmem.out")) 
[1] 4000040 :"as.list.data.table" "as.list" "lapply" "sapply"  
[2] 4000040 :"as.list.data.table" "as.list" "lapply" "sapply" 
[3] 4000040 :"as.list.data.table" "as.list" "lapply" "sapply" 
[4] 4000040 :"as.list.data.table" "as.list" "lapply" "sapply" 

> tracemem(DT) 
> sapply(DT,class) 
tracemem[000000000431A290 -> 00000000065D70D8]: as.list.data.table as.list lapply sapply 
     a   b   c   d 
"integer" "integer" "integer" "integer" 

Więc patrząc na as.list.data.table:

> data.table:::as.list.data.table 
function (x, ...) 
{ 
    ans <- unclass(x) 
    setattr(ans, "row.names", NULL) 
    setattr(ans, "sorted", NULL) 
    setattr(ans, ".internal.selfref", NULL) 
    ans 
} 
<environment: namespace:data.table> 
> 

Zanotować brzydkie unclass na pierwszej linii. ?unclass potwierdza, że ​​zajmuje głęboką kopię swojej argumentacji. Z tego szybkiego wyglądu nie wydaje się, aby kopiowanie było wykonywane przez sapply lub lapply (nie sądziłem, że zrobili, ponieważ R jest dobry w kopiowaniu przy zapisie, a nie są to pisanie), ale raczej as.list w lapply (które wysyła do as.list.data.table).

Tak więc, jeśli unikniemy modelu unclass, powinno przyspieszyć. Spróbujmy:

> DT = data.table(a=1:1e7,b=1:1e7,c=1:1e7,d=1:1e7) 
> system.time(sapply(DT,class)) 
    user system elapsed 
    0.28 0.06 0.35 
> system.time(sapply(DT,class)) # repeat timing a few times and take minimum 
    user system elapsed 
    0.17 0.00 0.17 
> system.time(sapply(DT,class)) 
    user system elapsed 
    0.13 0.04 0.18 
> system.time(sapply(DT,class)) 
    user system elapsed 
    0.14 0.03 0.17 
> assignInNamespace("as.list.data.table",function(x)x,"data.table") 
> data.table:::as.list.data.table 
function(x)x 
> system.time(sapply(DT,class)) 
    user system elapsed 
     0  0  0 
> system.time(sapply(DT,class)) 
    user system elapsed 
    0.01 0.00 0.02 
> system.time(sapply(DT,class)) 
    user system elapsed 
     0  0  0 
> sapply(DT,class) 
     a   b   c   d 
"integer" "integer" "integer" "integer" 
> 

Tak, tak, nieskończenie lepiej.

mam podniesione bug report #2000 usunąć metodę as.list.data.table, ponieważ data.tableis() już list, too. Może to przyspieszyć kilka idiomów, takich jak lapply(.SD,...). [EDIT: Naprawiono to w wersji 1.8.1].

Dzięki za pytanie!

+0

Bardzo pouczający post. Dziękuję za pokazanie kroków użytych do debugowania tego. –

+0

Dzięki Matthew! –

2

Nie widzę nic złego w podejściu jak to

colClasses <- sapply(head(DT1,1), FUN=class) 

Jest to w zasadzie rozwiązanie quick'n'dirty ale może nieco jaśniejsze (nawet jeśli nie tak dużo) ...

+0

To naprawdę dobre rozwiązanie, ale nie tak eleganckie, na co miałem nadzieję. –

+0

@ user1393348: tak, nadal jest to obejście :) – digEmAll