Objetos de datos

En R, toda la información se almacena en objetos. Dependiendo de cómo estén organizados los datos, estos objetos pueden adoptar distintas estructuras. Existen cuatro estructuras principales en R:

I. Vectores

II. Matrices

III. Data frames

IV. Listas

Continuamos con nuestro estudio de las listas.


Listas

Una lista en R es una estructura de datos flexible que permite almacenar objetos de distinto tipo y tamaño dentro de un mismo contenedor. A diferencia de vectores o matrices, los elementos de una lista pueden ser números, texto, vectores, data frames e incluso otras listas.

Para crear una lista se utiliza la función list() que recibe una secuencia de elementos (…) que pueden ser nombrados opcionalmente usando nombre = valor.

# Creación de una lista
mi_lista <- list(
  nombre = "María",
  edad = 25,
  notas = c(8, 9, 10)
)

mi_lista
## $nombre
## [1] "María"
## 
## $edad
## [1] 25
## 
## $notas
## [1]  8  9 10


Usamos corchetes simples [] para acceder a sublistas.

mi_lista[1]
## $nombre
## [1] "María"
mi_lista[c(2,3)]
## $edad
## [1] 25
## 
## $notas
## [1]  8  9 10


También utilizando el operador $ cuando las sublistas tienen nombre. Esto es preferido cuando se trabaja con listas nombradas.

mi_lista$nombre      # "María"
## [1] "María"
mi_lista$notas
## [1]  8  9 10


Para acceder a los elementos de las sublistas, accedemos por posición con corchetes dobles [[ ]].

mi_lista["notas"] # devuelve lista
## $notas
## [1]  8  9 10
mi_lista[["notas"]] # devuelve el vector c(8,9,10)
## [1]  8  9 10
mi_lista[["notas"]][2] # devuelve 9
## [1] 9


Entonces, ¿$ y [[]] sirven para lo mismo?

mi_lista <- list(nombre = "María", edad = 25)

mi_lista$nombre
## [1] "María"
mi_lista[["nombre"]]
## [1] "María"


Ahora con variable:

x <- "nombre"

mi_lista[[x]]   # funciona
## [1] "María"
mi_lista$x      # no funciona
## NULL


Para escribir código serio o generalizable → usa [[ ]] Para exploración rápida → $


Las listas son estructuras dinámicas, por lo que pueden modificarse después de su creación.

Es posible cambiar valores existentes, reemplazar elementos completos o incluso agregar nuevos componentes a la lista sin necesidad de redefinirla por completo.

# Modificación de elementos
mi_lista$edad <- 26              # cambiar valor
mi_lista$notas <- c(9, 10, 10)   # modificar vector
mi_lista$ciudad <- "CDMX"        # agregar nuevo elemento


Las listas también pueden contener otras listas en su interior, lo que permite construir estructuras jerárquicas más complejas. Este tipo de organización es útil cuando se necesita representar información agrupada, como conjuntos de estudiantes o registros anidados.

clase <- list(e1 = mi_lista,
              e2 = list(nombre = "Luis",
                        edad = 20,
                        notas = c(8, 9, 10))
              )


# Lista clase de dos estudiantes
clase
## $e1
## $e1$nombre
## [1] "María"
## 
## $e1$edad
## [1] 26
## 
## $e1$notas
## [1]  9 10 10
## 
## $e1$ciudad
## [1] "CDMX"
## 
## 
## $e2
## $e2$nombre
## [1] "Luis"
## 
## $e2$edad
## [1] 20
## 
## $e2$notas
## [1]  8  9 10


En estructuras anidadas, el acceso a los elementos requiere encadenar el operador $ tantas veces como niveles tenga la lista. Esto permite llegar a elementos específicos dentro de sublistas.

# Acceso a elementos
clase$e2$nombre             # "María"
## [1] "Luis"
clase$e2$notas              # notas de María
## [1]  8  9 10


De la misma forma, también es posible modificar elementos dentro de listas anidadas accediendo directamente a su nivel correspondiente, sin necesidad de reconstruir toda la estructura.

# Añadimos ciudad para estudiante 2
clase$e2$ciudad <- "Xalapa"  
clase
## $e1
## $e1$nombre
## [1] "María"
## 
## $e1$edad
## [1] 26
## 
## $e1$notas
## [1]  9 10 10
## 
## $e1$ciudad
## [1] "CDMX"
## 
## 
## $e2
## $e2$nombre
## [1] "Luis"
## 
## $e2$edad
## [1] 20
## 
## $e2$notas
## [1]  8  9 10
## 
## $e2$ciudad
## [1] "Xalapa"


Cuando todos los elementos de una lista requieren la misma operación, la función lapply() permite realizarla de forma automática.

