Imagine que uno tiene una matriz de datos \(X \in \mathbb{R}^{n \times p}\) que consiste en \(n\) observaciones, cada una con \(p\) caracterÃsticas, asà como un vector de respuesta \(y \in \mathbb{R}^n\). Queremos construir un modelo para \(y\) usando las columnas de caracterÃsticas en \(X\). En los mÃnimos cuadrados ordinarios (MCO), uno busca un vector de coeficientes \(\hat{\beta} \in \mathbb{R}^p\) tal que \[\begin{aligned} \hat{\beta} = \underset{\beta \in \mathbb{R}^p}{\text{argmin}} \quad \|y - X\beta \|_2^2. \end{aligned}\]
En los mÃnimos cuadrados no negativos (Non Negative Least Squares, NNLS), buscamos coeficientes \(\hat{\beta} \in \mathbb{R}^p\) vectoriales de manera que se minimice \(\quad \|y - X\beta \|_2^2\) sujeto al requisito adicional de que cada elemento de no sea negativo. Hay varias formas de realizar NNLS en R. Se presentan los dos usuales con los paquetes nnls y glmnet, que suelen tener problemas, y al final el de Kjytay (2019), quien propone un proceso sencillo y que corre sin mayores problema al implementarlo. Generemos algunos datos falsos que usaremos para el resto de la publicación:
set.seed(1)
n <- 100; p <- 10
x <- matrix(rnorm(n * p), nrow = n)
y <- x %*% matrix(rep(c(1, -1), length.out = p), ncol = 1) + rnorm(n)
nnlslibrary(nnls)
mod1 <- nnls(x, y)
mod1$x
## [1] 0.9073423 0.0000000 1.2971069 0.0000000 0.9708051 0.0000000 1.2002310
## [8] 0.0000000 0.3947028 0.0000000
Método 2: el paquete glmnet
La función glmnet() resuelve el problema de minimización \[\begin{aligned} \hat{\beta} = \underset{\beta \in \mathbb{R}^p}{\text{argmin}} \quad \frac{1}{2n} \|y - X\beta\|_2^2 + \lambda \left[\frac{1-\alpha}{2}\|\beta\|_2^2 + \alpha \|\beta\|_1 \right], \end{aligned}\] donde \(\alpha\) y \(\lambda\) son hiperparámetros que elige el usuario. Al fijar \(\alpha = 1\) (el valor predeterminado) y \(\lambda = 0\), glmnet() termina resolviendo el problema de los MÃnimos Cuadrados Ordinarios (Ordinary Least Sqaures, OLS). Al establecer lower.limits = 0, esto obliga a que los coeficientes sean no negativos. También debemos establecer intercept = FALSE para no tener un término de intercepción extraño.
library(glmnet)
## Loading required package: Matrix
## Loaded glmnet 3.0-1
mod2 <- glmnet(x, y, lambda = 0, lower.limits = 0, intercept = FALSE)
coef(mod2)
## 11 x 1 sparse Matrix of class "dgCMatrix"
## s0
## (Intercept) .
## V1 0.9073427
## V2 .
## V3 1.2971070
## V4 .
## V5 0.9708049
## V6 .
## V7 1.2002310
## V8 .
## V9 0.3947028
## V10 .
bvlsLos NNLS son un caso especial de MÃnimos Cuadrados de Variable Acotada (Bounded Variable Least Squares, BVLS), donde en lugar de tener restricciones \(\beta_j\geq0\) para cada \(j = 1, 2, \dots, p\), uno tiene restricciones \(a_j\leq\beta_j\leq b_j\) para cada \(j\). Los BVLS se implementan en la función bvls() del paquete bvls:
library(bvls)
mod3 <- bvls(x, y, bl = rep(0, p), bu = rep(Inf, p))
mod3$x
## [1] 0.9073423 0.0000000 1.2971069 0.0000000 0.9708051 0.0000000 1.2002310
## [8] 0.0000000 0.3947028 0.0000000
En lo anterior, bl contiene los lÃmites inferiores para los coeficientes, mientras que bu contiene los lÃmites superiores.
Cosas que pensé en un punto. Tres formas de hacer mÃnimos cuadrados no negativos en R.