Perché AS.Date è lento su un vettore di caratteri?

Ho iniziato a utilizzare il pacchetto data.table in R per migliorare le prestazioni del mio codice. Sto usando il seguente codice:

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

Ho notato che la conversione eseguita dalla funzione as.Date è molto lenta, se confrontata con altre funzioni che creano i giorni della settimana, ecc. Perché è così? C’è una soluzione migliore / più veloce, come convertire in formato data? (Se mi chiedessi se ho veramente bisogno del formato della data, probabilmente sì, perché poi uso ggplot2 per creare grafici, che funzionano come un incantesimo con questo tipo di dati.)

Per essere più precisi

 > system.time(sp500  system.time(sp500  system.time(sp500 <- sp500[,Year:=(as.POSIXlt(Date)$year+1900)]) user system elapsed 0.304 0.001 0.305 

Su MacAir i5 con un po ‘meno di 3000000 osservazioni.

Grazie

Penso che sia solo che as.Date converte il character in Date tramite POSIXlt , usando strptime . E il strptime è molto lento, credo.

Per rintracciarlo attraverso te stesso, digita as.Date , then methods(as.Date) , quindi osserva il metodo character .

 > as.Date function (x, ...) UseMethod("as.Date")   > 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) }   > 

Perché as.POSIXlt(Date)$year+1900 relativamente veloce? Di nuovo, tracciarlo attraverso:

 > as.POSIXct function (x, tz = "", ...) UseMethod("as.POSIXct")   > 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 }   > 

Incuriosito, scaviamo in Date2POSIXlt. Per questo bit abbiamo bisogno di grep main / src per sapere quale file .c guardare.

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

Ora sappiamo che dobbiamo cercare 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, avremmo potuto indovinare datetime.c. Ad ogni modo, guardando l’ultima copia live:

datetime.c

Cerca lì per D2POSIXlt e vedrai quanto è semplice passare da Data (numerico) a POSIXlt. Vedrai anche come POSIXlt è un vettore reale (8 byte) più sette vettori interi (4 byte ciascuno). Quello è 40 byte, per data!

Quindi il punto cruciale del problema (credo) è il motivo per cui il tempo di strptime è così lento, e forse ciò può essere migliorato in R. O semplicemente evitare POSIXlt , direttamente o indirettamente.


Ecco un esempio riproducibile utilizzando il numero di elementi dichiarati in questione (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 

Passando a tz sembra accelerare il strptime , come as.Date.character . Quindi forse dipende dal tuo locale. Ma il strptime sembra essere il colpevole, non il data.table . Forse rieseguire questo esempio e vedere se ci vogliono 90 secondi per la tua macchina?

Come altri hanno menzionato, qui il collo di bottiglia è il tempo strptime (conversione da carattere a POSIXlt). Un’altra soluzione semplice utilizza invece il pacchetto lubridate e il suo metodo fast_strptime .

Ecco come appare sui miei dati:

 > 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 

Per confronto:

 > system.time(pp[, date := as.Date(date, "%Y-%m-%d")]) user system elapsed 108.193 0.399 108.844 

È ~ 316 volte più veloce!

Grazie per i suggerimenti. L’ho risolto scrivendo l’algoritmo gaussiano per le date e ottenendo risultati di gran lunga migliori, vedi sotto.

 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 <- Yc*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 

Eseguire l'intero blocco sopra dà (compresa la lettura della data da csv) ... Data.table è davvero impressionante.

 user system elapsed 19.074 0.803 20.284 

La durata della conversione è di soli 3,49.

Questa è una vecchia domanda, ma penso che questo piccolo trucco potrebbe essere utile. Se hai più righe con la stessa data, puoi farlo

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

È molto più veloce poiché elabora solo ogni data una volta (nel mio set di dati di 40 milioni di righe va da 25 secondi a 0,5 secondi).

Inizialmente pensavo: “L’argomento di cui sopra non ha il formato specificato.”

Ora penso: ho pensato che il valore di Data su cui si stava digitando fosse in un formato standard. Non credo. Quindi stai facendo due processi. Si sta riformattando dal formato carattere al formato Data e si esegue il riordinamento in base ai nuovi valori che presentano una sequenza di confronto completamente diversa.