En lugar de modificar cada elemento por separado, lapply() aplica una misma función a todos los elementos de la lista y devuelve una nueva lista con los resultados.

# Obtener el promedio de las notas de cada estudiante
lapply(clase, function(x) mean(x$notas))
## $e1
## [1] 9.666667
## 
## $e2
## [1] 9
# Obtener la edad de cada estudiante
lapply(clase, function(x) x$edad)
## $e1
## [1] 26
## 
## $e2
## [1] 20


Para cada estudiante (x) dentro de clase:

toma sus notas: x$notas calcula el promedio: mean(…) devuelve ese promedio

Nota: Existe también la función mapply(), que permite aplicar una función utilizando varios objetos al mismo tiempo, por ejemplo, dos o más listas o vectores.

Las listas se presentan al final porque muchas de las funciones utilizadas en análisis de datos y aprendizaje automático devuelven sus resultados en este formato.

Por ejemplo, al entrenar un modelo estadístico, el objeto resultante suele ser una lista que contiene distintos componentes, como coeficientes, residuos, valores ajustados y métricas de desempeño. Comprender cómo acceder y manipular estos elementos facilitará la interpretación de los modelos que se estudiarán en las siguientes sesiones.


Introducción a Machine Learning

Hasta este momento del curso hemos utilizado R para importar, explorar y describir datos, además de familiarizarnos con sus principales estructuras, como vectores, matrices, data frames, factores y listas.

A partir de esta unidad cambiaremos el enfoque. Nuestro objetivo ya no será únicamente analizar los datos que ya tenemos, sino construir modelos capaces de aprender relaciones presentes en los datos para realizar predicciones sobre nuevas observaciones.

Machine Learning es una rama de la Inteligencia Artificial dedicada al desarrollo de algoritmos capaces de aprender a partir de datos.

En este curso seguiremos el enfoque del aprendizaje estadístico propuesto por libro Introduction to Statistical Learning with R (ISLR), donde el objetivo es construir modelos que aproximen las relaciones presentes en los datos para realizar predicciones y, cuando sea posible, interpretar esas relaciones.

La idea central puede resumirse mediante la expresión:

\[ Y = f(X) + \varepsilon \] donde:

  • Y representa la variable que queremos predecir.
  • X representa la información disponible para hacerlo.
  • f representa la relación desconocida entre ambas.
  • ε representa variabilidad no explicada (ruido).

Este último término nos dice que, incluso con la misma X, observaremos distintos valores de Y.

Supongamos que disponemos de información sobre viviendas: tamaño, habitaciones, antigüedad, ubicación y precio. Observando suficientes casos, notaríamos patrones en los datos. La pregunta que nos hacemos es:

¿Podemos construir un algoritmo que descubra automáticamente esas relaciones y las utilice para predecir nuevos casos?

Ese será precisamente el objetivo de los modelos que estudiaremos durante esta unidad. Hoy comenzaremos con el más sencillo: regresión lineal.


Regresión lineal

Exploración de problema

El dataset Advertising contiene datos sobre inversión en publicidad en distintos medios y ventas obtenidas.

Nuestro objetivo será responder:

¿Existe una relación entre la inversión en publicidad en televisión y las ventas?

# Cargamos los datos de la página del libro
advertising <- read.csv("https://www.statlearning.com/s/Advertising.csv")

# Eliminar la primera columna si aparece como un índice innecesario (X)
advertising$X <- NULL 

# Comprobar que se cargó correctamente
head(advertising)
##      TV radio newspaper sales
## 1 230.1  37.8      69.2  22.1
## 2  44.5  39.3      45.1  10.4
## 3  17.2  45.9      69.3   9.3
## 4 151.5  41.3      58.5  18.5
## 5 180.8  10.8      58.4  12.9
## 6   8.7  48.9      75.0   7.2


Antes de modelar, necesitamos entender los datos.

  • ¿Cuántas observaciones tenemos?
  • ¿Cuántas variables?
  • ¿Cuál parece ser la variable respuesta?

Esto lo lograremos con la herramienta str(), que resume de forma compacta el tipo de objeto, el número de observaciones y variables si aplica, y un vistazo a los primeros elementos de cada componente.

str(advertising)
## 'data.frame':    200 obs. of  4 variables:
##  $ TV       : num  230.1 44.5 17.2 151.5 180.8 ...
##  $ radio    : num  37.8 39.3 45.9 41.3 10.8 48.9 32.8 19.6 2.1 2.6 ...
##  $ newspaper: num  69.2 45.1 69.3 58.5 58.4 75 23.5 11.6 1 21.2 ...
##  $ sales    : num  22.1 10.4 9.3 18.5 12.9 7.2 11.8 13.2 4.8 10.6 ...


