change - ¿Por qué es as.Date lento en un vector de caracteres?
change format date r (5)
Como otros mencionaron, strptime
(conversión de carácter a POSIXlt) es el cuello de botella aquí. Otra solución simple utiliza el paquete lubridate
y su método fast_strptime
su lugar.
Esto es lo que parece en mis datos:
> 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
Para comparacion:
> system.time(pp[, date := as.Date(date, "%Y-%m-%d")])
user system elapsed
108.193 0.399 108.844
¡Eso es ~ 316 veces más rápido!
Empecé a usar el paquete data.table en R para mejorar el rendimiento de mi código. Estoy usando el siguiente código:
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)]
Noté que la conversión realizada por la función as.Date es muy lenta, en comparación con otras funciones que crean días de la semana, etc. ¿Por qué? ¿Hay una solución mejor / más rápida, cómo convertir a formato de fecha? (Si me preguntan si realmente necesito el formato de fecha, probablemente sí, porque luego uso ggplot2 para hacer gráficos, que funcionan como un encanto con este tipo de datos).
Ser más preciso
> 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
En MacAir i5 con un poco menos de 3000000 observaciones.
Gracias
Creo que es solo eso. as.Date
convierte el character
en Date
mediante POSIXlt
, usando strptime
. Y strptime
es muy lento, creo.
Para rastrearlo a través de usted, escriba as.Date
, luego los methods(as.Date)
, luego observe el método del 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>
>
¿Por qué as.POSIXlt(Date)$year+1900
relativamente rápido? Nuevamente, trace a través de:
> 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>
>
Intrigado, profundicemos en Date2POSIXlt. Para este bit, necesitamos grep main / src para saber qué archivo .c debemos ver.
~/R/Rtrunk/src/main$ grep Date2POSIXlt *
names.c:{"Date2POSIXlt",do_D2POSIXlt, 0, 11, 1, {PP_FUNCALL, PREC_FN, 0}},
$
Ahora sabemos que debemos buscar 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, podríamos haber adivinado datetime.c. De todos modos, para ver la última copia en vivo:
Busque allí D2POSIXlt
y verá lo simple que es pasar de Fecha (numérico) a POSIXlt. También verá cómo POSIXlt es un vector real (8 bytes) más siete vectores enteros (4 bytes cada uno). ¡Eso es 40 bytes, por fecha!
Entonces, el quid de la cuestión (creo) es por qué el tiempo de strptime
es tan lento, y tal vez eso se puede mejorar en R. O simplemente evitar POSIXlt
, ya sea directa o indirectamente.
Aquí hay un ejemplo reproducible que usa la cantidad de elementos indicados en la pregunta (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
Pasar tz
parece acelerar el strptime
, lo cual ocurre con as.Date.character
. Entonces tal vez depende de tu localidad. Pero strptime
parece ser el culpable, no data.table
. Quizás vuelva a ejecutar este ejemplo y vea si le lleva 90 segundos en su equipo.
Esta es una vieja pregunta, pero creo que este pequeño truco podría ser útil. Si tiene varias filas con la misma fecha, puede hacer
data[, date := as.Date(date[1]), by = date]
Es mucho más rápido ya que solo procesa cada fecha una vez (en mi conjunto de datos de 40 millones de filas va de 25 segundos a 0.5 segundos).
Gracias por las sugerencias. Lo resolví escribiendo el algoritmo gaussiano para las fechas y obtuve mejores resultados, ver abajo.
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
Ejecutar todo el bloque anterior (incluida la lectura de la fecha de csv) ... Data.table es realmente impresionante.
user system elapsed
19.074 0.803 20.284
El tiempo de la conversión en sí es 3.49 transcurrido.
Originalmente pensé: "El argumento a como fecha anterior no tiene el formato especificado".
Ahora pienso: asumí que el valor de Fecha que codificabas estaba en un formato estándar. Supongo que no. Entonces estás haciendo dos procesos. Está formateando de nuevo el formato de fecha y está reorganizando en función de los nuevos valores que tienen una secuencia de clasificación completamente diferente.