2013-03-07 21 views
10

Robię pewne agregacje na data.table (doskonały pakiet !!!) i znalazłem zmienną .SD bardzo przydatne dla wielu rzeczy. Jednak użycie go spowalnia znacznie obliczenia, gdy istnieje wiele grup. Następujący przykład:R data.table powolna agregacja podczas używania .SD

# A moderately big data.table 
x = data.table(id=sample(1e4,1e5,replace=T), 
       code=factor(sample(2,1e5,replace=T)), 
       z=runif(1e5) 
      ) 

setkey(x,id,code) 

system.time(x[,list(code2=nrow(.SD[code==2]), total=.N), by=id]) 
## user system elapsed 
## 6.226 0.000 6.242 

system.time(x[,list(code2=sum(code==2), total=.N), by=id]) 
## user system elapsed 
## 0.497 0.000 0.498 

system.time(x[,list(code2=.SD[code==2,.N], total=.N), by=id]) 
## user system elapsed 
## 6.152 0.000 6.168 

Czy robię coś nie tak? Czy powinienem unikać .SD na rzecz poszczególnych kolumn? Z góry dziękuję.

Odpowiedz

11

robię coś źle IE należy unikać .SD na rzecz poszczególnych kolumn?

Tak, dokładnie. Używaj tylko .SD, jeśli rzeczywiście używasz wszystkich danych wewnątrz .SD. Może się również okazać, że wywołanie nrow() i wywołanie podkwerendy do [.data.table wewnątrz j są również sprawcami: użyj Rprof, aby potwierdzić.

Zobacz ostatnie kilka zdań FAQ 2.1:

FAQ 2.1 Jak mogę uniknąć pisania naprawdę dużo ekspresji j? Powiedziałeś, że powinienem używać nazw kolumn, ale mam dużo kolumn.
Podczas grupowania wyrażenie j może używać nazwy kolumn jako zmienne, jak wiesz, ale można go również używać zarezerwowanej symbol .SD który odnosi się do podzbioru Data.table dla każdej grupy (z wyjątkiem kolumny zgrupowania ). Podsumowując wszystkie kolumny, jest to tylko DT[,lapply(.SD,sum),by=grp]. To może wydawać się trudne, ale szybkie jest pisanie i szybkie uruchamianie. Zauważ, że nie musisz tworzyć anonimowej funkcji . Zobacz winietę czasową i wiki dla porównania z innymi metodami . Obiekt .SD jest wydajnie implementowany wewnętrznie i jest bardziej wydajny niż przekazywanie argumentów do funkcji. Nie rób tego jednak : DT[,sum(.SD[,"sales",with=FALSE]),by=grp]. To działa, ale jest bardzo nieefektywne i nieeleganckie. To było zamierzone: DT[,sum(sales),by=grp] i mogło być 100 razy szybsze.

zobaczyć również pierwsza kula FAQ 3.1:

nas 3.1 Mam 20 kolumny i dużą liczbę wierszy. Dlaczego wyrażenie jednej kolumny jest tak szybkie?
kilku powodów:
- Tylko że kolumna jest pogrupowane, drugi 19 są ignorowane, ponieważ data.table kontroluje ekspresję j i uświadamia sobie, że nie korzystać z innych kolumn.

Kiedy data.table kontroluje j i widzi .SD symbol, że przyrost wydajności wychodzi przez okno. Będzie trzeba wypełnić cały podzestaw .SD dla każdej grupy, nawet jeśli nie użyjesz wszystkich jej kolumn. Bardzo trudno jest data.table wiedzieć, które kolumny z .SD naprawdę używasz (j może zawierać if s, na przykład). Jeśli jednak i tak ich potrzebujesz, to oczywiście nie ma to znaczenia, np. W DT[,lapply(.SD,sum),by=...]. To idealne wykorzystanie .SD.

Tak, tak, unikaj .SD, jeśli to możliwe. Używaj nazw kolumn bezpośrednio, aby zapewnić optymalizację danych w pliku data.table j. Istotne jest samo istnienie symbolu .SD w j.

To dlatego wprowadzono .SDcols. Więc możesz powiedzieć data.table, które kolumny powinny być w .SD, jeśli chcesz tylko podzbiór. W przeciwnym razie data.table zapełni .SD ze wszystkimi kolumnami na wypadek, gdyby potrzebował ich .

+0

Wielkie dzięki! Zostałem oszukany przez wyrażenie "Obiekt .SD jest sprawnie implementowany wewnętrznie i wydajniejszy niż przekazywanie argumentu funkcji" i nie zrozumiał "Proszę nie rób tego jednak: DT [, suma (.SD [," sales ", with = FALSE]), by = grp]" z powodu at = FALSE. Przyspieszy to znacznie mój kod! – vsalmendra

+0

@vsalmendra Ah, tak, może to być czystsze. Jest to coś, co pozostało do dyskusji w społeczności w przeszłości. W ostatecznym rozrachunku chcemy poprawić "j" optymalizację, aby użytkownicy nie musieli znać takich rzeczy. –

+0

@vsalmendra Ulepszyłem FAQ 2.1 dla następnej wersji. –

3

Spróbuj rozwiązać ten łamiąc obliczenia w dwóch etapach, a następnie łączenie wynikające ramek danych:

system.time({ 
    x2 <- x[code==2, list(code2=.N), by=id] 
    xt <- x[, list(total=.N), by=id] 
    print(x2[xt]) 
}) 

Na moim komputerze działa w 0,04 sekundy w porównaniu do 7,42 sekundy, czyli ~ 200 razy szybciej niż Oryginalny kod:

  id code2 total 
    1:  1  6 14 
    2:  2  8 10 
    3:  3  7 13 
    4:  4  5 13 
    5:  5  9 18 
    ---     
9995: 9996  4  9 
9996: 9997  3  6 
9997: 9998  6 10 
9998: 9999  3  4 
9999: 10000  3  6 
    user system elapsed 
    0.05 0.00 0.04 
+0

(+1) Zamieniłabym "x2" na "x2 <- x [J (unikalny (id)," 2 "), list (code2 = .N)] [, code: = NULL, keyby = "id"] '. Twój kod usuwa wiersze, w których kod! = 2 dla danego identyfikatora. – Arun

+0

+1 Tak naprawdę jest dużo szybciej niż 'x [, lista (suma (kod == 2), N), by = id]' (przykład 2 w pytaniu), czyż nie? Może dlatego, że unikasz powtarzającego się połączenia z '==' dla każdej grupy (powiązana alokacja itp. Dla tych małych wektorów). –

+0

@Arun Czy na pewno? 'x2 [xt] [is.na (code2)]' ma kilka wierszy. Po prostu dostajesz 'NA' zamiast' 0'. Może się mylić, tylko wyglądało to szybko. –