Sabemos que en datos reales no podemos esperar relaciones perfectas, porque siempre existe variabilidad no explicada entre observaciones.

Por esta razón, antes de construir un modelo, utilizamos visualizaciones que nos permitan identificar tendencias generales y patrones que no son evidentes al observar solo la tabla.

Cada observación del dataset puede interpretarse como un par (X, Y), lo que en el plano genera una colección de puntos por lo que usaremos un diagrama de dispersión o “nube de puntos” para visualizar.

plot(advertising$TV,            # Variable en el eje X: inversión en publicidad en TV
     advertising$sales,         # Variable en el eje Y: ventas observadas
     pch=19,                    # Tipo de punto (círculo sólido)
     col="steelblue",          # Color de los puntos
     xlab="Publicidad en TV",   # Etiqueta del eje X
     ylab="Ventas")             # Etiqueta del eje Y


La nube de puntos sugiere una relación entre variables, pero aún no permite cuantificarla de forma precisa ni utilizarla para predicción.

Por eso, nuestro objetivo será que R, pueda encontrar una línea que resuma de la mejor manera esa nube de puntos.

Modelo de regresión lineal lm

En R, la regresión lineal se ajusta mediante la función lm(). La sintaxis general es:

respuesta ~ predictor

que se lee como “explicar la respuesta utilizando el predictor”.

modelo <- lm(sales ~ TV,
             data = advertising)


La función lm() devuelve un objeto completo que contiene toda la información del modelo ajustado.

Se trata de una lista estructurada y podremos acceder a cada uno de sus componentes utilizando $.

modelo$residuals[1:10]
##          1          2          3          4          5          6          7 
##  4.1292255  1.2520260  1.4497762  4.2656054 -2.7272181 -0.2461623  2.0340496 
##          8          9         10 
##  0.4535023 -2.6414087 -5.9304143
modelo$fitted.values[1:10] # Son las predicciones del modelo para cada observación.
##         1         2         3         4         5         6         7         8 
## 17.970775  9.147974  7.850224 14.234395 15.627218  7.446162  9.765950 12.746498 
##         9        10 
##  7.441409 16.530414


Todo lo que aprendimos sobre listas sigue siendo válido, ahora aplicándolo a una lista particular que fue resultado de un modelo estadístico.

Interpretación visual del modelo

plot(advertising$TV,
     advertising$sales,
     pch=19,
     col="steelblue")

abline(modelo,
       col="red",
       lwd=3)


¿Qué representa exactamente la línea roja?

Porque no logra unir los puntos…

Esa línea representa la mejor aproximación lineal de la relación entre variables.

El residuo es la diferencia entre el valor observado y el valor predicho por el modelo. Gráficamente, es la distancia vertical entre el punto observado y la recta originada por la predicción del modelo.

library(ggplot2)

ggplot(advertising, aes(x = TV, y = sales)) +   # Define el dataset y asigna TV al eje X y sales al eje Y

  geom_point(color = "steelblue") +            # Dibuja los puntos observados (cada observación)

  geom_smooth(method = "lm", se = FALSE,       # Ajusta una recta de regresión lineal sin intervalo de confianza
               color = "red") +                # Color de la recta ajustada

  geom_segment(aes(xend = TV,                  # Define el inicio de cada segmento en X (mismo valor de TV)
                   yend = fitted(modelo)),     # Define el final del segmento en Y (valor predicho por el modelo)
               alpha = 0.3)                    # Transparencia de las líneas (residuos visuales)
## `geom_smooth()` using formula = 'y ~ x'


La regresión lineal busca minimizar estas distancias en conjunto.

La limitante es que si sumáramos todas esas distancias, algunas positivas y otras negativas se cancelarían. Por eso la regresión lineal utiliza los cuadrados de los residuos y elige la recta que minimiza la suma de esos errores al cuadrado.

Podríamos decir que, el modelo de regresión lineal buscará una recta que, en promedio, se equivoque lo menos posible.

Evaluación del modelo

La ecuación del modelo es

\[ Y = \beta_{0} + \beta_{1} X \]

La función summary() devuelve los parámetros de la recta que acabamos de ajustar.

summary(modelo)
## 
## Call:
## lm(formula = sales ~ TV, data = advertising)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.3860 -1.9545 -0.1913  2.0671  7.2124 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 7.032594   0.457843   15.36   <2e-16 ***
## TV          0.047537   0.002691   17.67   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.259 on 198 degrees of freedom
## Multiple R-squared:  0.6119, Adjusted R-squared:  0.6099 
## F-statistic: 312.1 on 1 and 198 DF,  p-value: < 2.2e-16


donde:

  • Intercepto (Intercept): es el valor predicho de las ventas cuando la inversión en TV es 0.

  • Pendiente (TV): indica cuánto cambian, en promedio, las ventas por cada unidad adicional invertida en publicidad en TV.

