Laboratorio 4: El Pandas no muerde (act. II) 🐼

MDS7202: Laboratorio de Programación Científica para Ciencia de Datos

Cuerpo Docente:¶

  • Profesor: Matías Rojas y Mauricio Araneda
  • Auxiliar: Ignacio Meza D.
  • Ayudante: Rodrigo Guerra

Equipo: SUPER IMPORTANTE - notebooks sin nombre no serán revisados¶

  • Nombre de alumno 1: Johnny Godoy
  • Nombre de alumno 2:

Link de repositorio de GitHub: https://github.com/johnny-godoy/laboratorios-mds/blob/main/lab%204/laboratorio_4.ipynb¶

Temas a tratar¶

  • Manejo de datos tabulares usando pandas. En esta segunda parte se incluye adicionalmente agregaciones, concatenaciones, merge y trabajo con strings.
  • Visualizaciones interactivas de los datos con plotly.

Reglas:¶

  • Fecha de entrega: 5 de mayo (atrasos hasta el 8 de mayo, 1 punto de descuento por día)
  • Grupos de máximo 2 personas
  • Ausentes deberán realizar la actividad solos.
  • Cualquier duda fuera del horario de clases al foro. Mensajes al equipo docente serán respondidos por este medio.
  • Prohibidas las copias.
  • Pueden usar cualquer matrial del curso que estimen conveniente.

Objetivos principales del laboratorio¶

  • Aplicar y aprovechar las ventajas que nos ofrece la libreria pandas.
  • Utilizar plotly para obtener información gráfica del dataset.
  • Aplicar el Análisis Exploratorio de Datos a un caso en particular.

Nota: El laboratorio deberá ser desarrollado sin el uso indiscriminado de iteradores nativos de python (aka "for", "while"). La idea es que aprendan a exprimir al máximo las funciones optimizadas que nos entrega pandas, las cuales vale mencionar, son bastante más eficientes que los iteradores nativos sobre DataFrames.

Importamos librerias utiles 😸¶

In [1]:
import collections

from IPython.display import display
import numpy as np
import pandas as pd 
import plotly.figure_factory as ff
import plotly.graph_objects as go
import plotly.express as px

1. Rendimiento en Estudiantes 📚¶

Para este laboratorio deberán continuar el Análisis Exploratorio de datos sobre el conjunto students_grades, el cual contiene una caracterización sobre el rendimiento y otros atributos de cada alumno de la Universidad de la Cachaña .

Carga de Datos [0.5 Puntos]¶

Ya finalizado en análisis inicial, ud. y su equipo le entregaron a Don Caguayo (rector de la Universidad de la Cachaña) tanto los resultados del análisis como también la base de datos limpia y lista para ser almacenada. Dada la ingente cantidad de los datos, el equipo de TI de la universidad resolvió separar el dataset en dos bases de datos distintas (lo que según argumentan ellos, permitiría hacer agregaciones de forma más eficiente).

Gracias a la excelente labor de ud. y su equipo en el análisis previo, el rector le solicita continuar el trabajo con una nueva batería de análisis. Por este motivo, la sección de TI les entrega nuevamente los datos. Sin embargo, argumentan que dada una escazes de personal, solo le entregarán dumps (copias) de cada base de datos y su equipo deberá unir las bases de datos. Los datos se encuentran en los siguiente archivos .json: students_grades_1.json y students_grades_2.json.

Por ende, ud. y su equipo deciden que la primera tarea se centrará en cargar estos datos y unirlos.

No se preocupe por la limpieza ni transformar el tipo de datos de las columnas, ni tampoco transformar a notas chilenas, recuerde que anteriormente ya se encargo de este tema.

In [2]:
df_grades = pd.concat((pd.read_json("data/students_grades_1.json"),
                       pd.read_json("data/students_grades_2.json")))
