Autor/a

Pamela E. Pairo

Conociendo la base de datos

Se quiere analizar los factores que pueden afectar en la performance de estudiantes en un examen.

Código
df <-read_csv("estudiantes.csv")
glimpse(df)
Rows: 60
Columns: 6
$ `Hours Studied`                    <dbl> 6, 6, 2, 9, 8, 4, 5, 3, 1, 4, 6, 3,~
$ `Previous Scores`                  <dbl> 83, 92, 90, 86, 75, 65, 80, 87, 66,~
$ `Extracurricular Activities`       <chr> "Yes", "Yes", "No", "Yes", "No", "Y~
$ `Sleep Hours`                      <dbl> 7, 4, 9, 6, 4, 5, 5, 8, 8, 6, 8, 5,~
$ `Sample Question Papers Practiced` <dbl> 1, 0, 0, 5, 1, 4, 6, 7, 0, 9, 9, 8,~
$ `Performance Index`                <dbl> 71, 83, 67, 84, 66, 45, 66, 68, 41,~

¿Hay valores faltantes?

Código
sum(is.na(df))# para saber cuantos na hay en la base de datos
[1] 0

¿Hay datos duplicados?

Código
any(duplicated(df))
[1] FALSE

Gráficos de dispersión

Código
ggpairs(df, diag = list(continuous = "blankDiag"))

Datos de entrenamiento y test

Código
set.seed(117)#setear la semilla

df_rs <- df |> 
        select(c(`Performance Index`,`Previous Scores`))

df_split <- initial_split(df_rs,
                          prop = 0.8)#para conservar la proporción de las clases

df_train <- df_split %>%
              training()

df_test <- df_split %>%
              testing()

# Número de datos en test y train
paste0("Total del dataset de entrenamiento: ", nrow(df_train))
[1] "Total del dataset de entrenamiento: 48"

Se realiza un grafico de dispersión.

Código
p<-ggplot(df_train, 
          aes(x =`Previous Scores` , y = `Performance Index`)) + 
          geom_point(aes(), colour ="deepskyblue", size=2)
p + xlab("Puntaje previo") +  ylab("Performance") 

Regresión Lineal Simple

Se plantea el modelo de Regresión

\(\LARGE Y_{i}= \beta_{0} + \beta_{1} X_1 + \varepsilon_i\)

Código
modelo1<-lm(`Performance Index` ~ `Previous Scores`, 
            data=df_train)
summary(modelo1)

Call:
lm(formula = `Performance Index` ~ `Previous Scores`, data = df_train)

Residuals:
    Min      1Q  Median      3Q     Max 
-16.194  -4.540  -1.241   6.662  13.824 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       -15.07798    4.70850  -3.202  0.00247 ** 
`Previous Scores`   1.00446    0.06635  15.139  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 7.743 on 46 degrees of freedom
Multiple R-squared:  0.8328,    Adjusted R-squared:  0.8292 
F-statistic: 229.2 on 1 and 46 DF,  p-value: < 2.2e-16

Supuestos

\(\LARGE \varepsilon_i \sim {\sf NID}(0, \sigma^{2})\)

Se calculan los residuos del modelo para chequear los supuestos.

Código
#Calculamos los residuos y los predichos
e<-resid(modelo1) # residuos
re<-rstandard(modelo1) #residuos estandarizados
pre<-predict(modelo1) #predichos
res<-cbind(df$`Previous Scores`,df$`Performance Index`,pre,e,round(re,2))
colnames(res)<-c("Puntaje anterior", "Performance", "Predichos", "Residuos", "residuos std") 
head(res)
     Puntaje anterior Performance Predichos    Residuos residuos std
[1,]               83          71  75.32301  -8.3230106        -1.10
[2,]               92          83  41.17152 -13.1715237        -1.73
[3,]               90          67  34.14034   6.8596648         0.91
[4,]               86          84  34.14034  -0.1403352        -0.02
[5,]               75          66  62.26509   8.7349109         1.14
[6,]               65          45  32.13142  12.8685758         1.71

Evaluamos el supuesto de normalidad de manera gráfica y mediante una prueba de hipótesis.

Código
#Supuestos
par(mfrow = c(1, 2))
plot(pre, re, xlab="Predichos", ylab="Residuos estandarizados",main="Grafico de dispersion de RE vs PRED" )
abline(0,0)
qqPlot(e)

[1] 15 20

Normalidad

Código
shapiro.test(e)

    Shapiro-Wilk normality test

data:  e
W = 0.97082, p-value = 0.2725

