1 Introducción

El presente análisis utiliza el dataset “Students Performance in Exams”, una base de datos educativa que recopila información sobre el rendimiento académico de estudiantes de secundaria en tres dimensiones: matemáticas, lectura y escritura.

Además de los puntajes obtenidos, el conjunto de datos incluye variables demográficas como el género, el grupo étnico, el nivel de educación de los padres, el tipo de almuerzo recibido (regular o subsidiado), y si el estudiante completó un curso de preparación para el examen.

Este conjunto de datos es ideal para introducirse en técnicas de análisis de datos con R, ya que permite:

*Explorar la estructura de un dataset real,

*Visualizar relaciones entre variables

*Manipular los datos usando dplyr

*Crear gráficos con ggplot2

*Construir un modelo de regresión lineal simple

A lo largo de este documento se presentarán diferentes ejemplos de código para ilustrar cómo abordar tareas comunes en análisis de datos, lo cual puede servir como guía para el lector. También se explicarán paso a paso los comandos utilizados, con el objetivo de que cualquiera pueda comprender su funcionamiento y adaptarlos a sus propios proyectos.

Pueden acceder a la base copiando y pegando el siguiente link: https://www.kaggle.com/datasets/spscientist/students-performance-in-exams

2 Preparamos nuestro espacio de trabajo

Este es el momento en el que vamos a planificar nuestro trabajo. Usamos nuestro conocimiento teórico para anticiparnos a las operaciones que tendremos que realizar con los datos.

Lo primero que vamos a hacer es instalar y/o cargar todos los recursos que vayamos a necesitar para nuestro análisis. Solo cargamos lo que vamos a usar.

Durante el proceso de investigación, puede ocurrir que tengamos que recurrir a paquetes que originalmente no habíamos contemplado. Es una buena práctica remitirse a esta sección para declarar de manera ordenada todo lo que hemos utilizado. Eso garantiza que nuestro trabajo y resultado sea reproducible

# Instalamos
# readr nos sirve para leer el archivo csv donde tenemos los datos
if (!require("readr")) install.packages("readr")
## Loading required package: readr
# dplyr nos ayuda a manipular los datos
if (!require("dplyr")) install.packages("dplyr")
## Loading required package: dplyr
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
# ggplot2 nos ayuda a generar gráficos
if (!require("ggplot2")) install.packages("ggplot2")
## Loading required package: ggplot2
# lmtest nos ayuda a comprobar que se cumplen los supuestos de la regresión
if (!require("lmtest")) install.packages("lmtest")
## Loading required package: lmtest
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
# Lo que sigue es cargar las librerías
library(readr)
library(dplyr)
library(ggplot2)
library(lmtest)

3 Carga de datos

En esta sección aprenderemos a cargar un conjunto de datos en R desde un archivo .csv. Utilizaremos varias funciones y opciones para mostrar diferentes formas de importar datos, controlando aspectos como el separador, el tipo de datos, y el manejo de encabezados.

El archivo utilizado se llama StudentsPerformance.csv. Siempre debemos asegurarnos de que el archivo esté en el directorio de trabajo. Esto podemos configurarlo en RStudio con Session > Set Working Directory > mi_proyecto.

3.1 Opción 1

Carga rápida usando readr.

Cuando ejecutamos el código, la consola nos proporciona cierta información sobre la base:

  • 1000 filas,

  • 8 columnas,

  • separador “,”

  • 5 variables nominales

  • 3 variables numéricas (spoiler-alert: estas son las que vamos a usar en la regresión!).

# Cargar el dataset
datos <- read_csv("StudentsPerformance.csv")
## Rows: 1000 Columns: 8
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (5): gender, race/ethnicity, parental level of education, lunch, test pr...
## dbl (3): math score, reading score, writing score
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

3.2 Opción 2

Esta es la forma tradicional de cargar datos. Conviene especificar stringsAsFactors = FALSE para evitar que las columnas de texto se conviertan automáticamente en factores (categorías).

# Opción clásica de R base
datos2 <- read.csv("StudentsPerformance.csv", 
                   header = TRUE,     # El archivo tiene encabezados
                   sep = ",",         # Separador de columnas
                   stringsAsFactors = FALSE)  # No convertir texto en factores

3.3 Opción 3

