2015-07-13 11 views
12

Natrafiłem dzisiaj na następujący problem i zastanawiam się, czy istnieje lepszy sposób na zrealizowanie tego, co próbuję zrobić.Używanie data.table z funkcjami zastępczymi w r

Załóżmy Mam następujący data.table (tylko datownik godzinowy):

library(data.table) 
tdt <- data.table(Timestamp = seq(as.POSIXct("1980-01-01 00:00:00"), as.POSIXct("2015-01-01 00:00:00"), '1 hour')) 

> tdt 
        Timestamp 
    1: 1980-01-01 00:00:00 
    2: 1980-01-01 01:00:00 
    3: 1980-01-01 02:00:00 
    4: 1980-01-01 03:00:00 
    5: 1980-01-01 04:00:00 
    ---      
306813: 2014-12-31 20:00:00 
306814: 2014-12-31 21:00:00 
306815: 2014-12-31 22:00:00 
306816: 2014-12-31 23:00:00 
306817: 2015-01-01 00:00:00 

Moim celem jest, aby zmienić protokół z datownika do, powiedzmy, 10 minut.

wiem, że mogę używać:

library(lubridate) 
minute(tdt$Timestamp) <- 10 

ale nie wykorzystują super szybkość z tabeli danych (które potrzebujesz). Na moim laptopie to trwało:

> system.time(minute(tdt$Timestamp) <- 10) 
    user system elapsed 
    11.29 0.16 11.45 

Więc moje pytanie brzmi: Czy możemy w jakiś sposób wykorzystać funkcję zastępczą w składni tabeli danych tak, że będzie robić to, co chcę używając data.table „s prędkość? Jeśli odpowiedź brzmi "nie", to wszelkie inne rozwiązanie, aby zrobić to szybko, byłoby do przyjęcia.

Jeśli zastanawiasz się jedną z rzeczy, próbowałem to:

tdt[, Timestamp2 := minute(Timestamp) <- 10] 

który nie działa.

oczekiwany wynik (ale z tabeli danych składni):

> tdt 
        Timestamp 
    1: 1980-01-01 00:10:00 
    2: 1980-01-01 01:10:00 
    3: 1980-01-01 02:10:00 
    4: 1980-01-01 03:10:00 
    5: 1980-01-01 04:10:00 
    ---      
306813: 2014-12-31 20:10:00 
306814: 2014-12-31 21:10:00 
306815: 2014-12-31 22:10:00 
306816: 2014-12-31 23:10:00 
306817: 2015-01-01 00:10:00 
+1

* I * myślę to może skierować Cię w dobrym kierunku: http://stackoverflow.com/questions/10449366/levels-what-sorcery -czy to jest . Ale przynajmniej od ubiegłego roku 'data.table' nie obsługuje dobrze formatów daty/czasu. Być może poprawił się już teraz. –

+6

Co powiecie na 'tdt [, Timestamp: = Timestamp + 600L]'? –

+0

@ AriB.Friedman Dzięki za ten link. Widziałem to pytanie i zdaję sobie sprawę z tego, jak działa funkcja zastępowania, ale nadal nie byłem w stanie tego w jakiś sposób wykorzystać ... – LyzandeR

Odpowiedz

7

funkcji wymiany znajdują się w dwóch etapach:

  1. Funkcja który tworzy pożądaną wydajność,
  2. To wyjście jest następnie przypisywane do wyniku.

Można uruchomić step 1 without running step 2. Wynik ten można następnie wykorzystać do ustawienia kolumny data.table (set tutaj użytej, ale można również użyć :=).

library(lubridate) 
library(data.table) 
tdt <- data.table(Timestamp = seq(as.POSIXct("1980-01-01 00:00:00"), as.POSIXct("2015-01-01 00:00:00"), '1 hour')) 
minute(tdt$Timestamp) <- 20 
print(`minute<-`(tdt$Timestamp,11)) 
set(tdt, j=1L,value=`minute<-`(tdt$Timestamp,11) ) 

Edytuj: Małe dane.tabela vs duże dane.tabeli porównawczej

library(lubridate) 
library(data.table) 
library(microbenchmark) 

# Config 
tms <- 5L 

# Sample data, 1 column 
tdt <- data.table(Timestamp = seq(as.POSIXct("1980-01-01 00:00:00"), as.POSIXct("2015-01-01 00:00:00"), '1 hour')) 
minute(tdt$Timestamp) <- 20 

tdf <- as.data.frame(tdt) 


# Sample data, lots of columns 
bdf <- cbind(tdf, as.data.frame(replicate(100, runif(nrow(tdt))))) 
bdt <- as.data.table(bdf) 

# Benchmark 
microbenchmark(
    `minute<-`(tdt$Timestamp,10), # How long does the operation to generate the new vector itself take? 
    set(tdt, j=1L,value=`minute<-`(tdt$Timestamp,11) ), # One column: How long does it take to generate the new vector and replace the contents in the data.table? 
    minute(tdf$Timestamp) <- 12, # One column: How long does it take to do it with a data.frame? 
    set(tdt, j=1L,value=`minute<-`(bdt$Timestamp,13) ), # Many columns: How long does it take to generate the new vector and replace the contents in the data.table? 
    minute(bdf$Timestamp) <- 14, # Many columns: How long does it take to do it with a data.frame? 
    times = tms 
) 

