1. Introducción

Este análisis explora la influencia de los hábitos de estudiantes en su rendimiento académico. El dataset cuenta con 1000 registros y más de 15 variables, incluyendo las horas de estudio, uso de redes sociales, cantidad de horas que ven Netflix, actividad física, edad, dieta y salud mental

2. Datos

2.1 Origen

Los datos fueron tomados de base abiertas de Kaggle: Analisis de Rendimiento Académico basado en Hábitos

2.2 Variables

  • student_id: Identificador Estudiante.
  • age: Edad
  • gender: Genero (masculino/femenino)
  • study_hours_per_day: Cantidad de horas de estudio por Día.
  • mental_health_rating: Estado Mental
  • social_media_hours: Horas de uso de Redes sociales
  • netflix_hours: Horas de Netflix
  • part_time_job: Marca de Trabajo a medio tiempo (True/False)
  • attendance_percentage: Asistencia en Días.
  • sleep_hours: Horas de Sueño
  • diet_quality: Calidad de la Dieta
  • exercise_frequency: Frencuencia de actividad física
  • parental_education_level: Nivel Académico de los padres
  • internet_quality: Calidad de Internet
  • extracurricular_participation: Indicador de actividades extra (True/False)
  • exam_score:Calificación

2.3 Recursos

#reticulate::py_install(“polars”)

#reticulate::py_install(“os”)

#reticulate::py_install(“pandas”)

#reticulate::py_install(“numpy”)

#reticulate::py_install(“matplotlib”)

#reticulate::py_install(“seaborn”)

#reticulate::py_install(“scikit-learn”)

#reticulate::py_install(“pyarrow”)

library(reticulate) py_config()

3. Metodología

3.1 Depuracion de Base y análisis exploratorio

  • Modificación nombres de variables
  • Creación de nuevas variables para analisis.
  • Análisis de valores de variables -describe de pandas.

3.2 PCA

  • Análisis de correlaciones.
  • Análisis de Variables.
  • Selección de Componentes principales y creación de conjuntos de prueba y entrenamiento.

3.3 Regresión Lógistica

  • Transformacion de variables categóricas para poder usarlas en el análisis.
  • Normalizacion de variables y gráfico de variables para analizar la distribución de valores.
  • Entrenamiento de modelo usando PCA y con las variables de la base.
  • Generación de Función de Regresión

3.4 Visualizacion de resultados.

  • Gráfico de funciones de Regresion usando PCA y sin usar PCA
  • Gráfico de componentes y variables PCA utilizadas e incidencia en la prediccion de la variables objetivo.
  • Gráfico de variables sin usar PCA utilizadas e incidencia en la prediccion de la variables objetivo.

4. Consideraciones finales

Analizando los resultados, vemos que existe una incidencia de los hábitos en las notas obtenidas. Principalmente, de la cantidad de horas es estudio, estado mental, actividad fisica y horas de sueño, así como el uso de redes sociales y Netflix u otras plataformas que afectan en este caso de forma inversa al resultado final. Luego de aplicar estrategias de seleccion de variables, vemos que los resultados no tienen una variación grande, quizás porque la base no cuenta con un número demasiado grande de variables o por la cantidad de registros del Dataset. En los siguientes capítulos se definen los pasos seguidos para poder realizar la carga, limpieza, análisis y modelado a partir de los datos de prueba.

5. Lectura y Depuración de Datos

import polars as pl
import os
import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LinearRegression


path = "C:/Users/Asus/Downloads/BIG DATA SUDAMERIS 1/student_habits_performance.csv"
columns=['student_id','age','gender','study_hours_per_day','social_media_hours',
'netflix_hours','part_time_job','attendance_percentage','sleep_hours','diet_quality',
'exercise_frequency','parental_education_level','internet_quality','mental_health_rating',
'extracurricular_participation','exam_score']
         
schemas_overrides = {"student_id":pl.Utf8,
"age":pl.Int32,
"gender":pl.Utf8,
"study_hours_per_day":pl.Float64,
"social_media_hours":pl.Float64,
"netflix_hours":pl.Float64,
"part_time_job":pl.Utf8,
"attendance_percentage":pl.Float64,
"sleep_hours":pl.Float64,
"diet_quality":pl.Utf8,
"exercise_frequency":pl.Int32,
"parental_education_level":pl.Utf8,
"internet_quality":pl.Utf8,
"mental_health_rating":pl.Int32,
"extracurricular_participation":pl.Utf8,
"exam_score":pl.Float64
}
dfs =  pl.read_csv(path,
        columns=columns,
        dtypes=schemas_overrides,
        ignore_errors=True,
        null_values=["1154522A"],
        encoding="latin1",
        )                           