En esta opción col_types = cols()nos permite tener control sobre el tipo de cada columna.

# Cargar datos con tipos de columnas definidos manualmente
datos3 <- read_csv("StudentsPerformance.csv", 
                   col_types = cols(
                     gender = col_character(),
                     `race/ethnicity` = col_factor(),
                     `parental level of education` = col_character(),
                     lunch = col_factor(),
                     `test preparation course` = col_factor(),
                     `math score` = col_double(),
                     `reading score` = col_double(),
                     `writing score` = col_double()
                   ))

4 Exploración de los datos

Por lo general, nos conviene inspeccionar los datos que hemos cargado o fragmentos de la base (si tenemos una cantidad de datos demasiado grande).

El análisis exploratorio es fundamental para realizar un modelo de regresión lineal. Estos datos vamos a tener que reportarlos en un estudio real.

Pero, incluso si ya tenemos cierto conocimiento de nuestra base y simplemente estamos repitiendo un análisis, es buena idea verificar que los datos se hayan cargado correctamente y que no arrastremos problemas a las secciones subsiguientes.

4.1 Primer recurso

Visualizar las primeras y ultimas filas de la tabla

# Ver las primeras 6 filas del dataset
head(datos)  # head() muestra por defecto las primeras 6 filas
## # A tibble: 6 × 8
##   gender `race/ethnicity` parental level of educa…¹ lunch test preparation cou…²
##   <chr>  <chr>            <chr>                     <chr> <chr>                 
## 1 female group B          bachelor's degree         stan… none                  
## 2 female group C          some college              stan… completed             
## 3 female group B          master's degree           stan… none                  
## 4 male   group A          associate's degree        free… none                  
## 5 male   group C          some college              stan… none                  
## 6 female group B          associate's degree        stan… none                  
## # ℹ abbreviated names: ¹​`parental level of education`,
## #   ²​`test preparation course`
## # ℹ 3 more variables: `math score` <dbl>, `reading score` <dbl>,
## #   `writing score` <dbl>
# Mostrar las primeras 10 filas utilizando el operador pipe (%>%) de dplyr
datos %>% head(10)  # Es equivalente a head(datos, 10)
## # A tibble: 10 × 8
##    gender `race/ethnicity` parental level of educ…¹ lunch test preparation cou…²
##    <chr>  <chr>            <chr>                    <chr> <chr>                 
##  1 female group B          bachelor's degree        stan… none                  
##  2 female group C          some college             stan… completed             
##  3 female group B          master's degree          stan… none                  
##  4 male   group A          associate's degree       free… none                  
##  5 male   group C          some college             stan… none                  
##  6 female group B          associate's degree       stan… none                  
##  7 female group B          some college             stan… completed             
##  8 male   group B          some college             free… none                  
##  9 male   group D          high school              free… completed             
## 10 female group B          high school              free… none                  
## # ℹ abbreviated names: ¹​`parental level of education`,
## #   ²​`test preparation course`
## # ℹ 3 more variables: `math score` <dbl>, `reading score` <dbl>,
## #   `writing score` <dbl>
# Mostrar las últimas 10 filas del dataset
datos %>% tail(10)  # tail() muestra las últimas filas; por defecto 6, aquí indicamos 10
## # A tibble: 10 × 8
##    gender `race/ethnicity` parental level of educ…¹ lunch test preparation cou…²
##    <chr>  <chr>            <chr>                    <chr> <chr>                 
##  1 male   group E          high school              free… completed             
##  2 female group B          some high school         stan… completed             
##  3 female group D          associate's degree       free… none                  
##  4 female group D          bachelor's degree        free… none                  
##  5 male   group A          high school              stan… none                  
##  6 female group E          master's degree          stan… completed             
##  7 male   group C          high school              free… none                  
##  8 female group C          high school              free… completed             
##  9 female group D          some college             stan… completed             
## 10 female group D          some college             free… none                  
## # ℹ abbreviated names: ¹​`parental level of education`,
## #   ²​`test preparation course`
## # ℹ 3 more variables: `math score` <dbl>, `reading score` <dbl>,
## #   `writing score` <dbl>

4.2 Segundo recurso

Revisar el tipo de datos que tenemos en cada columna

str() muestra las variables, su tipo (numérico, caracter, etc.), y los primeros valores