Unit: seconds 
                expr  min  lq  mean median  uq  max neval 
          `minute<-`(tdt$Timestamp, 10) 1.304388 1.385883 1.417616 1.389316 1.459166 1.549327  5 
set(tdt, j = 1L, value = `minute<-`(tdt$Timestamp, 11)) 1.314495 1.344277 1.376241 1.352124 1.389083 1.481225  5 
          minute(tdf$Timestamp) <- 12 1.342104 1.349231 1.488639 1.378840 1.380659 1.992358  5 
set(tdt, j = 1L, value = `minute<-`(bdt$Timestamp, 13)) 1.337944 1.383429 1.402802 1.418211 1.418922 1.455503  5 
          minute(bdf$Timestamp) <- 14 1.332482 1.333713 1.355331 1.335728 1.342607 1.432127  5 

Wygląda na to, że nie jest szybsza, co przeczy moim zrozumienie tego, co się dzieje. Dziwne.

+0

To miło, zastanawiam się, jak to będzie skalować. Z mojego testu porównawczego był dokładnie taki sam czas. –

+0

To jest fantastyczne! Odpowiedź Dawida (awansowałem Dave'a: P) jest o wiele szybsza, aby być szczerym z powodu określonego przykładu, który ustaliłem, ale w całkowitej uczciwości jest to ogólna odpowiedź, której szukałem. Dziękuję bardzo Ari. – LyzandeR

+0

Ponieważ twój przykład składa się z pojedynczej kolumny, spodziewałbym się, że zastąpienie kolumny względem zastąpienia całego pliku data.frame będzie podobne. Spróbuj porównać go z tabelą data.table za pomocą kilkuset kolumn? –

10

POSIXct obiekt jest po prostu podwójne z niektórych atrybutów

storage.mode(as.POSIXct("1980-01-01 00:00:00")) 
## [1] "double" 

Więc w celu manipulowania to skutecznie można po prostu potraktuj go jako jeden, na przykład

tdt[, Timestamp := Timestamp + 600L] 

Doda 600 sekund (10 minut), z każdym rzędem przez odniesienie


Niektóre odniesienia

tdt <- data.table(Timestamp = seq(as.POSIXct("1600-01-01 00:00:00"), 
            as.POSIXct("2015-01-01 00:00:00"), 
            '1 hour')) 
system.time(minute(tdt$Timestamp) <- 10) 
# user system elapsed 
# 124.86 1.95 127.68 
system.time(set(tdt, j = 1L, value = `minute<-`(tdt$Timestamp, 10))) 
# user system elapsed 
# 124.99 1.83 128.25 
system.time(tdt[, Timestamp := Timestamp + dminutes(10)]) 
# user system elapsed 
# 0.39 0.04 0.42 
system.time(tdt[, Timestamp := Timestamp + 600L]) 
# user system elapsed 
# 0.01 0.00 0.01 
+1

Wielkie dzięki za tę odpowiedź Dave. Właśnie zmodyfikowałem rdzeń mojej funkcji (zgodnie z tą odpowiedzią) i zastosowałem go do tego przykładowego znacznika czasu i działa on w mniej niż sekundę. Jest niezwykle szybki i dokładny. Dziękuję bardzo, że zaoszczędziłeś mi dużo czasu na oczekiwanie na rzeczy do uruchomienia. Chciałbym móc przyjąć obie odpowiedzi. Jeden pokazał mi sposób użycia funkcji zastępczej (która choć nie wydaje się działać dobrze, odpowiada bezpośrednio na moje pytanie), druga pomogła mi rozwiązać mój rzeczywisty problem. Jestem zdezorientowany: S. Przepełnienie stosu musi zrobić coś takiego na takie okazje! – LyzandeR

+1

W porządku, Ari dostarczył miłą odpowiedź i zasługuje na kleszcza. –

+1

Wielkie dzięki, Dave, sprawiłeś, że poczułam się lepiej :). Dzięki za pomoc i rzeczywiste rozwiązanie. – LyzandeR

3

Myślę, że to powinno wystarczyć dla Ciebie:

library(data.table) 
library(lubridate) 

tdt <- data.table(
    Timestamp = seq(as.POSIXct("1980-01-01 00:00:00") 
    , as.POSIXct("2015-01-01 00:00:00") 
    , '1 hour')) 
tdt[, Timestamp := Timestamp + dminutes(10)] 
+0

Dzięki za odpowiedź. Jest to praktycznie odpowiednik odpowiedzi Dave'a i będzie nieco wolniejsze, ponieważ korzysta z funkcji zewnętrznej. – LyzandeR

+0

Tak, rzeczywiście jest to odpowiednik. Ale nie widziałem tego, zanim napisałem :) Jeszcze bardziej lubię mój ze względu na jaśniejsze zrozumienie tego, co się dzieje. –