df=dfs.to_pandas()
print(df.head())
##   student_id  age  ... extracurricular_participation  exam_score
## 0      S1000   23  ...                           Yes        56.2
## 1      S1001   20  ...                            No       100.0
## 2      S1002   21  ...                            No        34.3
## 3      S1003   23  ...                           Yes        26.8
## 4      S1004   19  ...                            No        66.4
## 
## [5 rows x 16 columns]
#cambio de nombre de columnas y creacion de nuevas columnas
df_nombres =['id','Edad','Genero','HorasEstudioDia','HorasRedes','HorasNetflix',
'TrabajoMedioTiempo','PorcentajeAsistencia',
             'HorasSuenho','CalidadDieta','FrequenciaEjercicio',
             'NivelEducativoPadres','CalidadInternet','RatingEstadoMental',
             'ActividadExtra','Notas']
df.columns=df_nombres

# Definir los rangos de edad
bins = [0, 18, 21, 24]  # Los límites de cada rango
labels = ['0-18', '19-21', '22-24']  # Etiquetas de cada rango

# Definir los rangos de edad
bins_s = [0,4,7,24 ]  # Los límites de cada rango
labels_s = ['Bajo', 'Moderado', 'Alto']  # Etiquetas de cada rango

# Crear una nueva columna 'rango_edad' con los rangos definidos
df['RangoEdad'] = pd.cut(df['Edad'], bins=bins, labels=labels, right=False)
df['RangoSuenho'] = pd.cut(df['HorasSuenho'], bins=bins_s, labels=labels_s, right=False)

# Definir los rangos de las calificaciones
topes_nota = [0, 50, 70, 85, 100]  # Los límites de cada rango
Etiquetas = ['Bajo', 'Promedio', 'Bueno', 'Excelente']  # Etiquetas de cada rango

# Crear una nueva columna 'rango_calificacion' con los rangos definidos
df['RangoNotas'] = pd.cut(df['Notas'], bins=topes_nota, labels=Etiquetas, right=True)

df
##         id  Edad  Genero  ...  RangoEdad  RangoSuenho  RangoNotas
## 0    S1000    23  Female  ...      22-24         Alto    Promedio
## 1    S1001    20  Female  ...      19-21     Moderado   Excelente
## 2    S1002    21    Male  ...      22-24         Alto        Bajo
## 3    S1003    23  Female  ...      22-24         Alto        Bajo
## 4    S1004    19  Female  ...      19-21     Moderado    Promedio
## ..     ...   ...     ...  ...        ...          ...         ...
## 995  S1995    21  Female  ...      22-24         Alto       Bueno
## 996  S1996    17  Female  ...       0-18     Moderado    Promedio
## 997  S1997    20    Male  ...      19-21     Moderado    Promedio
## 998  S1998    24    Male  ...        NaN         Alto    Promedio
## 999  S1999    19  Female  ...      19-21         Alto       Bueno
## 
## [1000 rows x 19 columns]
#ANALISIS DATAFRAME
# Obtener el número total de filas del DataFrame
num_filas = df.shape[0]

# Imprimir el número de filas
print(f"Total de filas: {num_filas}")
#imprimir columnas(f)
## Total de filas: 1000
print(f"Columnas: {df.columns}")
## Columnas: Index(['id', 'Edad', 'Genero', 'HorasEstudioDia', 'HorasRedes', 'HorasNetflix',
##        'TrabajoMedioTiempo', 'PorcentajeAsistencia', 'HorasSuenho',
##        'CalidadDieta', 'FrequenciaEjercicio', 'NivelEducativoPadres',
##        'CalidadInternet', 'RatingEstadoMental', 'ActividadExtra', 'Notas',
##        'RangoEdad', 'RangoSuenho', 'RangoNotas'],
##       dtype='object')

6. Preparacion de datos para modelado

#Separamos la variables independientes de la variable dependiente
x=df.drop(columns=['Notas','id'])
y= df['Notas']