Ahora que tenemos un modelo ajustado, podemos preguntarnos qué tan bien representa los datos observados.

Residual Standard Error (RSE)

Más abajo aparece algo como: Residual standard error: 3.259

Esta es una medida del error típico que comete el modelo al hacer predicciones.

En promedio, nuestras predicciones se equivocan aproximadamente 3.259 unidades de ventas.

Mientras menor sea este valor, mejor se ajusta la recta a los datos.

¡OJO! No significa que todas las predicciones se equivoquen exactamente 3.259. Es un error promedio.

Multiple R-squared

Encontraremos también: Multiple R-squared: 0.61

El coeficiente de determinación, o R², indica qué proporción de la variabilidad observada en la variable respuesta logra explicar el modelo.

Por ejemplo, R² = 0.61, se interpreta como:

Aproximadamente el 61% de la variación en las ventas puede explicarse mediante la inversión en publicidad en televisión.

El 39% restante se debe a otros factores que este modelo no está considerando o al ruido inherente de los datos.

Con lm() y summary() logramos ajustar un modelo de regresión lineal a los datos, además de que identificamos que:

  • Los coeficientes beta 0 y beta 1 nos dicen cuál es la recta que ajustó el modelo.
  • El Residual Standard Error nos indica qué tan grandes son, en promedio, los errores de predicción.
  • El R² nos dice qué tan bien esa recta logra explicar la variabilidad observada en los datos.

Predicción

Una vez estimado el modelo, podemos utilizarlo para predecir valores nuevos de la variable respuesta.

Si una empresa invierte 150 unidades monetarias en publicidad en TV, ¿qué predice nuestro modelo?

predict(modelo,
        newdata=data.frame(TV=150))
##        1 
## 14.16309


De donde interpretamos que el modelo predice aproximadamente 14–15 unidades de ventas para ese nivel de inversión en TV.

Regresión lineal múltiple

Para las ventas en advertising, hicimos el supuesto de que dependen únicamente de la inversión en televisión aunque el conjunto de datos advertising contiene más información.

En problemas reales, la variable respuesta suele depender de múltiples factores simultáneamente.

Observemos nuevamente cuáles son otras variables disponibles.

names(advertising)
## [1] "TV"        "radio"     "newspaper" "sales"


Además de la inversión en televisión (TV), también disponemos de información sobre publicidad en radio (radio) y periódicos (newspaper).

modelo_multiple <- lm(sales ~ TV + radio + newspaper,
                      data = advertising)


Logramos ampliar el mismo modelo agregando más variables separadas por el operador +.

La función sigue siendo exactamente la misma:

  1. usamos lm()
  2. obtenemos un objeto de clase lm
  3. podemos utilizar summary()
  4. podemos utilizar predict()

En esencia, seguimos estimando la misma función, pero ahora incorporamos más variables para mejorar la predicción.

modelo_simple <- lm(sales ~ TV,
                    data = advertising)

modelo_multiple <- lm(sales ~ TV + radio + newspaper,
                      data = advertising)
summary(modelo_simple)
## 
## Call:
## lm(formula = sales ~ TV, data = advertising)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.3860 -1.9545 -0.1913  2.0671  7.2124 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 7.032594   0.457843   15.36   <2e-16 ***
## TV          0.047537   0.002691   17.67   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.259 on 198 degrees of freedom
## Multiple R-squared:  0.6119, Adjusted R-squared:  0.6099 
## F-statistic: 312.1 on 1 and 198 DF,  p-value: < 2.2e-16
summary(modelo_multiple)
## 
## Call:
## lm(formula = sales ~ TV + radio + newspaper, data = advertising)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.8277 -0.8908  0.2418  1.1893  2.8292 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  2.938889   0.311908   9.422   <2e-16 ***
## TV           0.045765   0.001395  32.809   <2e-16 ***
## radio        0.188530   0.008611  21.893   <2e-16 ***
## newspaper   -0.001037   0.005871  -0.177     0.86    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.686 on 196 degrees of freedom
## Multiple R-squared:  0.8972, Adjusted R-squared:  0.8956 
## F-statistic: 570.3 on 3 and 196 DF,  p-value: < 2.2e-16
# $sigma para RSE
# $r.squared


Hasta ahora hemos trabajado con predicción de variables numéricas.

Sin embargo, muchos problemas de interés práctico no buscan un número continuo, sino una categoría como:

  • clasificar un correo como spam o no
  • predecir si un cliente comprará o no
  • determinar si un paciente tiene una enfermedad

Esto nos lleva a la necesidad de modelos de clasificación.

En la siguiente clase estudiaremos la regresión logística, que adapta estas ideas al caso donde la variable respuesta es categórica.