14 votos

R: Forma rápida y eficiente de ejecutar una regresión multivariante en un panel (realmente) grande (Primera pasada de Fama MacBeth)

Estoy intentando realizar una regresión multivariante (14 variables explicativas) en un panel de 5000 acciones:

  • Para cada una de las 5.000 acciones, realizo 284 regresiones (recorriendo el periodo de la muestra).
  • En resumen: se han realizado 1.420.000 regresiones en total para el panel.

Para ello, utilizo un "bucle for" anidado: bucle sobre los valores y sobre el tiempo. Los coeficientes se exportan a un archivo csv.

Como era de esperar, el problema es que todo el procedimiento tarda una cantidad ENORME de tiempo en completarse. ¿Habría una manera eficiente de manejar esto? (Como me doy cuenta de que la función "aplicar" es más eficiente que un "bucle for", por favor, tenga en cuenta que dado el enorme tiempo de procesamiento, la ganancia de tiempo del uso alternativo de la función "aplicar" seguiría siendo mínima).

A continuación se muestra una instantánea del código:

sec = ncol(ret.zoo)
num.factors = ncol(data)
rows = nrow(ret.zoo) - 60 + 1
col.names <- c("gvkey", "date", "intercept", colnames(data))
write.table(as.data.frame(t(col.names)), file = paste(path, "betas.csv", sep = ""),  row.names = FALSE, col.names = FALSE, sep = ",")

for(i in 1:sec) {   
    beta = data.frame(matrix(nc = num.factors + 3, nr = rows))
    df = merge(ret.zoo[,i], data)
    names(df) <- c("return", names(data))

    for(j in 1:rows) {
        #Checks if number of observations >=30. If so, regression is ran. Otherwise, it is not.
        no.na = ret.zoo[j:(j+59),i][which(!is.na(coredata(ret.zoo[j:(j+59),i])))]
        if(length(no.na) >= 30) {
            beta[j,1] = substr(colnames(ret.zoo)[i],2,7)
            beta[j,2] = as.character(index(df[(j+59),])) ### Date
            beta[j,3:(num.factors+3)] = coef(lm(return ~., data = as.data.frame(df[j:(j+59),]), na.action = na.omit))
        }
    }
    write.table(beta, file = paste(path, "betas.csv", sep = ""), append = T, sep = ",", row.names = FALSE, col.names = FALSE)
    rm(beta)    
}

Tenga en cuenta que:

  • sec: número de acciones (valores). Cada valor tiene una serie temporal de rendimientos.
  • filas: número de periodos de tiempo (a lo largo de los cuales realizamos la regresión)
  • beta: matriz de coeficientes de todas las regresiones para cada valor. Se borra cada vez para cada valor.

MODELO:

Este es el modelo de regresión para cada valor i en el momento t :

R(i,t) = a(i,t) + b1(i,t)f1(t) + b2(i,t)f2(t) + .... + bn(i,t)fn(t) + e(i,t)

donde b son los coeficientes de regresión, f los factores, y e los residuos.