#tratamiento especial de columnas categoricas
categoricas = [
    'Genero', 'TrabajoMedioTiempo', 'CalidadDieta',
    'NivelEducativoPadres', 'CalidadInternet', 'ActividadExtra','RangoEdad', 'RangoSuenho'
]

numericas = ['Edad', 'HorasEstudioDia', 'HorasRedes', 'HorasNetflix',
    'PorcentajeAsistencia', 'HorasSuenho', 'FrequenciaEjercicio',
    'RatingEstadoMental']

x_codificadas = pd.get_dummies(x, columns=categoricas, drop_first=True) 

# Normalizar numéricas
normalizar = StandardScaler()
x_codificadas[numericas] = normalizar.fit_transform(x_codificadas[numericas])
x_codificadas = x_codificadas.drop(columns=['RangoNotas'])


#reservamos 20% de los datos para prueba
X_train, X_test, y_train, y_test = train_test_split(x_codificadas, y, test_size=0.2, 
random_state=3)
x_codificadas.columns
## Index(['Edad', 'HorasEstudioDia', 'HorasRedes', 'HorasNetflix',
##        'PorcentajeAsistencia', 'HorasSuenho', 'FrequenciaEjercicio',
##        'RatingEstadoMental', 'Genero_Male', 'Genero_Other',
##        'TrabajoMedioTiempo_Yes', 'CalidadDieta_Good', 'CalidadDieta_Poor',
##        'NivelEducativoPadres_High School', 'NivelEducativoPadres_Master',
##        'NivelEducativoPadres_None', 'CalidadInternet_Good',
##        'CalidadInternet_Poor', 'ActividadExtra_Yes', 'RangoEdad_19-21',
##        'RangoEdad_22-24', 'RangoSuenho_Moderado', 'RangoSuenho_Alto'],
##       dtype='object')
x_codificadas.head()
##        Edad  HorasEstudioDia  ...  RangoSuenho_Moderado  RangoSuenho_Alto
## 0  1.084551        -2.418068  ...                     0                 1
## 1 -0.215870         2.281707  ...                     1                 0
## 2  0.217604        -1.464491  ...                     0                 1
## 3  1.084551        -1.736942  ...                     0                 1
## 4 -0.649344         0.987566  ...                     1                 0
## 
## [5 rows x 23 columns]

6.1 Visualización Datasets Normalizados

#Graficamos el dataset normalizado y el dataset original para poder ver la diferencia.
#Tomamos como ejemplo 2 variables categoricas:CalidadDieta y genero y dos variables 
#numericas: HorasEstudioDia, HorasSuenho

var1 = 'HorasEstudioDia'
var2 = 'HorasSuenho'
var3 = 'Genero'
var4 = 'CalidadDieta'

#para categoricas se elige alguna de las columnas creadas: dummies crea una columna 
#por cada elemento de la variable categórica
var5='Genero_Male'
var6='CalidadDieta_Moderado'

#Creamos los gráficos
plt.figure(figsize=(12, 10))

# var1 1: Horas de estudio
plt.subplot(2, 2, 1)
sns.kdeplot(df[var1], label='Original', fill=True, color='yellow')
sns.kdeplot(x_codificadas[var1], label='Normalizada', fill=True, color='red')
plt.title(f'Distribución: {var1}')
plt.legend()

# var2 : Horas de suenho
plt.subplot(2, 2, 2)
sns.kdeplot(df[var2], label='Original', fill=True, color='yellow')
sns.kdeplot(x_codificadas[var2], label='Normalizada', fill=True, color='red')
plt.title(f'Distribución: {var2}')
plt.legend()

# var3 : Genero
plt.subplot(2, 2, 3)
sns.countplot(x=df[var3], color='skyblue', label='Original')
plt.title(f'Categoría original: {var3}')
plt.legend(['Original'])

# var3 Genero Masculino (normalizada)
plt.subplot(2, 2, 4)
sns.histplot(x_codificadas[var5], bins=3, color='orange', label='Normalizada')
plt.title(f'Categoría Normalizada: {var5}')
plt.legend()

plt.tight_layout()
plt.show()

# 7. Análisis PCA