df_grades
Out[2]:
names gender race/ethnicity parental level of education lunch test preparation course math score reading score writing score
0 Rita Courtney female group B some high school standard none 3.22 3.76 3.76
1 Charles Linstrom male group A bachelor's degree standard completed 5.80 5.68 5.86
2 Brian Young male group C some high school standard none 5.38 4.96 4.78
3 Howard Jimenez male group E some high school standard completed 5.86 5.50 5.56
4 Wayne Wilson male group B some high school standard completed 6.64 6.16 6.22
... ... ... ... ... ... ... ... ... ...
470 Richard Young male group D high school standard none 5.14 5.50 5.26
471 Wanda Russell female group B high school free/reduced completed 2.38 3.64 3.16
472 Marina Zeigler female group C bachelor's degree free/reduced completed 4.96 5.44 5.86
473 Laurie Carter female group B some high school standard completed 4.24 4.66 4.72
474 Amanda Perez female group A high school standard completed 5.08 5.80 5.56

875 rows × 9 columns

1.2- Análisis de Las Notas v2 [2 Punto por Gráficos + 0.5 respuesta]¶

Preocupado por la dificultad que representa el graficar correctamente las notas, el rector le solicita implementar distintas alternativas de visualización.

Para esto, genere un boxplot, un displot, un histograma con un gráfico marginal de caja y un histograma con el ramo como faceta de fila que permitan visualizar las notas.

Luego, responda las siguientes pregunta:

  1. ¿Existe una diferencia notable entre las notas?

  2. ¿Cuál de los gráficos mostrados cree que es adecuado para mostrarle al rector? ¿Y a los padres? ¿Y a un centro de estudios educativos? ¿Por qué?. Base sus respuestas en lo visto en la clase de visualizaciones como también en lo que usted y su equipo consideren correcto.

Hint: Para elaborar el histograma, puede que le sea de utilidad hacer un melt del DataFrame, dejando como variables los ramos y valores las notas. Por otra parte, visiten la documentación para generar los gráficos.

In [3]:
scores = [f"{subject} score" for subject in ("math", "reading", "writing")]
In [4]:
melted_frame = df_grades.melt(value_vars=scores, var_name="subject", value_name="score")
melted_frame
Out[4]:
subject score
0 math score 3.22
1 math score 5.80
2 math score 5.38
3 math score 5.86
4 math score 6.64
... ... ...
2620 writing score 5.26
2621 writing score 3.16
2622 writing score 5.86
2623 writing score 4.72
2624 writing score 5.56

2625 rows × 2 columns

Gráfico de Caja:

In [5]:
px.box(melted_frame, x="subject", y="score", color="subject")

Distplot:

In [6]:
ff.create_distplot([df_grades[score] for score in scores], scores, show_hist=False, show_rug=False)

Histograma con Boxplots:

In [7]:
px.histogram(melted_frame, x="score", color="subject", marginal='box', barmode="group", nbins=14)

Histograma con Faceta:

In [8]:
px.histogram(melted_frame, x="score", facet_row="subject", color="subject", nbins=14)

Justifique:

  1. No existe una diferencia significativa entre lectura y escritura, pero las notas de matemáticas si son más bajas.
  2. El mejor gráfico para mostrar a audiencias sin conocimientos de datos es el primer histograma, con barras agrupadas, pero sin la distribución marginal. Este permite comparar las notas en distintos ramos de manera simple, sin agobiar por información sino que destacando que las bajas notas más comúnmente son matemáticas, y que las altas no. Todo el resto de gráficos requiere más interpretación para sacar esta conclusión. Para una audiencia con más conocimiento técnico, cómo el centro de estudios educativo, se preferiría mantener la distribución marginal, pues logra apoyar a la información anterior. El resto de los gráficos contiene estrictamente menos información útil.

2. Análisis por Nivel Educacional Etnia de los Padres¶