Tenga en cuenta que i está en [1:5000], el número de factores n es 14, y el tiempo t está en [1:343] (343 meses).

  • Para cada valor i En este caso, ejecutamos esta regresión sobre períodos móviles de 60 meses (de ahí el j:j+59 en el código R).

  • Cada regresión rodante se ejecuta sólo si el no NA el número de observaciones de la ventana móvil para la variable dependiente es >= 30 (mientras que las variables independientes no pueden ser NA, las variables dependientes (en este caso los rendimientos de las acciones) pueden tomar valores NA, si la acción cae del índice).

  • Entonces obtenemos 284 = 343 - 60 + 1 coeficientes beta para cada factor f para cada valor i . Estos se almacenan en el marco de datos "beta" (el marco de datos "beta" tiene nr = 284, y ncol = 14+3 (14 factores, intercepción, fecha e identificador).

Así que, en resumen, realizamos 284 regresiones por valor, y tenemos un total de 5000 valores. Esto hace un total de 1.420.000 regresiones.

Para tener una perspectiva, la ejecución de este script tarda unos 50min en completarse con éxito.

Gracias,

3 votos

Bueno, para empezar, hay una alta probabilidad de que su ordenador portátil tiene más de un núcleo, empezar por hacer uso de todos los núcleos

1 votos

Dos ideas; (i) no correr lm(...) Utilizar $(X'X)^{-1}X'Y$ . (ii) cada cierto tiempo hacer un write.csv' o un save y rm() para limpiar la memoria, (iii) ejecutar el as.character en todo el vector de fechas en lugar de en una sola fecha en cada iteración del bucle..

0 votos

Hecho para el reparto de personajes. Gracias. Sin embargo, el uso de la multiplicación vectorial/matricial en lugar de lm() podría inducir más cálculos previos: ten en cuenta que el vector Y podría tener NA's, mientras que el vector X no puede tomar valores NA. Esto significa que antes de calcular las betas OLS utilizando la forma matricial, tenemos que emparejar el índice de los valores no NA de X, con los valores relevantes de Y, para que estén alineados en el tiempo. La función match() que ayudaría a lograr eso tomaría tiempo en sí misma..

8voto

Kyle Cronin Puntos 554

Parece que estás volviendo a ejecutar la regresión con cada nuevo punto de datos. En su lugar, debe utilizar una fórmula de actualización/en línea (véase una excelente respuesta del famoso Dr. Huber en estadísticas.se ).

Puede encontrar una implementación en el paquete R biglm . Si no tiene todas las características que necesitas (no hay ventana de datos antiguos), al menos puedes adaptarlo y utilizarlo para probar tu propio trabajo.

0 votos

+1: Parece ser un paquete interesante para aplicaciones de Big Data con R.

0 votos

El hecho de que biglm() esté añadiendo los conjuntos de datos en cada actualización realmente no ayuda.. Como tengo que borrar el conjunto de datos anterior de la memoria y utilizar un nuevo trozo de datos cada vez, el tiempo de ejecución no mejora mucho desde el uso de lm() simple.

0 votos

Estoy bastante seguro de que biglm no está añadiendo conjuntos de datos. Parece que tienes un error.

5voto

Loren Pechtel Puntos 2212

Esto no es exactamente lo que yo llamaría avanzado, pero ejecutar cada regresión en un núcleo separado en un bucle foreach paralelo ayudaría

http://cran.r-project.org/web/packages/foreach/foreach.pdf

0 votos

Además, para permitir la computación paralela utilizando foreach es necesario llamar y registrar el paquete "doMC". El problema es que la funcionalidad multinúcleo actualmente sólo funciona con sistemas operativos que soportan la llamada al sistema fork (lo que significa que Windows no es compatible). Como trabajo en una máquina Windows, esta opción es desgraciadamente imposible. Tenga en cuenta que si el doMC no está activado, el paquete foreach realizaría las operaciones secuencialmente y no en paralelo.

0 votos

Encontré una forma de evitarlo utilizando "doParallel". Es un "backend paralelo" para el foreach aplicable en una máquina Windows. Por cierto, ahora he reducido con éxito el secuencial cálculos a 30 minutos. Estoy seguro de que la paralización haría los cálculos aún más rápidos. Pero parece que todo el código tiene que ser modificado para acomodar la configuración paralela para el foreach %dopar% bucle.

0 votos

@Mariam bien, los próximos pasos son averiguar cómo perfilar en R y averiguar qué partes son las que más tardan y centrarse en eso

1voto

Ryan Versaw Puntos 3682

Es realmente importante vectorizar las operaciones tanto como sea posible cuando se trabaja con big data en R cuando la velocidad es una consideración. El código siguiente es un ejemplo de regresión múltiple realizado en una matriz con 1000 filas y 10000 columnas con las variables independientes de interés en cada columna. También se controlan las mismas 5 covariables en cada modelo. Debería tardar menos de 10 segundos en ejecutarse.

n <- 1000
m <- 10000
k <- 5
S <- matrix(2*rnorm(n*m), n, m) # matrix containing (simulated) independent variables of     interest in each column 
y <- rnorm(n)
X0 <- matrix(rnorm(n*k), n, k) # # matrix containing (simulated) covariates
X <- cbind(1, X0)

U1 <- crossprod(X, y)
U2 <- solve(crossprod(X), U1)
ytr <- y - X %*% U2 
U3 <- crossprod(X, S)
U4 <- solve(crossprod(X), U3)
Str <- S - X %*% U4 
Str2 <- colSums(Str ^ 2)

b <- as.vector(crossprod(ytr, Str) / Str2) # Beta's for each column in S after controlling for covariates   
## calculate residual error
sig <- (sum(ytr ^ 2) - b ^ 2 * Str2) / (n - k - 2)
## calculate standard error for beta
err <- sqrt(sig * (1 / Str2))
p <- 2 * pnorm(-abs(b / err))
logp <- -log10(p) # -log10 p-values for each Beta estimate

0voto

Dave Puntos 28

Convierta el problema a un formato de matriz, y si es posible use algo como MATLAB porque R es significativamente más lento para las matrices - como la función index() de MATLAB es súper rápida comparada con la función match() de R,

Una vez en formato de matriz, utiliza con diligencia la expresión escrita por Jase en los comentarios. También está el paquete fastmatch si quieres seguir con R, siempre que los datos estén ordenados a-priori.

Además, una reflexión aparte: Las regresiones de Fama-McBeth suelen realizarse sobre la sección transversal del tiempo, que sobre los valores en una serie temporal.

Finanhelp.com

FinanHelp es una comunidad para personas con conocimientos de economía y finanzas, o quiere aprender. Puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X