# Ver la estructura del dataframe
str(datos)
## spc_tbl_ [1,000 × 8] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ gender                     : chr [1:1000] "female" "female" "female" "male" ...
##  $ race/ethnicity             : chr [1:1000] "group B" "group C" "group B" "group A" ...
##  $ parental level of education: chr [1:1000] "bachelor's degree" "some college" "master's degree" "associate's degree" ...
##  $ lunch                      : chr [1:1000] "standard" "standard" "standard" "free/reduced" ...
##  $ test preparation course    : chr [1:1000] "none" "completed" "none" "none" ...
##  $ math score                 : num [1:1000] 72 69 90 47 76 71 88 40 64 38 ...
##  $ reading score              : num [1:1000] 72 90 95 57 78 83 95 43 64 60 ...
##  $ writing score              : num [1:1000] 74 88 93 44 75 78 92 39 67 50 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   gender = col_character(),
##   ..   `race/ethnicity` = col_character(),
##   ..   `parental level of education` = col_character(),
##   ..   lunch = col_character(),
##   ..   `test preparation course` = col_character(),
##   ..   `math score` = col_double(),
##   ..   `reading score` = col_double(),
##   ..   `writing score` = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

summary()devuelve mínimo, 1er cuartil, mediana, media, 3er cuartil y máximo para cada variable numérica

# Mostrar estadísticas descriptivas de las variables numéricas
summary(datos)
##     gender          race/ethnicity     parental level of education
##  Length:1000        Length:1000        Length:1000                
##  Class :character   Class :character   Class :character           
##  Mode  :character   Mode  :character   Mode  :character           
##                                                                   
##                                                                   
##                                                                   
##     lunch           test preparation course   math score     reading score   
##  Length:1000        Length:1000             Min.   :  0.00   Min.   : 17.00  
##  Class :character   Class :character        1st Qu.: 57.00   1st Qu.: 59.00  
##  Mode  :character   Mode  :character        Median : 66.00   Median : 70.00  
##                                             Mean   : 66.09   Mean   : 69.17  
##                                             3rd Qu.: 77.00   3rd Qu.: 79.00  
##                                             Max.   :100.00   Max.   :100.00  
##  writing score   
##  Min.   : 10.00  
##  1st Qu.: 57.75  
##  Median : 69.00  
##  Mean   : 68.05  
##  3rd Qu.: 79.00  
##  Max.   :100.00

4.3 Tercer recurso

Ahora que tenemos una idea general de los datos, podemos realizar resúmenes agrupados por variables como género o grupo étnico.

# Contar cuántos estudiantes hay en cada grupo étnico
datos %>%
  count(`race/ethnicity`) %>%  # Agrupa por 'race/ethnicity' y cuenta las observaciones
  arrange(desc(n))             # Ordena los resultados de mayor a menor
## # A tibble: 5 × 2
##   `race/ethnicity`     n
##   <chr>            <int>
## 1 group C            319
## 2 group D            262
## 3 group B            190
## 4 group E            140
## 5 group A             89
# Calcular promedios de puntajes por género
datos %>%
  group_by(gender) %>%  # Agrupa las observaciones por género
  summarise(
    promedio_matematicas = mean(`math score`, na.rm = TRUE),   # Calcula la media de cada variable
    promedio_lectura = mean(`reading score`, na.rm = TRUE),
    promedio_escritura = mean(`writing score`, na.rm = TRUE)
  )
## # A tibble: 2 × 4
##   gender promedio_matematicas promedio_lectura promedio_escritura
##   <chr>                 <dbl>            <dbl>              <dbl>
## 1 female                 63.6             72.6               72.5
## 2 male                   68.7             65.5               63.3

4.4 Cuarto recurso

También graficaremos los resultados para facilitar su interpretación.

# Crear un gráfico de barras con los promedios de matemáticas por grupo étnico
datos %>%
  group_by(`race/ethnicity`) %>%
  summarise(promedio_matematicas = mean(`math score`)) %>%
  ggplot(aes(x = `race/ethnicity`, y = promedio_matematicas, fill = `race/ethnicity`)) +
  geom_bar(stat = "identity") +  # Crea las barras con altura igual al valor (no al conteo)
  labs(title = "Promedio de Matemáticas por Grupo Étnico", 
       x = "Grupo Étnico", 
       y = "Promedio")