El rector, basado en su experiencia, cree fuertemente que el nivel educacional y la etnia de los padres influyen en las notas que obtienen sus hijos. Como científicos de datos, ud. y su equipo creen que deben encontrar evidencia para confirmar o refutar la hipótesis del rector.

Para esto, deciden generar dos análisis: una tabla de resumen por una parte y gráficos de caja por otro.

1.3.4 Tabla de Resumen [1 punto]¶

Para generar la tabla de resumen:

  • [x] Calcular el promedio de las notas y guardarlo en una variable GPA (grade point average).
  • [x] Hacer una simplificación a través de un mapeo (investigar el método map()) de la variable parental level of education según la siguiente conversión:

      some high school -> school
      some college -> school
      high school -> school
      bachelor's degree -> college
      associate's degree -> college
      master's degree -> postgraduate
    
    

    Los resultados de este mapeo deben ser guardados en la columna simple parental level of education.

  • [x] Agregar según 2 niveles: race/ethnicity y simple parental level of education para obtener el promedio de las notas.
  • [x] Agregar según 2 niveles: race/ethnicity y simple parental level of education para obtener un conteo de los alumnos en cada grupo y agregarlos como una nueva fila count.
  • [x] Obtener el porcentaje de alumnos con respecto al total. Los porcentajes deben ser strings que contienen la frecuencia de cada grupo con respecto al total y deben ser terminados en '%'.

Utilizar la tabla de resultados esperados como guía para desarrollar este punto.

In [9]:
def percentage(x):
    return f"{100*x.count()/len(df_resumen):.2f} %"
In [10]:
df_resumen = df_grades.copy()
df_resumen["GPA"] = df_resumen[scores].mean(axis=1)
replacement_dict = collections.defaultdict(lambda: 'school')
replacement_dict["bachelor's degree"] = "college"
replacement_dict["associate's degree"] = "college"
replacement_dict["master's degree"] = "postgraduate"

df_resumen["simple parental level of education"] = df_resumen["parental level of education"].map(replacement_dict)
df_resumen.drop("parental level of education", axis=1, inplace=True)
grouped = df_resumen.groupby(["race/ethnicity", "simple parental level of education"])
aggregators = {score: "mean" for score in scores}
aggregators["GPA"] = "mean"
aggregators["names"] = "count"
resumen_final = grouped.agg(aggregators).round(2).reset_index()
resumen_final.rename(columns={"names": "count"}, inplace=True)
resumen_final["percentage"] = resumen_final["count"].apply(lambda x: f"{100*x/len(df_resumen):.2f} %")
resumen_final
Out[10]:
race/ethnicity simple parental level of education math score reading score writing score GPA count percentage
0 group A college 4.74 5.00 4.89 4.88 24 2.74 %
1 group A postgraduate 4.69 5.23 5.35 5.09 2 0.23 %
2 group A school 4.57 4.73 4.56 4.62 51 5.83 %
3 group B college 5.07 5.26 5.19 5.18 54 6.17 %
4 group B postgraduate 4.91 5.69 5.55 5.38 5 0.57 %
5 group B school 4.69 4.89 4.76 4.78 107 12.23 %
6 group C college 5.02 5.37 5.35 5.25 102 11.66 %
7 group C postgraduate 4.92 5.14 5.10 5.06 15 1.71 %
8 group C school 4.76 5.02 4.92 4.90 155 17.71 %
9 group D college 5.11 5.25 5.25 5.20 70 8.00 %
10 group D postgraduate 5.22 5.54 5.73 5.50 20 2.29 %
11 group D school 5.02 5.13 5.11 5.09 149 17.03 %
12 group E college 5.54 5.45 5.45 5.48 52 5.94 %
13 group E postgraduate 5.54 6.03 5.89 5.82 6 0.69 %
14 group E school 5.40 5.31 5.16 5.29 63 7.20 %

Resultado Esperado