#correlacion entre variables
datos = x_codificadas
datos['Notas'] = df['Notas']
variables_numericas = datos.select_dtypes(include=['float64', 'int64','boolean']).columns
datos[variables_numericas].corr()['Notas'].sort_values(ascending=False)
## Notas                   1.000000
## HorasEstudioDia         0.825419
## RatingEstadoMental      0.321523
## FrequenciaEjercicio     0.160107
## HorasSuenho             0.121683
## PorcentajeAsistencia    0.089836
## Edad                   -0.008907
## HorasRedes             -0.166733
## HorasNetflix           -0.171779
## Name: Notas, dtype: float64

El analisis de correlacion indica que la variable que más afecta es la cantidad de estudio, lo que suena bastante lógico y esperable, al correr PCA, veremos que variables no son necesarias.

#  PCA para reducir dimensionalidad
pca = PCA(n_components=0.95)  # Mantener 95% de certeza
X_train_pca = pca.fit_transform(X_train)
X_test_pca = pca.transform(X_test)

print(f"componentes PCA retenidos: {pca.n_components_}")
## componentes PCA retenidos: 16

7.1 Análisis de Componentes PCA

# %Varianza
print("Porcentaje de varianza por cada componente:")
## Porcentaje de varianza por cada componente:
print(pca.explained_variance_ratio_)

# Ver el total de varianza explicada por los primeros n componentes
## [0.12020004 0.10804882 0.10460308 0.0944171  0.09132323 0.08897602
##  0.08728345 0.08518392 0.02704465 0.02561843 0.02414548 0.02272975
##  0.02104595 0.01992384 0.01591062 0.01451805]
print(f"Varianza total explicada por {pca.n_components_} componentes: {sum(pca.explained_variance_ratio_)}")

#  (contribución de cada variable original a cada componente)
## Varianza total explicada por 16 componentes: 0.9509724248354532
Contrib = pca.components_

contrib_df = pd.DataFrame(Contrib, columns=X_train.columns, 
index=[f'Componente {i+1}' for i in range(Contrib.shape[0])])

# Ver las primeras 5 filas de contribucion
print("\Contribucion de cada componente:")
## \Contribucion de cada componente:
print(contrib_df)
##                    Edad  ...  RangoSuenho_Alto
## Componente 1   0.008659  ...          0.365727
## Componente 2  -0.872469  ...         -0.008058
## Componente 3   0.072728  ...         -0.004286
## Componente 4  -0.012289  ...         -0.034014
## Componente 5   0.143870  ...         -0.041686
## Componente 6  -0.177793  ...          0.023618
## Componente 7   0.208449  ...         -0.018357
## Componente 8  -0.007512  ...          0.048199
## Componente 9  -0.002963  ...         -0.047505
## Componente 10  0.091126  ...         -0.015436
## Componente 11  0.000124  ...          0.025726
## Componente 12 -0.084212  ...          0.036039
## Componente 13 -0.344650  ...          0.008301
## Componente 14 -0.002706  ...          0.034196
## Componente 15 -0.033082  ...         -0.196302
## Componente 16  0.004312  ...          0.463538
## 
## [16 rows x 23 columns]

8. Entrenamiento del modelo de regresion con componentes PCA

# Regresion Linear sobre PCA:
model = LinearRegression()
model.fit(X_train_pca, y_train)

# Ver el intercepto
## LinearRegression()
print(f"Intercepto (b0): {model.intercept_:.3f}")

# Ver los coeficientes para cada componente PCA
## Intercepto (b0): 69.741
for i, coef in enumerate(model.coef_):
    print(f"Coeficiente del Componente {i+1} (b{i+1}): {coef:.3f}")

#EN TERMINOS DE VARIABLES Y NO COMPONENTES: 
## Coeficiente del Componente 1 (b1): 1.211
## Coeficiente del Componente 2 (b2): 0.342
## Coeficiente del Componente 3 (b3): 3.658
## Coeficiente del Componente 4 (b4): 5.553
## Coeficiente del Componente 5 (b5): -4.645
## Coeficiente del Componente 6 (b6): -7.902
## Coeficiente del Componente 7 (b7): -2.315
## Coeficiente del Componente 8 (b8): 11.281
## Coeficiente del Componente 9 (b9): 0.822
## Coeficiente del Componente 10 (b10): -0.390
## Coeficiente del Componente 11 (b11): -0.597
## Coeficiente del Componente 12 (b12): 0.454
## Coeficiente del Componente 13 (b13): -0.342
## Coeficiente del Componente 14 (b14): 0.261
## Coeficiente del Componente 15 (b15): 0.841
## Coeficiente del Componente 16 (b16): -0.545
cargas = pca.components_

