library(ISLR2)
Warning: package 'ISLR2' was built under R version 4.4.1
Se han aglutinado en este anexo diversas herramientas transversales a varios Labs.
El material original del Lab3 Regresión lineal empieza comentando las nociones básicas sobre cargar paquetes (o instalarlos cuando es necesario), pero en todos los Labs es necesario cargar paquetes. Aquí, simplemente recordamos que
Al iniciar una sesión de R
se cargan unos pocos paquetes, los básicos, por lo que el resto de paquetes es necesario cargarlos en cada sesión.
la función para cargar un paquete en una sesión es library()
.
library(ISLR2)
Warning: package 'ISLR2' was built under R version 4.4.1
Packages
> Install
…Aunque R
viene con muchas funciones útiles, y hay muchas más funciones disponibles a través de los paquetes de R
, en ocasiones nos interesará realizar una operación para la que no se dispone de ninguna función. Pues bien, es posible crear/escribir funciones nuevas.
En el material original se crean varias funciones:
LoadLibraries()
que lee los paquetes ISLR2
y MASS
.boot.fn()
predict.regsubsets()
rocplot()
Aquí se ha decidido explicar de forma práctica y sencilla cómo escribir una función. Aprovechamos el ejemplo de creación de la función LoadLibraries()
.
El nombre que le vamos a dar a la función es LoadLibraries
. Para decirle a R
que es una función utilizamos function()
cuya sintaxis es:
function( arglist ) expr
Es decir hay que decir que argumentos espera la función: arglist
, y en expr
la expresión o expresiones que queremos que ejecute la función. Si son varias expresiones se deben escribir entre llaves, {
al principio y }
al final de la función.
<- function() {
LoadLibraries library(ISLR2)
library(MASS)
print("Los paquetes ISLR2 y MASS han sido cargados.")
}
En este ejemplo sencillo, no hay arglist
pues la función que se va a definir no necesita argumentos. Y se utilizan llaves dado que hay 3 expresiones que se van a ejecutar: cargar los dos paquetes e imprimir en la consola/pantalla que se han cargado.
Ahora, si llamamos a la función, se cargan los paquetes y se imprime la sentencia indicada.
LoadLibraries()
Adjuntando el paquete: 'MASS'
The following object is masked from 'package:ISLR2':
Boston
[1] "Los paquetes ISLR2 y MASS han sido cargados."
Otro ejemplo es la función boot.fn
definida en el Lab5 Remuestreo:
boot.fn <- function(data, index) ...
Como se ve se define con 2 argumentos: data
e index
, que después se utilizan en la expresión (se omite por ser específica del tema, no tener aquí el contexto suficiente). Ahora bien, cabe mencionar que al ser una sola expresión la que se va a ejecutar se define sin llaves (véanse más detalles en el Lab5 Remuestreo).
for()
En el material original se definen varios bucles usando esta función for()
:
Esta función for()
sirve para repetir un procedimiento de forma iterativa. Su sintaxis es:
for(var in seq) expr
donde var
es la variable que va cambiando de valor según lo indicado en seq
. Para cada valor que tome var
ejecuta la expresión indica en expr
que puede constar de una sola línea o varias (para lo que es necesario usar llaves de apertura y cierre).
Ejemplo sencillo: imprimir por pantalla una serie de números. Una función para imprimir por pantalla es print()
, basta utilizar i
como variable índice y definir la serie de números, por ejemplo del 1 al 6, o los números 3, 7, 8 y 12.
for (i in 1:6) print(i)
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
for (i in c(3, 7, 8, 12)) print(i)
[1] 3
[1] 7
[1] 8
[1] 12
Ejemplo algo más complejo: guardar en un par de vectores una serie de números. Previamente es necesario inicializar los vectores (si no R
no sabe a qué se refieren los objetos vector1
y vector2
)
<- vector2 <- rep(0,5)
vector1 for (i in 1:5) {
= i-1
vector1[i] = vector1[i]^2
vector2[i]
}data.frame(vector1, vector2)
vector1 vector2
1 0 0
2 1 1
3 2 4
4 3 9
5 4 16
Nótese que se han inicializado los dos vectores a la vez. El vector1
se crea utilizando la variable índice i
, restándole 1 a cada elemento, es decir que el elemento i-ésimo de dicho vector, vector1[i]
, es i-1
. El vector2
se crea utilizando el vector1
, elevando cada elemento i
al cuadrado.
Nota: Estos mismos vectores se pueden definir de otras maneras alternativas. Una de ellas sería:
<- vector2 <- rep(0,5)
vector1 for (i in 0:4) {
+1] = i
vector1[i
}<- vector1^2
vector2 data.frame(vector1, vector2)
vector1 vector2
1 0 0
2 1 1
3 2 4
4 3 9
5 4 16
Nótese la necesidad de poner i+1
en el índice para obtener el mismo resultado que antes.
Para crear, a partir de un conjunto de datos, dos subconjuntos (completamente separados), uno para utilizar como datos de entrenamiento (train) y otro para utilizarlos como datos de test/prueba/validación (test), tenemos varios opciones:
Crear un vector de tipo booleano
(aparece en el Lab4 Clasificación)
Utilizar la función sample()
(aparece en Lab5 Remuestreo, Lab6 Selección de variables, Lab8 Árboles y Lab9 SVM)
Utilizar una variable índice, bien usando la función sample()
anterior, bien usando los valores que se deseen, por ejemplo los números enteros entre \(1\) y \(n\).
(aparece en el Lab4 Clasificación)
De las tres opciones, en las que se use sample()
es la que se puede considerar aleatoria. Ahora bien, cualquier enfoque funciona igualmente bien y es válido.
Nota técnica: Para ajustar los modelos se usará sólo el subconjunto de observaciones train
:
subset
: subset = train
(aparece en el Lab6 Selección de variables),Hitters[train, ]
, (aparece en el Lab6 Selección de variables)Se implementa esta estrategia utilizando inteligentemente alguna de las variables del conjunto de datos.
Por ejemplo, en el Lab4 Clasificación, se dispone de los datos Smarket
que contienen la variable Year
que toma valores de 2001 a 2005 (en total 1250 valores). Se pueden tomar los datos de los años 2001 a 2004 como de entrenamiento y los de 2005 como de test. ¿Cómo se puede hacer esta división con R?… Usando esta estrategia de vector booleano: creando primero un vector correspondiente a las observaciones de 2001 a 2004, y usándolo después para crear el subconjunto de datos de observaciones de 2005.
<- (Smarket$Year < 2005)
train .2005 <- Smarket[!train, ] Smarket
El objeto train
es un vector booleano, ya que sus elementos son TRUE
y FALSE
. Es un vector de 1250 elementos, correspondientes a todas las observaciones en nuestro conjunto de datos.
TRUE
,FALSE
.length(train)
[1] 1250
head(train) # aprovechando que están ordenadas por Year
[1] TRUE TRUE TRUE TRUE TRUE TRUE
tail(train) # aprovechando que están ordenadas por Year
[1] FALSE FALSE FALSE FALSE FALSE FALSE
dim(Smarket[train, ])
[1] 998 9
dim(Smarket.2005)
[1] 252 9
la sentencia Smarket[train, ]
selecciona una submatriz del conjunto de datos del mercado de valores, correspondiente solo a las fechas anteriores a 2005, ya que esas son aquellas para las que los elementos de train
son TRUE
. El resultado de dim()
indica que hay 998
observaciones del año 2001 al 2004.
Smarket[!train, ]
produce una submatriz de los datos del mercado de valores que contiene solo las observaciones para las cuales train
es FALSE
—es decir, las observaciones con fechas en 2005. Esto es así por el símbolo !
(que significa distinto) y se utiliza aquí para invertir todos los elementos del vector booleano. Es decir, !train
es un vector similar a train
, excepto que los elementos que son TRUE
en train
se cambian a FALSE
en !train
, y los elementos que son FALSE
en train
se cambia a TRUE
en !train
. El resultado de dim()
indica que hay 252
de tales observaciones.
sample()
La función sample()
permite obtener muestras aleatorias del conjunto x
que se le especifique, y tantos valores como se le indiquen en size
.
sample(x, size,...)
Al tener un componente aleatorio para garantizar la reproducibilidad de resultados se debe utilizar la función set.seed()
que establece la semilla de aleatorización (véase más adelante en Funciones interesantes). Es decir, estableciendo la misma semilla aleatoria cualquier usuario obtendrá la misma división de conjuntos de entrenamiento y test.
Se ilustra aquí su uso:
<- 0:9 + 0.5
numeros set.seed(1)
<- sample(numeros, 4)) (train1
[1] 8.5 3.5 6.5 0.5
<- sample(numeros, 6)) (train2
[1] 1.5 6.5 2.5 5.5 9.5 7.5
set.seed(2)
<- sample(numeros, 6)) (train2
[1] 4.5 5.5 8.5 0.5 9.5 6.5
Con sample()
se selecciona aleatoriamente del conjunto de observaciones las que se indiquen en size
(segundo argumento): 4
en el primer caso, 6
en el segundo y tercero, por defecto sin reemplazamiento. Aquí se ha omitido el nombre del argumento (en tal caso debe ponerse en el orden indicado en la función), vea ?sample
para más detalles. Por ejemplo, para saber cómo se puede indicar que la selección aleatoria sea con reemplazamiento, como hace el método bootstrap que también aparece en el Lab5 Remuestreo.
En dicho Lab5 Remuestreo se utiliza sample()
de una forma ligeramente distinta a la aquí utilizada (los detalles se explican en dicha sección).
En ocasiones la selección es no aleatoria, se elige por el orden/índice en el que están los datos.
Por ejemplo, en el Lab4 Clasificación se utilizan los datos Caravan
donde se escogen para el conjunto de test las primeras 1000 observaciones, y para el conjunto de entrenamiento, las observaciones restantes. En dicho Lab, primero se escalan/estandarizan las variables (véase la función scale()
en el apartado Funciones interesantes de este mismo anexo). Aquí se ilustra el uso de la selección mediante índices sobre el conjunto de datos original:
dim(Caravan)
[1] 5822 86
<- 1:1000
test <- Caravan[test, ]
test.X <- Caravan[-test, ]
train.X dim(test.X)
[1] 1000 86
dim(train.X)
[1] 4822 86
head(Caravan[,1])
[1] 33 37 37 9 40 23
head(test.X[,1])
[1] 33 37 37 9 40 23
1001:1006 , 1] Caravan[
[1] 40 26 10 38 39 9
head(train.X[,1])
[1] 40 26 10 38 39 9
El vector test
se crea con valores desde 1
hasta 1000
. Al escribir Caravan[test, ]
se obtiene la submatriz de los datos que contienen las observaciones cuyos índices varían entre 1
y 1000
, mientras que al escribir Caravan[-test, ]
produce la submatriz que contiene las observaciones restantes, las que sus índices no están en ese rango. Para completitud, utilizando la función head()
se muestran, para la primera variable, los primeros valores de ambos conjuntos y se comparan con los valores originales de Caravan
.
NA
valores faltantesAparece sólo en el Lab6 Selección de variables, pero es un tema que puede afectar a otros Labs.
Para algunos métodos estadísticos que el vector o el data.frame contenga NA
s produce resultados no deseados. De una manera muy ilustrativa se explica la problemática de los valores faltantes en el libro R for Data Science, en este apartado: https://es.r4ds.hadley.nz/05-transform.html#valores-faltantes… Se dice que los NA
¡son “contagiosos”!
¿Cómo manejar/trabajar con datos/valores faltantes?
La función is.na()
se puede utilizar para identificar las observaciones que faltan. Devuelve un vector de la misma longitud que el vector de entrada, con un TRUE
para los elementos que faltan y un FALSE
para los elementos que no faltan.
Aprovechamos el conjunto de datos Hitters
que se utiliza en el Lab6 Selección de variables. Sabemos que en la variable Salary
hay datos faltantes:
library(ISLR2)
head(Hitters$Salary)
[1] NA 475.0 480.0 500.0 91.5 750.0
length(Hitters$Salary)
[1] 322
sum(is.na(Hitters$Salary))
[1] 59
La función head()
muestra los primeros valores del vector, cuya length()
es 322. Con la función sum()
se obtienen los elementos/datos que faltan en Salary
, concretamente falta el salario de 59 jugadores.
Como se ha mencionado, trabajar con variables/vectores con NA
s puede conducir a resultados no deseados. En algunos casos, la solución es incluir un argumento:
mean(Hitters$Salary)
[1] NA
mean(Hitters$Salary, na.rm = TRUE)
[1] 535.9259
En otros casos hay que acudir a la función na.omit()
, que elimina todas las filas de un data.frame que tengan valores faltantes en cualquier variable.
dim(Hitters)
[1] 322 20
<- na.omit(Hitters)
Hitters mean(Hitters$Salary) # sólo se han quitado los 59 sin Salary
[1] 535.9259
dim(Hitters)
[1] 263 20
sum(is.na(Hitters))
[1] 0
Dado que el interés de cada función depende de factores subjetivos, aquí se presentan por orden alfabético (no subjetivo).
anova()
Aparece en: Lab3 Regresión Lineal y Lab7 Modelización No lineal.
La función anova()
realiza un contraste de hipótesis comparando los modelos que se le indiquen:
anova(modelo1, modelo2, ...)
Los modelos deben estar anidados: los predictores de modelo1
deben ser un subconjunto de los predictores de modelo2
; los de modelo2
deben serlo de modelo3
, etc.
La comparación la hace por pares de modelos. La hipótesis nula es que los dos modelos se ajustan igualmente bien a los datos, y la hipótesis alternativa es que el modelo más grande se ajusta significativamente mejor a los datos.
En lugar de proporcionar aquí un ejemplo, remitimos al Lab7 Modelización No lineal para ver un ejemplo con su contexto.
contrast()
Aparece en: Lab3 Regresión Lineal y Lab4 Clasificación.
La función contrasts()
devuelve la codificación que R
usa para las variables ficticias, dummys, asociadas a variables cualitativas.
contrasts(iris$Species)
versicolor virginica
setosa 0 0
versicolor 1 0
virginica 0 1
Así, R
ha creado una variable ficticia Speciesversicolor
que toma el valor 1 si la especie es versicolor y 0 en caso contrario. También ha creado una variable ficticia Speciesvirginica
que equivale a 1 si la especie es virginica y 0 en caso contrario. La especie setosa corresponde a 0 para cada una de las dos variables ficticias anteriores.
Use ?contrasts
para aprender sobre otros contrastes y cómo configurarlos.
I()
Aparece en: Lab3 Regresión Lineal, Lab5 Remuestreo y Lab7 Modelización No lineal.
La función I()
permite envolver la expresión que se escriba dentro. Típicamente es necesaria para incluir en una fórmula una potencia usando ^
. Envolver con I()
permite el uso estándar en R
, que es elevar X
a la potencia 2
, pues se crea un vector/variable con los valores correspondientes a la potencia considera. Con ello aparecerá el coeficiente asociado a dicha “potencia” en el modelo que se esté ajustando (con lm()
, glm()
, …).
Sin embargo, en el Lab7 Modelización No lineal se hace un uso distinto al anterior: se crea una variable de respuesta binaria sobre la marcha: I(wage > 250)
.
poly()
Aparece en: Lab3 Regresión Lineal y Lab7 Modelización No lineal.
La función poly()
permite crear de forma abreviada la fórmula de un polinomio, evitando escribir una fórmula larga con potencias de la variable explicativa o predictora. Típicamente se usa para incluirla en un modelo: y ~ ...
. Por ejemplo, para definir el polinomio de quinto orden de una variable llamada X1
poly(X1, 5)
Por defecto, la función poly()
ortogonaliza los predictores: esto significa que las características que genera esta función no son simplemente una secuencia de potencias del argumento. Concretamente, devuelve una matriz cuyas columnas son una base de polinomios ortogonales (cada columna es una combinación lineal de las variables X1
, X1^2
, X1^3
…).
Para obtener los polinomios “sin procesar”, la base polinómica, se debe usar en poly()
el argumento Raw = TRUE
. Es equivalente a “envolver” las potencias X1^2
, etc. a través de la función I()
(vista anteriormente) pues el símbolo ^
tiene un significado especial en las fórmulas (de ahí la necesidad de envolver las potencias). Es decir,
poly(X1, 4)
es equivalente a X1 + I(X1^2) + I(X1^3) + I(X1^4)
Otra forma equivalente (y algo más compacta) es:
cbind(X1, X1^2, X1^3, X1^4)
construye una matriz a partir de una colección de vectores. Cualquier llamada de función como cbind()
dentro de una fórmula también sirve como ‘envoltorio’.
Se muestra así la flexibilidad del lenguaje de fórmulas en R
.
Nota: La elección de la base afecta a las estimaciones de los coeficientes del modelo que se quiera ajustar, pero no afecta a los valores ajustados obtenidos.
scale()
Aparece en: Lab4 Clasificación e indirectamente en Lab9 SVM.
En ocasiones es bueno/necesario escalar/estandarizar/tipificar las variables, en el sentido de llevarlas a la misma escala, que aquí significa que las variables tengan media 0 y desviación típica 1. Así se consigue que las variables sean comparables.
En el Lab4 Clasificación en el método KNN es bueno/necesario escalar las variables dado que se busca el vecino más próximo de una observación/dato, por lo que la escala afecta a la cercanía. Las variables con una escala grande tendrán un efecto mucho mayor en la distancia entre las observaciones, que las variables con una escala pequeña. Por ejemplo, si en un conjunto de datos se tienen dos variables, “salario anual” y “edad” (medidas en dólares y años, respectivamente). En lo que respecta a “distancia”, una diferencia de 1000 en salario es enorme en comparación con una diferencia de 50 en edad. Esto es contrario a nuestra intuición de que una diferencia salarial de 1000 dólares anuales es bastante pequeña en comparación con una diferencia de edad de 50 años. Además, si midiéramos el “salario” en yenes japoneses, o si midiéramos la “edad” en minutos, los resultados de distancia serían bastante diferentes de los que obtenemos midiendo en dólares y años.
La función scale()
estandariza los datos. Lo hacemos sobre las dos primeras variables del conjunto de datos Caravan
, del paquete ISLR2
:
head(Caravan[, 1:2])
MOSTYPE MAANTHUI
1 33 1
2 37 1
3 37 1
4 9 1
5 40 1
6 23 1
summary(Caravan[, 1:2])
MOSTYPE MAANTHUI
Min. : 1.00 Min. : 1.000
1st Qu.:10.00 1st Qu.: 1.000
Median :30.00 Median : 1.000
Mean :24.25 Mean : 1.111
3rd Qu.:35.00 3rd Qu.: 1.000
Max. :41.00 Max. :10.000
c(sd(Caravan[, 1]), sd(Caravan[, 2]))
[1] 12.8467057 0.4058421
<- scale(Caravan[,1:2])
standardized.X head(standardized.X)
MOSTYPE MAANTHUI
1 0.68084775 -0.2725565
2 0.99221162 -0.2725565
3 0.99221162 -0.2725565
4 -1.18733547 -0.2725565
5 1.22573452 -0.2725565
6 -0.09756193 -0.2725565
summary(standardized.X)
MOSTYPE MAANTHUI
Min. :-1.8101 Min. :-0.2726
1st Qu.:-1.1095 1st Qu.:-0.2726
Median : 0.4473 Median :-0.2726
Mean : 0.0000 Mean : 0.0000
3rd Qu.: 0.8365 3rd Qu.:-0.2726
Max. : 1.3036 Max. :21.9036
c(sd(standardized.X[, 1]), sd(standardized.X[, 2]))
[1] 1 1
set.seed()
Aparece en casi todos los Labs
La función set.seed()
permite establecer una semilla de aleatorización. Siempre que se utilice la misma semilla, los resultados aleatorios posteriores arrojarán los mismos resultados en cualquier máquina que se ejecuten: reproducibilidad
rnorm(4) # genera 4 números aleatorios utilizando la Normal(0,1)
[1] 0.1256203 -0.7098623 -0.9122442 1.0517744
# En cada ordenador se obtendrán unos valores distintos
set.seed(1)
rnorm(4) # en todos los ordenadores se obtendrán los mismos valores
[1] -0.6264538 0.1836433 -0.8356286 1.5952808