race/ethnicity simple parental level of education math score reading score writing score GPA count percentage
0 group A college 4.74 5 4.89 4.88 24 2.74 %
1 postgraduate 4.69 5.23 5.35 5.09 2 0.23 %
2 school 4.57 4.73 4.56 4.62 51 5.83 %
3 group B college 5.07 5.26 5.19 5.18 54 6.17 %
4 postgraduate 4.91 5.69 5.55 5.38 5 0.57 %
5 school 4.69 4.89 4.76 4.78 107 12.23 %
6 group C college 5.02 5.37 5.35 5.25 102 11.66 %
7 postgraduate 4.92 5.14 5.1 5.06 15 1.71 %
8 school 4.76 5.02 4.92 4.9 155 17.71 %
9 group D college 5.11 5.25 5.25 5.2 70 8.0 %
10 postgraduate 5.22 5.54 5.73 5.5 20 2.29 %
11 school 5.02 5.13 5.11 5.09 149 17.03 %
12 group E college 5.54 5.45 5.45 5.48 52 5.94 %
13 postgraduate 5.54 6.03 5.89 5.82 6 0.69 %
14 school 5.4 5.31 5.16 5.29 63 7.2 %

Visualizaciones [0.5 Puntos]¶

Ahora, implemente un gráfico de caja en donde se muestre el GPA con respecto al nivel educacional y que la variable de color sea la etnicidad y luego comente.

In [11]:
px.box(df_resumen, x="simple parental level of education", y="GPA",
      color="race/ethnicity")
  1. ¿Hay alguna diferencia entre los grupos graficados tanto para el nivel educacional de los padres como también para la etnicidad?
  2. ¿Este gráfico permite hacer facilmente un análisis conjunto de estas dos variables de forma sencilla?

Justifique:

  1. Casi todos los grupos graficados tienen una mediana similar ~5, con pequeñas ventajas de los grupos de mayor nivel eduvativo, salvo postgrado grupo E. Sí se nota como diferencia que los grupos de mayor nivel educativo tienen una distribución menos dispersa que los de mayor nivel educativo, pero esto también puede explicarse por el número pequeño de datos que se tiene.
  2. No es sencillo de realizar el análisis conjunto más complejo que el de la pregunta anterior, dado que hay que comparar una 15 gráficos distintos con 5 puntos de interés cada uno.

3. Combinar Dataset [1 punto]¶

Mientras le notificaba por videollamada los resultados de sus descubrimientos a Don Caguayo, un exaltado practicante del area de TI entra a la reunión y les informa que ha encontrado una nueva base de datos que cuenta con las notas de dos asignaturas (en escala chilena): historia y ciencias. Para más remate, antes de huir, el practicante les cuenta que este dataframe lamentablemente contiene nuevamente los alumnos de los registros corruptos que ud. y su equipo filtraron en el análisis anterior.

El rector (evidentemente molesto por la situación) les ruega incluir estos datos (vaciados en el archivo other_grades.csv) al estudio original(students_grades.csv).

Para esto, carge el archivo other_grades.csv y busque la forma de unir ambos DataFrames, de tal manera que las columnas de history score y science score se anexen al final del DataFrame original. NO LIMPIE LOS DATOS, si no que explore los distintos tipos de merge para encontrar el mas situable para su situación (y así evitar buscar duplicados).

To-Do

  • [x] Cargar el other_grades.csv
  • [x] Unir df_grades con other_grades.csv usando outer join y explique el resultado.
  • [x] Unir df_grades con other_grades.csv usando left join y explique el resultado.
  • [x] Unir df_grades con other_grades.csv usando right join y explique el resultado.
  • [x] Unir df_grades con other_grades.csv usando inner join y explique el resultado.
  • [x] Defina cuál join es el que utilizará para generar el nuevo DataFrame.

Hint: Puede explicar los resultados del merge a través de la cantidad de filas resultantes y los valores que estas contienen.