Como el p-valor= 0.27 es mayor a \(\alpha\) =0.05, se asume normalidad en los residuos.

Intervalo de Confianza

Se calcula el intervalo de confianza del 95% para la ordenada al origen y la pendiente del modelo de regresión.

Para cambiar el nivel de confianza cambiar el parámetro level

Código
confint(modelo1)#por default es del 95%
                        2.5 %    97.5 %
(Intercept)       -24.5556970 -5.600271
`Previous Scores`   0.8709023  1.138009

Se agrega la recta al gráfico y la banda de confianza.

Código
p + geom_smooth(method = "lm", 
                se = TRUE)#para mostrar la banda de confianza

Entonces, se analiza el el modelo propuesto y como el p-valor dado que es menor al valor de significancia, entonces la pendiente es significativamente distinta a cero y por ende el modelo lineal propuesto es válido.

Código
summary(modelo1)

Call:
lm(formula = `Performance Index` ~ `Previous Scores`, data = df_train)

Residuals:
    Min      1Q  Median      3Q     Max 
-16.194  -4.540  -1.241   6.662  13.824 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       -15.07798    4.70850  -3.202  0.00247 ** 
`Previous Scores`   1.00446    0.06635  15.139  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 7.743 on 46 degrees of freedom
Multiple R-squared:  0.8328,    Adjusted R-squared:  0.8292 
F-statistic: 229.2 on 1 and 46 DF,  p-value: < 2.2e-16

Por el valor obtenido de \(\ R^2= 0.83\) se deduce que el modelo propuesto explica el 86,63% de la variabilidad de los datos.

Código
#coeficiente de determinación (en summary)
summary(modelo1)$r.squared
[1] 0.8328426

Evaluamos el modelo en df_test

Código
pred_m1 <- modelo1 |>  
           predict(df_test) |> 
            bind_cols(df_test)
pred_m1
# A tibble: 12 x 3
    ...1 `Performance Index` `Previous Scores`
   <dbl>               <dbl>             <dbl>
 1  50.2                  45                65
 2  73.3                  69                88
 3  59.3                  71                74
 4  82.4                  94                97
 5  62.3                  66                77
 6  39.2                  30                54
 7  33.1                  42                48
 8  80.3                  80                95
 9  38.2                  24                53
10  70.3                  61                85
11  39.2                  31                54
12  60.3                  70                75

Cálculo del RMSE ( Root Mean Squared Error o Error Cuadrático Medio)

Código
# Evaluamos en df_test

rmse_result <- pred_m1 %>%
  metrics(truth = "Performance Index", estimate = "...1") %>%
  filter(.metric == "rmse")

# Mostrar el resultado del RMSE
print(rmse_result)
# A tibble: 1 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard        8.87

Otras métricas para evaluar modelos de regresión

Código
glance(modelo1)
# A tibble: 1 x 12
  r.squared adj.r.squared sigma statistic  p.value    df logLik   AIC   BIC
      <dbl>         <dbl> <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl> <dbl>
1     0.833         0.829  7.74      229. 1.73e-19     1  -165.  337.  342.
# i 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>

Regresión Lineal Múltiple

Se incluye la variable Hours Studied al modelo.

\(\LARGE Performance_{i}= \beta_{0} + \beta_{1} PuntajePrevio + \beta_{2} HorasEstudiadas + \varepsilon_i\)

\(\beta_{i}\) son los coeficientes de regresión parcial, ya que indican la influencia (parcial) de cada variable explicatoria sobre Y, cuando se mantiene constante la influencia de las otras variables explicatorias.

Código
set.seed(11)#setear la semilla

df_rm <- df |> 
      select(c(`Performance Index`,`Previous Scores`, `Hours Studied`))

df_split_rm <- initial_split(df_rm,
                          prop = 0.8)

df_train_rm <- df_split_rm %>%
              training()

df_test_rm <- df_split_rm %>%
              testing()

# Número de datos en test y train
paste0("Total del dataset de entrenamiento: ", nrow(df_train_rm))
[1] "Total del dataset de entrenamiento: 48"
Código
paste0("Total del dataset de testeo: ", nrow(df_test_rm))
[1] "Total del dataset de testeo: 12"
Código
modelo2<-lm(`Performance Index` ~ `Previous Scores`+ `Hours Studied`, 
            data=df_train_rm)

Supuestos