# Paso 2: Coeficientes del modelo en el espacio PCA
coefs_modelo = model.coef_

# Paso 3: Multiplicar coeficientes del modelo por las cargas del PCA
efecto_original = np.dot(coefs_modelo, cargas)

# Mostrar en DataFrame
df_efecto = pd.DataFrame({
    'Variable': X_train.columns,
    'Peso_aproximado': efecto_original
}).sort_values(by='Peso_aproximado', key=abs, ascending=False)

print(df_efecto)
##                             Variable  Peso_aproximado
## 1                    HorasEstudioDia        14.186778
## 7                 RatingEstadoMental         5.584123
## 2                         HorasRedes        -3.017093
## 6                FrequenciaEjercicio         2.831163
## 3                       HorasNetflix        -2.571149
## 5                        HorasSuenho         2.370670
## 4               PorcentajeAsistencia         1.399506
## 11                 CalidadDieta_Good        -0.739657
## 12                 CalidadDieta_Poor         0.434721
## 22                  RangoSuenho_Alto         0.385454
## 16              CalidadInternet_Good        -0.346018
## 20                   RangoEdad_22-24        -0.276241
## 17              CalidadInternet_Poor        -0.251367
## 14       NivelEducativoPadres_Master        -0.211696
## 8                        Genero_Male         0.179967
## 15         NivelEducativoPadres_None         0.154855
## 19                   RangoEdad_19-21        -0.109985
## 0                               Edad         0.089688
## 21              RangoSuenho_Moderado        -0.070677
## 9                       Genero_Other         0.067837
## 18                ActividadExtra_Yes         0.055044
## 13  NivelEducativoPadres_High School        -0.054609
## 10            TrabajoMedioTiempo_Yes         0.054579
plt.figure(figsize=(10,6))
sns.barplot(x=df_efecto['Peso_aproximado'], y=df_efecto['Variable'], orient='h')
plt.title('Coeficientes del Modelo de Regresión Lineal')
plt.xlabel('Peso (coeficiente)')
plt.ylabel('Variable')
plt.grid(True)
plt.tight_layout()
plt.show()

score = model.score(X_test_pca, y_test)
print(f"R^2 en test: {score:.3f}")
## R^2 en test: 0.896

Se verifica, a partir del calculo de los interceptos, y la descomposicion de los componentes, cuales son las variables con mayor incidencia en la variable Objetivo, horas de suenho y estado mental serían las que se asocian de forma directa con la calificacion, mientras que la cantidad de horas en redes sociales o en Netflix, se asocia de forma inversa a la calificación final.

8.1 Gráfico Predicciones vs. Valores - PCA

# predicciones sobre el conjunto de prueba
y_pred = model.predict(X_test_pca)

#DataFrame para comparar
df_comparacion = pd.DataFrame({
    'Real': y_test.values,
    'Predicho': y_pred
})

# 3. Gráfico de dispersión
plt.figure(figsize=(8,6))
sns.scatterplot(data=df_comparacion, x='Real', y='Predicho', alpha=0.6)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--',
label='Perfecta Predicción')
plt.title('Comparación: Notas reales vs Predichas')
plt.xlabel('Notas reales')
plt.ylabel('Notas predichas')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

9. Entrenamiento del modelo de regresion con variables originales, normalizadas

# Regresion Linear sobre valores:
model_sin_pca = LinearRegression()
model_sin_pca.fit(X_train, y_train)

# Ver el intercepto
## LinearRegression()
print(f"Intercepto (b0): {model_sin_pca.intercept_:.3f}")

# Ver los coeficientes para cada componente PCA
## Intercepto (b0): 69.764
for i, coef in enumerate(model_sin_pca.coef_):
    print(f"Coeficiente de las variable {X_train.columns[i]} (b{i+1}): {coef:.3f}")


