Dlaczego jest as.Data wolna na wektorze znaków?

Zacząłem używać danych.pakiet tabeli w R, aby zwiększyć wydajność mojego kodu. Używam następującego kodu:

sp500 <- read.csv('../rawdata/GMTSP.csv')
days <- c("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")

# Using data.table to get the things much much faster
sp500 <- data.table(sp500, key="Date")
sp500 <- sp500[,Date:=as.Date(Date, "%m/%d/%Y")]
sp500 <- sp500[,Weekday:=factor(weekdays(sp500[,Date]), levels=days, ordered=T)]
sp500 <- sp500[,Year:=(as.POSIXlt(Date)$year+1900)]
sp500 <- sp500[,Month:=(as.POSIXlt(Date)$mon+1)]

Zauważyłem, że konwersja dokonana przez as.Funkcja Date jest bardzo powolna w porównaniu z innymi funkcjami tworzącymi dni powszednie itp. Dlaczego? Czy istnieje lepsze / szybsze rozwiązanie, jak przekonwertować na format daty? (Gdybyś zapytał, czy naprawdę potrzebuję formatu daty, prawdopodobnie tak, bo potem użyj ggplot2 do robienia działek, które działają jak urok z tego typu data.)

By być bardziej precyzyjnym

> system.time(sp500 <- sp500[,Date:=as.Date(Date, "%m/%d/%Y")])
   user  system elapsed 
 92.603   0.289  93.014 
> system.time(sp500 <- sp500[,Weekday:=factor(weekdays(sp500[,Date]), levels=days, ordered=T)])
   user  system elapsed 
  1.938   0.062   2.001 
> system.time(sp500 <- sp500[,Year:=(as.POSIXlt(Date)$year+1900)])
   user  system elapsed 
  0.304   0.001   0.305 

Na MacAir i5 z nieco mniej niż 3000000 obserwacji.

Dzięki

Author: Matt Dowle, 2012-10-08

5 answers

Myślę, że po prostu as.Date konwertuje character na Date Poprzez POSIXlt, używając strptime. I wydaje mi się, że jest bardzo powolny.

Aby prześledzić to przez siebie, wpisz as.Date, Następnie methods(as.Date), a następnie spójrz na metodę character.

> as.Date
function (x, ...) 
UseMethod("as.Date")
<bytecode: 0x2cf4b20>
<environment: namespace:base>

> methods(as.Date)
[1] as.Date.character as.Date.date      as.Date.dates     as.Date.default  
[5] as.Date.factor    as.Date.IDate*    as.Date.numeric   as.Date.POSIXct  
[9] as.Date.POSIXlt  
   Non-visible functions are asterisked

> as.Date.character
function (x, format = "", ...) 
{
    charToDate <- function(x) {
        xx <- x[1L]
        if (is.na(xx)) {
            j <- 1L
            while (is.na(xx) && (j <- j + 1L) <= length(x)) xx <- x[j]
            if (is.na(xx)) 
                f <- "%Y-%m-%d"
        }
        if (is.na(xx) || !is.na(strptime(xx, f <- "%Y-%m-%d", 
            tz = "GMT")) || !is.na(strptime(xx, f <- "%Y/%m/%d", 
            tz = "GMT"))) 
            return(strptime(x, f))
        stop("character string is not in a standard unambiguous format")
    }
    res <- if (missing(format)) 
        charToDate(x)
    else strptime(x, format, tz = "GMT")       ####  slow part, I think  ####
    as.Date(res)
}
<bytecode: 0x2cf6da0>
<environment: namespace:base>
> 

Dlaczego as.POSIXlt(Date)$year+1900 jest stosunkowo szybki? Jeszcze raz prześledzić:

> as.POSIXct
function (x, tz = "", ...) 
UseMethod("as.POSIXct")
<bytecode: 0x2936de8>
<environment: namespace:base>

> methods(as.POSIXct)
[1] as.POSIXct.date    as.POSIXct.Date    as.POSIXct.dates   as.POSIXct.default
[5] as.POSIXct.IDate*  as.POSIXct.ITime*  as.POSIXct.numeric as.POSIXct.POSIXlt
   Non-visible functions are asterisked

> as.POSIXlt.Date
function (x, ...) 
{
    y <- .Internal(Date2POSIXlt(x))
    names(y$year) <- names(x)
    y
}
<bytecode: 0x395e328>
<environment: namespace:base>
> 

Zaintrygowani, zagłębimy się w Date2POSIXlt. Dla tego bitu musimy grep main / src, aby wiedzieć, który .plik c do obejrzenia.

~/R/Rtrunk/src/main$ grep Date2POSIXlt *
names.c:{"Date2POSIXlt",do_D2POSIXlt,   0,  11, 1,  {PP_FUNCALL, PREC_FN,   0}},
$

Teraz wiemy, że musimy szukać D2POSIXlt :

~/R/Rtrunk/src/main$ grep D2POSIXlt *
datetime.c:SEXP attribute_hidden do_D2POSIXlt(SEXP call, SEXP op, SEXP args, SEXP env)
names.c:{"Date2POSIXlt",do_D2POSIXlt,   0,  11, 1,  {PP_FUNCALL, PREC_FN,   0}},
$
Oh, mogliśmy zgadnąć datetime.c. w każdym razie, więc patrząc na najnowszą kopię NA ŻYWO:

Datetime.c