In [12]:
other_grades = pd.read_csv("data/other_grades.csv")
other_grades
Out[12]:
names science score history score
0 Pam Otoole 57 4.0
1 Diane Olson 48 4.8
2 Sarah Flannery 72 6.1
3 Stanley Chappell 47 3.7
4 Arnold Dodson 60 5.2
... ... ... ...
995 Melanie Acevedo 79 6.3
996 Christopher Wall 62 4.0
997 Margaret Spannaus 47 4.0
998 Vickie Sellers 47 5.7
999 Kari Mitchell 53 6.2

1000 rows × 3 columns

In [13]:
def joiner(how):
    return df_grades.merge(other_grades, on="names", how=how)

joined_dfs = {how: joiner(how) for how in ("outer", "left", "right", "inner")}
for how, df in joined_dfs.items():
    print(f"{how} join")
    display(df)
outer join
names gender race/ethnicity parental level of education lunch test preparation course math score reading score writing score science score history score
0 Rita Courtney female group B some high school standard none 3.22 3.76 3.76 25 3.8
1 Charles Linstrom male group A bachelor's degree standard completed 5.80 5.68 5.86 56 5.2
2 Brian Young male group C some high school standard none 5.38 4.96 4.78 58 3.8
3 Howard Jimenez male group E some high school standard completed 5.86 5.50 5.56 72 5.0
4 Wayne Wilson male group B some high school standard completed 6.64 6.16 6.22 94 4.6
... ... ... ... ... ... ... ... ... ... ... ...
995 Leona Markow NaN NaN NaN NaN NaN NaN NaN NaN 39 3.8
996 Maxine Gulbranson NaN NaN NaN NaN NaN NaN NaN NaN 38 3.9
997 Jeane Budds NaN NaN NaN NaN NaN NaN NaN NaN 43 3.9
998 Nancy Ramos NaN NaN NaN NaN NaN NaN NaN NaN 54 6.2
999 Andrea Mcdavid NaN NaN NaN NaN NaN NaN NaN NaN 40 4.2

1000 rows × 11 columns

left join
names gender race/ethnicity parental level of education lunch test preparation course math score reading score writing score science score history score
0 Rita Courtney female group B some high school standard none 3.22 3.76 3.76 25 3.8
1 Charles Linstrom male group A bachelor's degree standard completed 5.80 5.68 5.86 56 5.2
2 Brian Young male group C some high school standard none 5.38 4.96 4.78 58 3.8
3 Howard Jimenez male group E some high school standard completed 5.86 5.50 5.56 72 5.0
4 Wayne Wilson male group B some high school standard completed 6.64 6.16 6.22 94 4.6
... ... ... ... ... ... ... ... ... ... ... ...
870 Richard Young male group D high school standard none 5.14 5.50 5.26 62 4.2
871 Wanda Russell female group B high school free/reduced completed 2.38 3.64 3.16 16 3.6
872 Marina Zeigler female group C bachelor's degree free/reduced completed 4.96 5.44 5.86 59 4.1
873 Laurie Carter female group B some high school standard completed 4.24 4.66 4.72 43 4.3
874 Amanda Perez female group A high school standard completed 5.08 5.80 5.56 61 5.3

875 rows × 11 columns

right join
names gender race/ethnicity parental level of education lunch test preparation course math score reading score writing score science score history score
0 Pam Otoole female group B bachelor's degree standard none 5.32 5.32 5.44 57 4.0
1 Diane Olson female group C some college standard completed 5.14 6.40 6.28 48 4.8
2 Sarah Flannery female group B master's degree standard none 6.40 6.70 6.58 72 6.1
3 Stanley Chappell male group A associate's degree free/reduced none 3.82 4.42 3.64 47 3.7
4 Arnold Dodson male group C some college standard none 5.56 5.68 5.50 60 5.2
... ... ... ... ... ... ... ... ... ... ... ...
995 Melanie Acevedo female group E master's degree standard completed 6.28 6.94 6.70 79 6.3
996 Christopher Wall male group C high school free/reduced none 4.72 4.30 4.30 62 4.0
997 Margaret Spannaus female group C high school free/reduced completed 4.54 5.26 4.90 47 4.0
998 Vickie Sellers female group D some college standard completed 5.08 5.68 5.62 47 5.7
999 Kari Mitchell female group D some college free/reduced none 5.62 6.16 6.16 53 6.2

