Nota : He publicado una versión ampliada de esta respuesta en mi sitio web .
¿Sería tan amable de publicar una respuesta similar con el motor R real expuesto?
Claro que sí. Vamos a la madriguera del conejo.
La primera capa es lm
la interfaz expuesta al programador de R. Puedes ver el código fuente de esto simplemente escribiendo lm
en la consola R. La mayor parte (como la mayor parte del código de producción) está ocupada comprobando entradas, estableciendo atributos de objetos y lanzando errores; pero esta línea llama la atención
lm.fit(x, y, offset = offset, singular.ok = singular.ok,
...)
lm.fit
es otra función de R, puedes llamarla tú mismo. Mientras que lm
trabaja cómodamente con fórmulas y marcos de datos, lm.fit
quiere matrices, así que eso es un nivel de abstracción eliminado. Comprobación de la fuente de lm.fit
más trabajo, y la siguiente línea realmente interesante
z <- .Call(C_Cdqrls, x, y, tol, FALSE)
Ahora estamos llegando a alguna parte. .Call
es la forma que tiene R de llamar al código C. Hay una función C, C_Cdqrls en el código fuente de R en alguna parte, y tenemos que encontrarla. Aquí está .
Observando la función C, de nuevo, encontramos principalmente comprobación de límites, limpieza de errores y trabajo ocupado. Pero esta línea es diferente
F77_CALL(dqrls)(REAL(qr), &n, &p, REAL(y), &ny, &rtol,
REAL(coefficients), REAL(residuals), REAL(effects),
&rank, INTEGER(pivot), REAL(qraux), work);
Así que ahora estamos en nuestro tercer lenguaje, R ha llamado a C que está llamando a fortran. Aquí está el código fortran .
El primer comentario lo dice todo
c dqrfit is a subroutine to compute least squares solutions
c to the system
c
c (1) x * b = y
(curiosamente, parece que el nombre de esta rutina se cambió en algún momento, pero alguien olvidó actualizar el comentario). Así que por fin estamos en el punto donde podemos hacer un poco de álgebra lineal, y realmente resolver el sistema de ecuaciones. Este es el tipo de cosas para las que Fortran es realmente bueno, lo que explica por qué hemos pasado por tantas capas para llegar hasta aquí.
El comentario también explica lo que va a hacer el código
c on return
c
c x contains the output array from dqrdc2.
c namely the qr decomposition of x stored in
c compact form.
Así que fortran va a resolver el sistema encontrando el $QR$ descomposición.
Lo primero que ocurre, y con mucho lo más importante, es
call dqrdc2(x,n,n,p,tol,k,qraux,jpvt,work)
Esto llama a la función fortran dqrdc2
en nuestra matriz de entrada x
. ¿Qué es esto?
c dqrfit uses the linpack routines dqrdc and dqrsl.
Por fin hemos llegado a linpack . Linpack es una biblioteca de álgebra lineal fortran que existe desde los años 70. La mayoría del álgebra lineal seria eventualmente encuentra su camino a linpack. En nuestro caso, estamos utilizando la función dqrdc2
c dqrdc2 uses householder transformations to compute the qr
c factorization of an n by p matrix x.
Aquí es donde se hace el trabajo de verdad. Me llevaría un día entero entender lo que hace este código, es de muy bajo nivel. Pero genéricamente, tenemos una matriz $X$ y queremos factorizarlo en un producto $X = QR$ donde $Q$ es una matriz ortogonal y $R$ es una matriz triangular superior. Esto es algo inteligente, porque una vez que se tiene $Q$ y $R$ puede resolver las ecuaciones lineales de regresión
$$ X^t X \beta = X^t Y $$
muy fácilmente. De hecho
$$ X^t X = R^t Q^t Q R = R^t R $$
por lo que todo el sistema se convierte en
$$ R^t R \beta = R^t Q^t y $$
pero $R$ es triangular superior y tiene el mismo rango que $X^t X$ por lo que si nuestro problema está bien planteado, es de rango completo, y podemos resolver el sistema reducido.
$$ R \beta = Q^t y $$
Pero esto es lo increíble. $R$ es triangular superior, por lo que la última ecuación lineal aquí es simplemente constant * beta_n = constant
por lo que resolviendo para $\beta_n$ es trivial. A continuación, puede subir las filas, una por una, y sustituir en el $\beta$ s que ya conoces, cada vez obteniendo una simple ecuación lineal de una variable para resolver. Así, una vez que tengas $Q$ y $R$ todo se reduce a lo que se llama sustitución regresiva que es fácil. Puede leer más información al respecto aquí en el que se elabora un pequeño ejemplo explícito.