Data Science II-A: Estadística descriptiva unidimensional. Tablas de frecuencia y gráficos de distribución

Histograma de notas optimo, con curva de densidad

Felipe Maggi

Lenguaje de programación: Python

Siguiendo con la serie de artículos dedicados a la Ciencia de Datos, en la cual ya hemos publicado el capítulo 1 (Data Science I: población, muestra, experimentos y tipos de variables), vamos a tratar ahora la estadística descriptiva unidimensional.

De nuevo nos guiaremos, en cuanto a estructura, por lo expuesto en material de Máster de Big Data y Data Science de la Universidad de Barcelona, cuya autora es Dolores Lorente porque, desde nuestro punto de vista, fasea la materia de forma adecuada.

Las conceptos estadísticos son de dominio general, pero cuando tengamos que recurrir a una forma concreta de plantear las cosas, haremos uso, mayoritariamente, de las definiciones expuestas en el libro Estadística general: lo esencial, de Johnson & Kuby.

También conocida como análisis descriptivo de datos de una sola variable, la estadística descriptiva unidimensional es la rama de la estadística que «se centra en el análisis de una única característica o cualidad del individuo» (Lorente), entre otras cosas, la estadística unidimensional estudia:

  • las distribuciones de frecuencias,
  • las medidas de tendencia central,
  • las medidas de dispersión,
  • y la representación gráfica de las variables, tanto cualitativas como cuantitativas.

Distribuciones de frecuencias

Una distribución de frecuencias es una lista en forma de tabla que enlaza los valores que adopta una variable, con el número de veces que aparecen dichos valores en el conjunto de datos analizado.

Para trabajar con ejemplos, vamos a rescatar el conjunto de datos relativos a las notas obtenidas en los exámenes de matemáticas por los estudiantes de segundo de bachillerato del instituto Luis Vives de Valencia, en el año académico 2024-2025, que utilizamos en el primer artículo de esta serie.

IMPORTANTE: No son las notas de verdad, si no un conjunto de datos generados con fines de ejemplo.


import pandas as pd
import numpy as np
import random
import string

# Establecer semillas para la reproducibilidad
np.random.seed(42)
random.seed(42)

# Función para generar id_estudiante alfanuméricos aleatorios
def generar_id_estudiante(n):
    ids = []
    for _ in range(n):
        id_estudiante = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
        ids.append(id_estudiante)
    return ids

# Generar datos
num_estudiantes_por_grupo = 40
total_estudiantes = num_estudiantes_por_grupo * 4
num_notas = 10

ids = generar_id_estudiante(total_estudiantes)
asignatura = 'Matemáticas'
temas = [f'Tema {i+1}' for i in range(num_notas)]
fecha_inicio = pd.Timestamp('2024-09-01')

# Función para generar tiempo de estudio con correlación positiva con notas
def generar_tiempo_estudio(notas, media=270, sd=60, correlacion=0.75):
    ruido = np.random.normal(0, sd, len(notas))
    tiempo_estudio = media + correlacion * (notas - np.mean(notas)) + ruido
    return tiempo_estudio.clip(60, 480).astype(int)

# Generar las notas, fechas y otras columnas
data = {
    'id_estudiante': [],
    'asignatura': [],
    'tema': [],
    'fecha': [],
    'nota': [],
    'calificacion': [],
    'estatura': [],
    'sexo': [],
    'tiempo_estudio': [],
    'grupo': []
}

# Grupos disponibles
grupos = ['A', 'B', 'C', 'D']
num_grupos = len(grupos)

# Asignar aleatoriamente los estudiantes a los grupos
grupo_asignado = np.repeat(grupos, num_estudiantes_por_grupo)
random.shuffle(grupo_asignado)

for idx, id_estudiante in enumerate(ids):
    fechas = [fecha_inicio + pd.DateOffset(weeks=i*4) for i in range(num_notas)]
    notas = np.random.normal(6.5, 1, num_notas).clip(0, 10)
    estatura = np.random.uniform(150, 190, num_notas)
    sexo = random.choice(['H', 'M'])

    tiempo_estudio = generar_tiempo_estudio(notas)

    for i in range(num_notas):
        data['id_estudiante'].append(id_estudiante)
        data['asignatura'].append(asignatura)
        data['tema'].append(temas[i])
        data['fecha'].append(fechas[i])
        data['nota'].append(round(notas[i], 2))

        # Asignar calificación basada en la nota
        if 9 <= notas[i] <= 10:
            calificacion = 'sobresaliente'
        elif 7 <= notas[i] < 9:
            calificacion = 'notable'
        elif 5 <= notas[i] < 7:
            calificacion = 'aprobado'
        else:
            calificacion = 'suspenso'

        data['calificacion'].append(calificacion)
        data['estatura'].append(estatura[i])
        data['sexo'].append(sexo)
        data['tiempo_estudio'].append(tiempo_estudio[i])
        data['grupo'].append(grupo_asignado[idx])