Código
#Calculamos los residuos y los predichos
e_m2<-resid(modelo2) # residuos
re_m2<-rstandard(modelo2) #residuos estandarizados
pre_m2<-predict(modelo2) #predichos
res_m2<-cbind(df$`Previous Scores`,df$`Performance Index`,pre_m2,e_m2,round(re_m2,2))
colnames(res_m2)<-c("superficie", "precio", "Predichos", "Residuos", "residuos std") 
head(res_m2)
     superficie precio Predichos   Residuos residuos std
[1,]         83     71  28.26026 -1.2602619        -0.53
[2,]         92     83  21.10176 -0.1017645        -0.04
[3,]         90     67  24.21730  0.7827037         0.33
[4,]         86     84  65.75772  0.2422801         0.10
[5,]         75     66  92.31469  1.6853117         0.72
[6,]         65     45  28.59349 -4.5934922        -1.92
Código
par(mfrow = c(1, 2))
plot(pre_m2, re_m2, xlab="Predichos", ylab="Residuos estandarizados",main="Grafico de dispersion de RE vs PRED" )
abline(0,0)
qqPlot(e_m2)

[1]  6 34

Se chequea normalidad

Código
shapiro.test(resid(modelo2))

    Shapiro-Wilk normality test

data:  resid(modelo2)
W = 0.97209, p-value = 0.305

Vemos los resultados del modelo

Código
summary(modelo2)

Call:
lm(formula = `Performance Index` ~ `Previous Scores` + `Hours Studied`, 
    data = df_train_rm)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.5935 -1.8195  0.1734  1.7944  4.5427 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       -32.45648    1.66399  -19.50   <2e-16 ***
`Previous Scores`   1.03851    0.02095   49.58   <2e-16 ***
`Hours Studied`     3.00446    0.13960   21.52   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 2.475 on 45 degrees of freedom
Multiple R-squared:  0.9845,    Adjusted R-squared:  0.9838 
F-statistic:  1426 on 2 and 45 DF,  p-value: < 2.2e-16

Evaluamos en df_test_rm

Código
pred_rm <- modelo2 |>  
           predict(df_test_rm) |> 
           bind_cols(df_test_rm)
pred_rm
# A tibble: 12 x 4
    ...1 `Performance Index` `Previous Scores` `Hours Studied`
   <dbl>               <dbl>             <dbl>           <dbl>
 1  71.8                  71                83               6
 2  27.4                  34                49               3
 3  68.4                  71                74               8
 4  58.5                  57                76               4
 5  48.2                  49                69               3
 6  65.5                  66                77               6
 7  90.3                  90                98               7
 8  25.3                  19                44               4
 9  70.6                  71                79               7
10  55.7                  56                82               1
11  55.5                  58                76               3
12  37.4                  39                47               7
Código
rmse_result_rm <- pred_rm %>%
  metrics(truth = "Performance Index", estimate = "...1") %>%
  filter(.metric == "rmse")

print(rmse_result_rm)
# A tibble: 1 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard        2.91
Código
glance(modelo2)
# A tibble: 1 x 12
  r.squared adj.r.squared sigma statistic  p.value    df logLik   AIC   BIC
      <dbl>         <dbl> <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl> <dbl>
1     0.984         0.984  2.48     1426. 2.00e-41     2  -110.  228.  236.
# i 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>

VIF (factor de inflación de la varianza o variance inflation factor)

\(\LARGE VIF= \frac{1}{1-R_{j}^{2}}\)

Mide para cada X el aumento de la varianza del coeficiente de regresión debido a la correlación entre VE. Valores superiores a 5 son considerados indicativos de colinealidad. Toma valores entre 1 e infinito. Valores superiores a 5 son considerados indicativos de colinealidad

¿Porqué importa la colinealidad?

Los coeficientes de regresión tendrán varianzas muy altas, es decir que estarán estimados con poca precisión.

Código
vif(modelo2)
`Previous Scores`   `Hours Studied` 
          1.00113           1.00113 

De acuerdo a los resultados obtenidos, el modelo propuesto es significativo y válido (p-valor < \(\alpha=0.05\)), resultando significativo ambos coeficientes(p<0.05). El 98% de la variabilidad de los datos es explicado por el modelo de regresión múltiple.

Importancia de las variables en el modelo

Código
library(vip)
importancia <- vip(modelo2)

plot(importancia)

Bonus track: Residuos parciales

Los gráficos de dispersión de Y vs cada X muestran el efecto sobre la variable respuesta (VR) de una variable explicatoria (VE) sin considerar el efecto de las otras VE. Los gráficos de residuos parciales muestran el efecto parcial sobre la VR de una VE cuando las otras VE son incluidas en el modelo y mantenidas constantes.