Poszukaj tam D2POSIXlt, a zobaczysz, jak proste jest przejście od daty (numerycznej) do POSIXlt. Zobaczysz także, jak POSIXlt to jeden wektor rzeczywisty (8 bajtów) plus siedem wektorów całkowitych (po 4 bajty). To 40 bajtów na datę!

Więc sednem sprawy (myślę) jest to, dlaczego strptime jest tak powolny, i może to można poprawić w R. lub po prostu uniknąć POSIXlt, bezpośrednio lub pośrednio.


Oto powtarzalny przykład wykorzystujący liczbę przedmiotów podanych w pytaniu (3,000,000):
> Range = seq(as.Date("2000-01-01"),as.Date("2012-01-01"),by="days")
> Date = format(sample(Range,3000000,replace=TRUE),"%m/%d/%Y")
> system.time(as.Date(Date, "%m/%d/%Y"))
   user  system elapsed 
 21.681   0.060  21.760 
> system.time(strptime(Date, "%m/%d/%Y"))
   user  system elapsed 
 29.594   8.633  38.270 
> system.time(strptime(Date, "%m/%d/%Y", tz="GMT"))
   user  system elapsed 
 19.785   0.000  19.802 

Mijanie tz wydaje się przyspieszać strptime, co as.Date.character robi. Więc może to zależy od Twojej lokalizacji. Ale strptime wydaje się być winowajcą, a nie data.table. Może powtórz ten przykład i sprawdź, czy zajmuje ci to 90 sekund na komputerze?

 18
Author: Matt Dowle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-08 21:06:51

Jak wspomnieli inni, strptime (Konwersja ze znaku na POSIXlt) jest tutaj wąskim gardłem. Inne proste rozwiązanie wykorzystuje pakiet lubridate i jego metodę fast_strptime.

Oto Jak to wygląda na moich danych:

> tables()
     NAME      NROW  MB COLS                                     
[1,] pp   3,718,339 126 session_id,date,user_id,path,num_sessions
     KEY         
[1,] user_id,date
Total: 126MB

> pp[, 2, with = F]
               date
      1: 2013-09-25
      2: 2013-09-25
      3: 2013-09-25
      4: 2013-09-25
      5: 2013-09-25
     ---           
3718335: 2013-09-25
3718336: 2013-09-25
3718337: 2013-09-25
3718338: 2013-10-11
3718339: 2013-10-11

> system.time(pp[, date := as.Date(fast_strptime(date, "%Y-%m-%d"))])
   user  system elapsed 
  0.315   0.026   0.344  

Dla porównania:

> system.time(pp[, date := as.Date(date, "%Y-%m-%d")])
   user  system elapsed 
108.193   0.399 108.844 
316 razy szybciej!
 24
Author: daniel.s,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-12 23:02:14

Dzięki za sugestie. Rozwiązałem go, pisząc algorytm Gaussa dla dat sam i uzyskałem znacznie lepsze wyniki, patrz poniżej.

getWeekDay <- function(year, month, day) {
  # Implementation of the Gaussian algorithm to get weekday 0 - Sunday, ... , 7 - Saturday
  Y <- year
  Y[month<3] <- (Y[month<3] - 1)

  d <- day
  m <- ((month + 9)%%12) + 1
  c <- floor(Y/100)
  y <- Y-c*100
  dayofweek <- (d + floor(2.6*m - 0.2) + y + floor(y/4) + floor(c/4) - 2*c) %% 7
  return(dayofweek)
}

sp500 <- read.csv('../rawdata/GMTSP.csv')
days <- c("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday")

# Using data.table to get the things much much faster
sp500 <- data.table(sp500, key="Date")
sp500 <- sp500[,Month:=as.integer(substr(Date,1,2))]
sp500 <- sp500[,Day:=as.integer(substr(Date,4,5))]
sp500 <- sp500[,Year:=as.integer(substr(Date,7,10))]
#sp500 <- sp500[,Date:=as.Date(Date, "%m/%d/%Y")]
#sp500 <- sp500[,Weekday:=factor(weekdays(sp500[,Date]), levels=days, ordered=T)]
sp500 <- sp500[,Weekday:=factor(getWeekDay(Year, Month, Day))]
levels(sp500$Weekday) <- days

Uruchomienie całego bloku powyżej daje (włącznie z odczytaniem daty z pliku csv)... Data.stół robi wrażenie.

user  system elapsed 
 19.074   0.803  20.284 

Czas samej konwersji wynosi 3,49.

 8
Author: krhlk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-09 12:07:18

To stare pytanie, ale myślę, że ta mała sztuczka może się przydać. Jeśli masz kilka wierszy z tą samą datą, możesz wykonać

data[, date := as.Date(date[1]), by = date]

Jest o wiele szybszy, ponieważ przetwarza każdą datę tylko raz(w moim zbiorze danych 40 milionów wierszy trwa od 25 sekund do 0,5 sekundy).

 5
Author: Elio Campitelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-06 21:16:38

Początkowo myślałem: "argument do as.Data powyżej nie ma określonego formatu."

Teraz myślę: założyłem, że wartość daty, którą wpisywałeś, była w standardowym formacie. Chyba nie. Więc robisz dwa procesy. Formatujesz z formatu znaków na format daty i ponownie sortujesz w oparciu o nowe wartości, które mają zupełnie inną sekwencję zestawiania.

 2
Author: 42-,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-08 18:49:14