1000 rows × 11 columns

inner join
names gender race/ethnicity parental level of education lunch test preparation course math score reading score writing score science score history score
0 Rita Courtney female group B some high school standard none 3.22 3.76 3.76 25 3.8
1 Charles Linstrom male group A bachelor's degree standard completed 5.80 5.68 5.86 56 5.2
2 Brian Young male group C some high school standard none 5.38 4.96 4.78 58 3.8
3 Howard Jimenez male group E some high school standard completed 5.86 5.50 5.56 72 5.0
4 Wayne Wilson male group B some high school standard completed 6.64 6.16 6.22 94 4.6
... ... ... ... ... ... ... ... ... ... ... ...
870 Richard Young male group D high school standard none 5.14 5.50 5.26 62 4.2
871 Wanda Russell female group B high school free/reduced completed 2.38 3.64 3.16 16 3.6
872 Marina Zeigler female group C bachelor's degree free/reduced completed 4.96 5.44 5.86 59 4.1
873 Laurie Carter female group B some high school standard completed 4.24 4.66 4.72 43 4.3
874 Amanda Perez female group A high school standard completed 5.08 5.80 5.56 61 5.3

875 rows × 11 columns

Justificación:

  • outer: Utiliza como llave la unión de los nombres de ambos frames, es decir, toda persona está en el frame, y una persona solamente tiene todos sus atributos definidos si es que estaba en ambas tablas.
  • left: Utiliza como llave los nombres de df_grades es decir, solamente aquellos de este frame están en el resultado, y tienen sus notas de historia/ciencia definidas si y solamente sí se encuentran en other_grades
  • right: Utiliza como llave los nombres de other_grades, es decir, solamente aquellos de este frame están en el resultado, y tienen su género/raza/etc definidos solamente si aparecen en df_grades
  • inner: Utiliza como llave los nombres que están en ambos frames, por lo cual todo atributo está definido

Cabe destacar sin embargo que todos los nombres en df_grades están en other_grades:

In [14]:
df_grades.names.isin(other_grades.names).all()
Out[14]:
True

Pero no viceversa:

In [15]:
other_grades.names.isin(df_grades.names).all()
Out[15]:
False

En particular, si tenemos que $A\subseteq B$, entonces:

  • $A\cup B = B$, es decir, el outer join coincide con el right join
  • $A\cap B = A$, es decir, el inner join coincide con el left join

2.1 Más visualizaciones [0.5 puntos]¶

Genere dos visualizaciones extras que encuentre interesantes (y no triviales) con estos datos y explique sus resultados. Agrupe los atributos que estime convenientes.

To-Do:

  • [x] Generar dos nuevas visualizaciones con los datos y explicar que están representando.

NOTA: No utilice historia ni ciencias, son notas generadas aleatoriamente.

In [16]:
px.histogram(df_resumen, x="GPA", color="gender",
             marginal='box', barmode="group", nbins=14)

Este gráfico representa la distribución de GPA según género.

In [17]:
px.box(df_resumen, x="GPA", color="test preparation course")

Este gráfico busca entender la distribución de GPA según si completaron o no la prueba de preparación. Poren imbalance de datos, se prefiere un gráfico de cajas a un histograma.

Conclusión¶

Eso ha sido todo para el lab de hoy, recuerden que el laboratorio tiene un plazo de entrega de una semana y que los días de atraso no se pueden utilizar para entregas de lab solo para tareas. Cualquier duda del laboratorio, no duden en contactarnos por mail o U-cursos.



Created in deepnote.com Created in Deepnote