Código
library(faraway)
prplot(modelo2,1)

Código
grid_x1 <- seq(min(df$`Hours Studied`), max(df$`Hours Studied`))
grid_x2 <- seq(min(df$`Previous Scores`), max(df$`Previous Scores`))
grid <- expand.grid(`Hours Studied` = grid_x1, `Previous Scores` = grid_x2)
grid$y_pred <- predict(modelo2, newdata = grid)


plot_ly(df, x = ~`Hours Studied`, y = ~`Previous Scores`, z = ~`Performance Index`, type = "scatter3d", mode = "markers", name = "Datos") %>%
  add_trace(x = grid$`Hours Studied`, y = grid$`Previous Scores`, z = grid$y_pred, type = "mesh3d", opacity = 0.5, name = "Superficie de Regresión") %>%
  layout(scene = list(xaxis = list(title = 'Horas de estudio'),
                      yaxis = list(title = 'Puntaje previo'),
                      zaxis = list(title = 'Performance')),
         title = "Regresión Múltiple")
Código
sessionInfo()
R version 4.1.3 (2022-03-10)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] faraway_1.0.8      vip_0.3.2          GGally_2.1.2       yardstick_1.2.0   
 [5] workflowsets_1.0.1 workflows_1.1.3    tune_1.1.1         rsample_1.1.1     
 [9] recipes_1.0.7      parsnip_1.1.1      modeldata_1.1.0    infer_1.0.4       
[13] dials_1.2.0        scales_1.2.0       broom_1.0.5        tidymodels_1.1.0  
[17] lubridate_1.9.2    forcats_1.0.0      stringr_1.5.0      dplyr_1.1.2       
[21] purrr_1.0.1        readr_2.1.4        tidyr_1.3.0        tibble_3.2.1      
[25] tidyverse_2.0.0    car_3.1-0          carData_3.0-5      MASS_7.3-55       
[29] plotly_4.10.1      ggplot2_3.4.3     

loaded via a namespace (and not attached):
 [1] minqa_1.2.4        colorspace_2.0-3   class_7.3-20       rstudioapi_0.15.0 
 [5] listenv_0.8.0      furrr_0.3.0        farver_2.1.1       bit64_4.0.5       
 [9] prodlim_2019.11.13 fansi_1.0.3        codetools_0.2-18   splines_4.1.3     
[13] knitr_1.39         jsonlite_1.8.4     nloptr_2.0.3       compiler_4.1.3    
[17] httr_1.4.7         backports_1.4.1    Matrix_1.4-0       fastmap_1.1.0     
[21] lazyeval_0.2.2     cli_3.6.1          htmltools_0.5.2    tools_4.1.3       
[25] gtable_0.3.1       glue_1.6.2         Rcpp_1.0.8.3       DiceDesign_1.9    
[29] vctrs_0.6.1        nlme_3.1-155       crosstalk_1.2.0    iterators_1.0.14  
[33] timeDate_3043.102  gower_1.0.0        xfun_0.31          globals_0.15.0    
[37] lme4_1.1-29        timechange_0.2.0   lifecycle_1.0.3    future_1.26.1     
[41] ipred_0.9-13       vroom_1.6.1        hms_1.1.3          parallel_4.1.3    
[45] RColorBrewer_1.1-3 yaml_2.3.5         gridExtra_2.3      rpart_4.1.16      
[49] reshape_0.8.9      stringi_1.7.6      foreach_1.5.2      lhs_1.1.5         
[53] boot_1.3-28        hardhat_1.3.0      lava_1.6.10        rlang_1.1.0       
[57] pkgconfig_2.0.3    evaluate_0.15      lattice_0.20-45    htmlwidgets_1.5.4 
[61] labeling_0.4.2     bit_4.0.4          tidyselect_1.2.0   parallelly_1.32.0 
[65] plyr_1.8.7         magrittr_2.0.3     R6_2.5.1           generics_0.1.2    
[69] pillar_1.9.0       withr_2.5.0        mgcv_1.8-39        survival_3.2-13   
[73] abind_1.4-5        nnet_7.3-17        future.apply_1.9.0 crayon_1.5.1      
[77] utf8_1.2.2         tzdb_0.3.0         rmarkdown_2.15     grid_4.1.3        
[81] data.table_1.14.2  digest_0.6.29      GPfit_1.0-8        munsell_0.5.0     
[85] viridisLite_0.4.1