Esta es una pregunta sencilla, ¿es correcto utilizar un modelo Random Forest en datos de series temporales? Lo pregunto porque, en un modelo de bosque aleatorio, realizamos bootstrapping de observaciones en las que tomamos muestras aleatorias del conjunto de entrenamiento con reemplazo. ¿No estropea esto el "orden de las observaciones" en el modelo, al tratarse de datos de series temporales? Estoy preguntando esto en el contexto de los datos financieros, digamos que estoy haciendo un problema de tipo de clasificación para comprar / no comprar un activo, y recojo datos diarios para algunas características para predecir esta variable.
Respuestas
¿Demasiados anuncios?Funciona bien, pero sólo si las características están bien preparadas para que el orden de las líneas deje de ser importante.
Por ejemplo, para una serie temporal univariante $y_i$ utilizaría $y_i$ como respuesta y, por ejemplo, las siguientes características:
-
Versiones retrasadas $y_{i-1}$ , $y_{i-2}$ , $y_{i-3}$ etc.
-
Diferencias de orden apropiado, por ejemplo $y_{i-1} - y_{i-2}$ , $y_{i-1} - y_{i-8}$ (si se prevé una estacionalidad semanal y las observaciones son diarias), etc.
-
Información horaria periódica codificada con números enteros o ficticios, como el mes del año, el día de la semana, la hora del día, el minuto de la hora, etc.
El mismo planteamiento funciona con distintas técnicas de modelización, como la regresión lineal, las redes neuronales, los árboles potenciados, etc.
Un ejemplo es el siguiente (utilizando un objetivo binario "aumento de temperatura" (sí/no)):
library(tidyverse)
library(lubridate)
library(ranger)
library(MetricsWeighted) # AUC
# Import
raw <- read.csv("https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-min-temperatures.csv")
# Explore
str(raw)
head(raw)
summary(raw)
hist(raw$Temp, breaks = "FD")
# Prepare and add binary response
prep <- raw %>%
mutate(Date = ymd(Date),
y = year(Date),
m = month(Date),
d = day(Date),
increase = 0 + (Temp > lag(Temp)))
with(prep, table(y))
summary(prep)
# Plot full data -> year as seasonality
ggplot(data = prep, aes(x = Date, y = Temp))+
geom_line(color = "#00AFBB", size = 2) +
scale_x_date()
# No visible within year seasonality
prep %>%
filter(y == 1987) %>%
ggplot(aes(x = Date, y = Temp))+
geom_line(color = "#00AFBB", size = 2) +
scale_x_date()
# Add some lags and diffs & remove incomplete rows
prep <- prep %>%
mutate(lag1 = lag(Temp),
lag2 = lag(Temp, 2L),
lag3 = lag(Temp, 3L),
dif1 = lag1 - lag2,
dif2 = lag2 - lag3) %>%
filter(complete.cases(.))
# Train/valid split in blocks
valid <- prep %>%
filter(y == 1990)
train <- prep %>%
filter(y < 1990)
# Models
y <- "increase" # response
x <- c("lag1", "lag2", "lag3", "dif1", "dif2", "y", "m", "d") # covariables
form <- reformulate(x, y)
# Logistic model: Linear dependence between difs and lags
fit_glm <- glm(form,
data = train,
family = binomial())
summary(fit_glm)
# Random forest
fit_rf <- ranger(form,
data = train,
seed = 345345,
importance = "impurity",
probability = TRUE)
fit_rf
barplot(-sort(-importance(fit_rf))) # Variable importance
# Evaluate on 1990 for glm by looking at ROC AUC
pred_glm <- predict(fit_glm, valid, type = "response")
AUC(valid[[y]], pred_glm) # 0.684 ROC AUC
# Then for rf
pred_rf <- predict(fit_rf, valid)$predictions[, 2]
AUC(valid[[y]], pred_rf) # 0.702 ROC AUC
# view OOB residuals of rf within one month to see if structure is left over
random_month <- train %>%
mutate(residuals = increase - fit_rf$predictions[, 2]) %>%
filter(y == 1987, m == 3)
ggplot(random_month, aes(x = Date, y = residuals))+
geom_line(color = "#00AFBB", size = 2) +
scale_x_date()
Sustituir las variables "y" y "m" por factores probablemente mejoraría la regresión logística. Pero como la pregunta era sobre bosques aleatorios, dejo esto al lector.
No es de esperar que un bosque aleatorio funcione bien con datos de series temporales por varias razones. Sin embargo, en mi opinión, los mayores escollos no están relacionados con el bootstrapping y no son exclusivos de los bosques aleatorios:
- Las series temporales presentan una interdependencia entre las observaciones, que el modelo ignorará.
- El aprendiz subyacente suele ser un algoritmo basado en árboles, que no extrapola tendencias. Si existen tendencias temporales reales en los datos, éstas no se proyectarán hacia adelante.