# Crear un boxplot para observar la distribución del puntaje de lectura según el nivel educativo de los padres
ggplot(datos, aes(x = `parental level of education`, y = `reading score`)) +
  geom_boxplot(fill = "skyblue") +   # Boxplot con color de relleno
  coord_flip() +                     # Gira los ejes para facilitar lectura horizontal
  labs(title = "Distribución de Reading Score por Nivel Educativo de los Padres", 
       x = "Nivel educativo", 
       y = "Reading Score")

5 Visualizar relación entre las variables de interés

Un recurso útil puede ser la creación de un gráfico de dispersión con una línea de regresión lineal ajustada. Esto nos sirve para visualizar si hay una relación lineal entre dos variables numéricas. En nuestro caso, nos interesa estudiar la relación entre el puntaje de escritura y el de lectura.

Desarmamos el código línea a línea:

  • gplot(datos, aes(x = ..., y = ...)): Inicializa el gráfico con el conjunto de datos datos, mapeando el eje X al writing score y el eje Y al reading score.

  • geom_point(...): Agrega los puntos del gráfico de dispersión.

alpha = 0.6: Ajusta la transparencia de los puntos (0 totalmente transparente, 1 totalmente opaco).

color = "steelblue": Define el color de los puntos.

  • geom_smooth(method = "lm", col = "red", se = TRUE): Agrega una línea de tendencia ajustada por regresión lineal.