# Crear el DataFrame
df = pd.DataFrame(data)

# Añadir la columna "aprobado"
df['aprobado'] = df['calificacion'].apply(lambda x: 0 if x == 'suspenso' else 1)

# Mostrar el DataFrame
print(df)

     id_estudiante   asignatura     tema      fecha  nota calificacion  \
0         XAJI0Y6D  Matemáticas   Tema 1 2024-09-01  7.00     aprobado   
1         XAJI0Y6D  Matemáticas   Tema 2 2024-09-29  6.36     aprobado   
2         XAJI0Y6D  Matemáticas   Tema 3 2024-10-27  7.15      notable   
3         XAJI0Y6D  Matemáticas   Tema 4 2024-11-24  8.02      notable   
4         XAJI0Y6D  Matemáticas   Tema 5 2024-12-22  6.27     aprobado   
...            ...          ...      ...        ...   ...          ...   
1595      FNKOMV2X  Matemáticas   Tema 6 2025-01-19  4.28     suspenso   
1596      FNKOMV2X  Matemáticas   Tema 7 2025-02-16  7.87      notable   
1597      FNKOMV2X  Matemáticas   Tema 8 2025-03-16  5.41     aprobado   
1598      FNKOMV2X  Matemáticas   Tema 9 2025-04-13  7.76      notable   
1599      FNKOMV2X  Matemáticas  Tema 10 2025-05-11  7.45      notable   

        estatura sexo  tiempo_estudio grupo  aprobado  
0     157.272999    H             357     C         1  
1     157.336180    H             256     C         1  
2     162.169690    H             274     C         1  
3     170.990257    H             185     C         1  
4     167.277801    H             236     C         1  
...          ...  ...             ...   ...       ...  
1595  173.907293    M             252     A         0  
1596  184.787835    M             250     A         1  
1597  185.633286    M             299     A         1  
1598  167.961510    M             189     A         1  
1599  168.372148    M             284     A         1  

[1600 rows x 11 columns]

Ahora que tenemos nuestro dataframe con las notas de los alumnos, vamos a crear una tabla de frecuencias de dichas notas y explicaremos los conceptos correspondientes.


# Calcular la tabla de frecuencias
frecuencia_absoluta = df['nota'].value_counts().sort_index()
frecuencia_acumulada = frecuencia_absoluta.cumsum()
frecuencia_relativa = frecuencia_absoluta / frecuencia_absoluta.sum()
frecuencia_relativa_acumulada = frecuencia_relativa.cumsum()

# Crear el DataFrame de la tabla de frecuencias
tabla_frecuencias = pd.DataFrame({
    'x_i': frecuencia_absoluta.index,  # Columna de notas
    'n_i': frecuencia_absoluta.values,  # Frecuencia absoluta
    'N_i': frecuencia_acumulada.values,  # Frecuencia acumulada
    'f_i': frecuencia_relativa.values,  # Frecuencia relativa
    'F_i': frecuencia_relativa_acumulada.values  # Frecuencia relativa acumulada
})

# Reiniciar el índice para que comience desde 1
tabla_frecuencias.index = np.arange(1, len(tabla_frecuencias) + 1)

# Mostrar la tabla de frecuencias
print(tabla_frecuencias)

       x_i  n_i   N_i       f_i       F_i
1     3.48    1     1  0.000625  0.000625
2     3.49    1     2  0.000625  0.001250
3     3.51    1     3  0.000625  0.001875
4     3.56    1     4  0.000625  0.002500
5     3.60    1     5  0.000625  0.003125
..     ...  ...   ...       ...       ...
426   9.37    1  1596  0.000625  0.997500
427   9.45    1  1597  0.000625  0.998125
428   9.61    1  1598  0.000625  0.998750
429   9.74    1  1599  0.000625  0.999375
430  10.00    1  1600  0.000625  1.000000

[430 rows x 5 columns]

La tabla que estamos viendo tiene varias columnas. Vamos a revisarlas una por una.

Variable y posición ($x_i$)


# Mostrar la tabla de valores de x_i
print(tabla_frecuencias[['x_i']])

       x_i
1     3.48
2     3.49
3     3.51
4     3.56
5     3.60
..     ...
426   9.37
427   9.45
428   9.61
429   9.74
430  10.00