# Crear un DataFrame con los coeficientes y las variables
## Coeficiente de las variable Edad (b1): 0.091
## Coeficiente de las variable HorasEstudioDia (b2): 14.188
## Coeficiente de las variable HorasRedes (b3): -3.024
## Coeficiente de las variable HorasNetflix (b4): -2.584
## Coeficiente de las variable PorcentajeAsistencia (b5): 1.436
## Coeficiente de las variable HorasSuenho (b6): 2.329
## Coeficiente de las variable FrequenciaEjercicio (b7): 2.855
## Coeficiente de las variable RatingEstadoMental (b8): 5.620
## Coeficiente de las variable Genero_Male (b9): 0.209
## Coeficiente de las variable Genero_Other (b10): 0.425
## Coeficiente de las variable TrabajoMedioTiempo_Yes (b11): 0.143
## Coeficiente de las variable CalidadDieta_Good (b12): -1.179
## Coeficiente de las variable CalidadDieta_Poor (b13): -0.289
## Coeficiente de las variable NivelEducativoPadres_High School (b14): -0.123
## Coeficiente de las variable NivelEducativoPadres_Master (b15): 0.123
## Coeficiente de las variable NivelEducativoPadres_None (b16): -0.699
## Coeficiente de las variable CalidadInternet_Good (b17): -0.261
## Coeficiente de las variable CalidadInternet_Poor (b18): 0.059
## Coeficiente de las variable ActividadExtra_Yes (b19): 0.233
## Coeficiente de las variable RangoEdad_19-21 (b20): 0.261
## Coeficiente de las variable RangoEdad_22-24 (b21): 0.075
## Coeficiente de las variable RangoSuenho_Moderado (b22): 0.145
## Coeficiente de las variable RangoSuenho_Alto (b23): 0.677
coef_df = pd.DataFrame({
    'Variable': X_train.columns,
    'Peso (coeficiente)': model_sin_pca.coef_
}).sort_values(by='Peso (coeficiente)', key=abs, ascending=False)

# Gráfico  de los coeficientes
# Mostrar las variables más importantes
print(coef_df)
##                             Variable  Peso (coeficiente)
## 1                    HorasEstudioDia           14.188363
## 7                 RatingEstadoMental            5.619587
## 2                         HorasRedes           -3.023690
## 6                FrequenciaEjercicio            2.854931
## 3                       HorasNetflix           -2.584230
## 5                        HorasSuenho            2.328735
## 4               PorcentajeAsistencia            1.435855
## 11                 CalidadDieta_Good           -1.178996
## 15         NivelEducativoPadres_None           -0.699314
## 22                  RangoSuenho_Alto            0.677236
## 9                       Genero_Other            0.425014
## 12                 CalidadDieta_Poor           -0.289265
## 16              CalidadInternet_Good           -0.261004
## 19                   RangoEdad_19-21            0.260813
## 18                ActividadExtra_Yes            0.233012
## 8                        Genero_Male            0.209348
## 21              RangoSuenho_Moderado            0.145118
## 10            TrabajoMedioTiempo_Yes            0.143227
## 13  NivelEducativoPadres_High School           -0.123233
## 14       NivelEducativoPadres_Master            0.122895
## 0                               Edad            0.091251
## 20                   RangoEdad_22-24            0.074681
## 17              CalidadInternet_Poor            0.059092
plt.figure(figsize=(10,6))
sns.barplot(x=coef_df['Peso (coeficiente)'], y=coef_df['Variable'], orient='h')
plt.title('Coeficientes del Modelo de Regresión Lineal')
plt.xlabel('Peso (coeficiente)')
plt.ylabel('Variable')
plt.grid(True)
plt.tight_layout()
plt.show()

score = model_sin_pca.score(X_test, y_test)
print(f"R^2 en test: {score:.3f}")
## R^2 en test: 0.894

Verificando los resultados, vemos que el uso de pca, en este caso no alteró el resultado final obtenido usando las variables originales.

9.1 Gráfico Predicciones vs. Valores - PCA

# predicciones sobre el conjunto de prueba
y_pred = model_sin_pca.predict(X_test)

#DataFrame para comparar
df_comparacion = pd.DataFrame({
    'Real': y_test.values,
    'Predicho': y_pred
})

# 3. Gráfico de dispersión
plt.figure(figsize=(8,6))
sns.scatterplot(data=df_comparacion, x='Real', y='Predicho', alpha=0.6)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', 
label='Perfecta Predicción')
plt.title('Comparación: Notas reales vs Predichas')
plt.xlabel('Notas reales')
plt.ylabel('Notas predichas')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()