method = "lm": Le dice a ggplot que use el modelo lineal (

col = "red": Color rojo para la línea.

se = TRUE: Muestra el intervalo de confianza alrededor de la línea (por defecto es del 95%)

  • labs(...): Definir el título del gráfico
ggplot(datos, aes(x = `writing score`, y = `reading score`)) +
  geom_point(alpha = 0.6, color = "steelblue") +
  geom_smooth(method = "lm", col = "red", se = TRUE) +
  labs(title = "Relación entre Reading Score y Writing Score")
## `geom_smooth()` using formula = 'y ~ x'

6 Creamos el modelo

Si el modelo de regresión que vimos en el paso anterior nos parece interesante, podemos guardarlo en una variable y continuar trabajando sobre el mismo

  • reading score es la variable dependiente (Y).

  • writing score es la variable independiente (X).

  • data = datos: El modelo se ajusta usando los datos del dataframe datos

  • lm() es una función para ajustar modelos lineales.

La función lm() utiliza el método de mínimos cuadrados ordinarios (OLS - Ordinary Least Squares). Eso es justamente lo que nosotros estamos ensayando, asi que nos viene de diez.

modelo <- lm(`reading score` ~ `writing score`, data = datos)

6.1 Resumen del modelo

Llegó la hora de la verdad. El paso que sigue consiste en analizar los resultados de nuestro modelo.

El resumen del modelo nos dice cuánto mejora nuestra predicción del puntaje de lectura gracias a conocer el puntaje de escritura, y cuán confiables son esas estimaciones. Además, nos permite evaluar si el modelo es estadísticamente útil y si los errores (residuals) están distribuidos de manera adecuada.

summary(modelo)
## 
## Call:
## lm(formula = `reading score` ~ `writing score`, data = datos)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -14.6226  -2.9554   0.1352   3.0399  11.4602 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      6.75051    0.63175   10.69   <2e-16 ***
## `writing score`  0.91719    0.00906  101.23   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 4.352 on 998 degrees of freedom
## Multiple R-squared:  0.9113, Adjusted R-squared:  0.9112 
## F-statistic: 1.025e+04 on 1 and 998 DF,  p-value: < 2.2e-16

Vamos a tratar de desarmar esto, parte por parte, para comprenderlo mejor

6.1.1 Call

Call: 
lm(formula = `reading score` ~ `writing score`, data = datos)

Esta es simplemente la llamada a la función lm() que ejecutamos anteriormente Es un recordatorio para saber qué fórmula usamos, qué variables incluimos en el modelo y de qué base de datos (datos) provienen.

6.1.2 Residuals

Residuals:      
    Min       1Q   Median       3Q      Max  
-14.6226  -2.9554   0.1352   3.0399  11.4602 

Los residuos son las diferencias entre los valores reales observados de la variable dependiente (reading score) y los valores que predice el modelo. Es decir:

residuo = valor real − valor predicho

Tenemos un resumen de los residuos

  • Min y Max: residuos situados en los extremos (peor y mejor predicción).

  • 1Q (Primer Cuartil) y 3Q (Tercer Cuartil): nos dicen cómo están distribuidos los errores.

  • Median: el residuo del caso “típico” (debería estar cerca de 0 si el modelo es bueno).

6.1.3 Coefficients

Esta es una parte crucial del resumen, ya que nos muestra los parámetros estimados del modelo, junto con su precisión e importancia estadística.

Columna Significado
Estimate Es el valor estimado del coeficiente del modelo.
Std. Error El error estándar: mide la precisión de la estimación del coeficiente. Valores bajos indican mayor precisión.
t value Valor t de Student: se usa para testear si el coeficiente es significativamente distinto de 0. Se calcula como Estimate / Std.Error.
Pr(>|t|) Valor p (p-value): indica la probabilidad de obtener un coeficiente tan extremo por azar si el coeficiente real fuera 0 (hipótesis nula). Un valor p bajo sugiere que el coeficiente es estadísticamente significativo.

Intercept (β₀ = 6.75): Cuando writing score = 0, el modelo predice un reading score de 6.75 (aunque en la práctica un puntaje 0 en escritura probablemente no exista, el intercepto se incluye en el modelo como parte de la fórmula matemática).

Coeficiente de writing score (β₁ = 0.917): por cada punto adicional en escritura, el modelo predice un aumento de 0.917 puntos en lectura. Es decir, hay una fuerte relación positiva entre ambas habilidades.

El t value (valor t) es el resultado de un test estadístico que sirve para evaluar si cada coeficiente (es decir, cada parámetro del modelo) es estadísticamente diferente de cero.

Si un coeficiente es igual a cero, significa que esa variable no aporta nada al modelo (no está relacionada con la variable que queremos predecir). En cambio, si es significativamente distinto de cero, entonces sí influye en la variable dependiente y vale la pena mantenerlo en el modelo.

$$t = \frac{Estimate}{Std. Error} = \frac{0.91719}{0.00906} = 101.23$$

Esto significa que el coeficiente estimado (0.917) está a más de 100 desviaciones estándar de 0.

¿Qué indica un valor t alto? Un valor t grande (positivo o negativo) indica que es muy poco probable que ese coeficiente sea cero por casualidad.

Eso se refleja en el valor p (Pr(>|t|)) que aparece al lado:

Si el valor t es alto, el valor p debería ser muy bajo → el coeficiente es significativo

Ambos valores p (< 2e-16) son extremadamente bajos, por lo que podemos afirmar con seguridad que ambos coeficientes son estadísticamente significativos. En la misma línea, los `***` nos dicen que el nivel de significacncia es menor a 0.001.

6.1.4 Residual Standard Error (RSE)

Es una medida de la desviación típica de los residuales, es decir, cuánto se equivocan nuestras predicciones en promedio. Un valor más bajo indica mejor ajuste.

Se nos indica que tenemos 988 grados de libertad porque hay 1000 observaciones y tenemos dos parámetros en el modelo (β₀ y β₁)

Residual standard error: 4.352 on 998 degrees of freedom

6.1.5 R-squared y Adjusted R-squared

R² es el Coeficiente de determinación: nos indica el porcentaje de variabilidad de la variable dependiente (reading score) que es explicada por el modelo. En este caso, el modelo explica el 91.13% de la variabilidad, lo cual es excelente para un modelo lineal.

Multiple R-squared:  0.9113,    Adjusted R-squared:  0.9112 

6.1.6 F-statistic

El F-statistic evalúa si el modelo explica una parte significativa de la variabilidad de la variable dependiente (reading score), comparandolo con un modelo que no incluye ninguna variable explicativa (sólo una constante, la media).

En este caso, el valor F es muy alto (10,250), y el valor p es extremadamente bajo (< 2.2e-16), lo cual indica que el predictor writing score aporta información relevante al modelo y no deberíamos descartarlo.

F-statistic: 1.025e+04 on 1 and 998 DF,  p-value: < 2.2e-16

6.2 Intervalos de confianza

Los elementos que hemos mencionado anteriormente pueden complementarse mediante el recurso a los intervalos de confianza.

confint(modelo)
##                     2.5 %   97.5 %
## (Intercept)     5.5107855 7.990224
## `writing score` 0.8994114 0.934970

Veamos cómo leer esto

El intervalo de confianza del 95% para la ordenada al origen (intercepto) va de 5.51 a 7.99.Esto significa que, si el writing score fuera 0 (lo cual puede no tener sentido práctico), el modelo predice que el valor promedio de la variable dependiente estaría entre 5.51 y 7.99, con un 95% de confianza.

El intervalo de confianza del 95% para el coeficiente de writing score va de 0.899 a 0.935. Esto indica que, con un 95% de confianza, el verdadero efecto poblacional de aumentar una unidad en writing score sobre la variable dependiente está entre +0.899 y +0.935.

Si el intervalo no incluye 0, el predictor se considera estadísticamente significativo.


7 Verificamos los supuestos del modelo

Siempre que realizamos un análisis de regresión debemos comprobar que se cumplan los supuestos.

Podemos usar la siguiente tabla como guía para evaluar cada supuesto y graficar nuestros datos. Es posible que un gráfico se use para más de un supuesto.

Supuesto Descripción Test estadístico Gráfico recomendado
1. Linealidad El modelo asume que existe una relación lineal entre la variable dependiente (respuesta) y las variables independiente (predictora). Coeficiente de correlación de Pearson - Gráfico de dispersión (X vs. Y)
- Residuos vs. valores ajustados
2. Homocedasticidad Se espera que los residuos tengan una varianza constante a lo largo de todos los niveles de las variables predictoras. - Breusch-Pagan Residuos vs. valores ajustados
- Residuos en orden de observación (solo si hay orden temporal).
3. Normalidad de los errores Los errores del modelo (no las variables originales) deben seguir una distribución normal con media cero. - Shapiro-Wilk - Histograma de residuos
- Q–Q plot de residuos
4. Independencia de los errores Se asume que los errores son independientes entre sí, es decir, el error asociado a una observación no debe estar relacionado con el error de otra. Durbin-Watson (solo si hay orden temporal). Ninguno específico (se evalúa que no haya patrones en residuos)

7.1 Gráfico de residuos vs. valores ajustados

Este gráfico nos muestra en el eje x los valores ajustados (predichos) por el modelo lineal, es decir, las estimaciones obtenidas a partir de la combinación de los coeficientes del modelo y los valores de las variables independientes. En el eje y se representan los residuos, es decir, las diferencias entre los valores reales observados de la variable dependiente y los valores predichos por el modelo.

Este gráfico es esencial para evaluar la validez del supuesto de homocedasticidad, que indica que los residuos deberían tener una varianza constante a lo largo del rango de valores ajustados. Si el modelo es adecuado, deberíamos observar una nube de puntos dispersos alrededor de la línea horizontal (residuo = 0), sin ningún patrón sistemático.

Patrones en forma de abanico (es decir, residuos que se expanden o se contraen a medida que aumentan los valores ajustados) indican heterocedasticidad, lo cual viola uno de los supuestos fundamentales de la regresión lineal y puede afectar la validez de los intervalos de confianza y pruebas de hipótesis. Asimismo, si los residuos se organizan en forma curva, esto puede indicar no linealidad, es decir, que la relación entre las variables no es lineal, lo que sugiere que el modelo lineal no es adecuado y podría ser necesario incorporar transformaciones o un modelo más complejo.

# calculamos residuos y valores ajustados
residuos <- resid(modelo)
valores_ajustados <- fitted(modelo)

# graficamos
ggplot(data = NULL, aes(x = valores_ajustados, y = residuos)) +
  geom_point(alpha = 0.6, color = "darkorange") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  labs(
    title = "Residuos vs. Valores Ajustados",
    x = "Valores Ajustados",
    y = "Residuos"
  ) +
  theme_minimal()

7.2 Test de Breusch-Pagan (homocedasticidad)

El test de Breusch-Pagan evalúa si los residuos del modelo de regresión tienen varianza constante (homocedasticidad) o si, por el contrario, la varianza cambia sistemáticamente en función de los valores ajustados u otras variables (heterocedasticidad).

  1. Hipótesis nula (H₀): los residuos tienen varianza constante (homocedasticidad).

  2. Hipótesis alternativa (H₁): la varianza de los residuos no es constante (heterocedasticidad).

Un p-valor bajo (< 0.05) sugiere que hay evidencia de heterocedasticidad.

#Usamos la función de lmtest
## para el test Breusch-Pagan
bptest(modelo)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 0.11724, df = 1, p-value = 0.7321

Dado que el p-value = 0.7321 es mucho mayor que 0.05, no tenemos evidencia suficiente para rechazar la hipótesis nula. Asumimos que hay homocedasticidad en el modelo.

7.3 Histograma de residuos

El histograma de residuos tiene como objetivo evaluar la distribución de los errores del modelo, o más específicamente, si los residuos se distribuyen de manera aproximadamente normal. Este supuesto de normalidad de los errores es clave para que las pruebas de significancia (como los t-tests para los coeficientes del modelo) y los intervalos de confianza funcionen correctamente.

En este gráfico, se espera que la distribución de los residuos tenga una forma de campana simétrica alrededor de cero. Si el histograma muestra asimetría (skewness) hacia la derecha o izquierda, o si hay muchas observaciones en los extremos (colas gruesas), esto sugiere desviaciones de la normalidad, lo cual puede hacer que las inferencias estadísticas asociadas al modelo sean menos confiables.

ggplot(data = NULL, aes(x = residuos)) +
  geom_histogram(bins = 30, fill = "skyblue", color = "black") +
  labs(
    title = "Histograma de Residuos",
    x = "Residuos",
    y = "Frecuencia"
  ) +
  theme_minimal()

7.4 Gráfico Q-Q

El Q-Q plot es una herramienta gráfica para evaluar si los residuos del modelo se distribuyen de forma normal. En este gráfico se comparan los cuantiles teóricos de una distribución normal (en el eje x) con los cuantiles observados de los residuos (en el eje y).

Si los residuos siguen una distribución normal, los puntos deberían alinearse aproximadamente sobre una línea recta de 45°, que representa la concordancia entre los valores observados y los esperados bajo normalidad. Las desviaciones sistemáticas de esta línea indican problemas con la distribución de los errores. Por ejemplo:

  • Si los puntos se curvan hacia arriba o hacia abajo en los extremos, esto indica colas pesadas o ligeras (leptocurtosis o platicurtosis).

  • Si los puntos se desvían de forma asimétrica hacia uno de los lados, se trata de una distribución sesgada.

El Q-Q plot es especialmente útil para detectar valores atípicos, ya que estos suelen aparecer como puntos que se alejan notoriamente de la línea de referencia. En combinación con el histograma, nos brinda una perspectiva más rica sobre el supuesto de normalidad en el modelo.

ggplot(data = NULL, aes(sample = residuos)) +
  stat_qq(color = "steelblue") +
  stat_qq_line(color = "red") +
  labs(
    title = "Q-Q Plot de los Residuos",
    x = "Cuantiles teoricos",
    y = "Cuantiles de los residuos"
  ) +
  theme_minimal()

7.5 Shapiro-Wilk

Hay algunos test que podemos usar para evaluar la normalidad de los residuos

# Calcular los residuos del modelo
residuos <- resid(modelo)

# Test de Shapiro-Wilk
shapiro.test(residuos)
## 
##  Shapiro-Wilk normality test
## 
## data:  residuos
## W = 0.99782, p-value = 0.2163

El test de Shapiro-Wilk evalúa la hipótesis nula de que los datos provienen de una distribución normal. Dado que el p-valor es mayor a 0.05, no se rechaza la hipótesis nula, lo cual indica que no hay evidencia suficiente para afirmar que los residuos se desvían de la normalidad.

8 Ejercicios de práctica

Para practicar lo que hemos visto hasta aquí, pueden realizar los siguientes ejercicios que compartiremos en el encuentro:

  • Use las categorías de la columna “gender” para separar la base en grupos. Vea cómo cambian los resultados que obtuvimos al realizar nuestros análisis con la base completa. Trate de explicar los resultados obtenidos: la recta estimada, pendiente, ordenada al origen, errores, etc.

  • Puede evaluar la relación entre la puntuación obtenida en escritura y la puntuación obtenida en matemáticas.