Felipe Maggi
Lenguaje de programación: Python
En esta serie de artículos dedicados a la Ciencia de Datos, ya hemos publicado los capítulos:
- Data Science I: Población, muestra, experimentos y tipos de variables.
- Data Science II-A: Estadística descriptiva unidimensional. Tablas de frecuencia y gráficos de distribución.
- Data Science II-B: Estadística descriptiva unidimensional. Medidas de tendencia central.
Vamos a tratar ahora, dentro de la estadística descriptiva unidimensional, las medidas de dispersión.
Seguimos en un terreno que parece de sobra conocido, y los que lean esto podrán pensar que el tema no aporta nada nuevo. Esencialmente, esto es cierto. En mi defensa diré que, como veremos, las cosas empiezan a presentar más bemoles de los que a menudo se tienen en cuenta.
También podría justificar la necesidad de tratar este tema como ya he hecho otras veces: si la base no es la adecuada, todo lo que se construya después es inestable.
Finalmente, quiero pensar que en esta serie de artículos, aunque los temas estén superados a nivel teórico desde hace cientos de años, al menos el acercamiento a los mismos aporta algo, en términos de compresión.
En este artículo, 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.
Los 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. Según estos autores:
Las medidas de dispersión son valores numéricos que describen la variabilidad de los datos.
Otra forma de definirlas es diciendo que nos dan una idea de lo alejados que están los datos entre sí. La dispersión mínima puede ser cero (cuando la variable adquiere el mismo valor en todos los casos), pero no existe una dispersión máxima, que puede llegar a ser arbitrariamente grande.
Las medidas de dispersión principales son:
- El rango
- La varianza
- La desviación estándar
Antes de entrar de lleno en cada una de ellas, vamos a suponer que estamos trabajando con un conjunto de 3 datos: las notas de los exámenes de matemáticas de un alumno concreto, de un trimestre concreto de un año académico concreto. Por comodidad, vamos a considerar este conjunto de datos como la población.
Para que un estadístico no se lleve las manos a la cabeza si lee esto, diremos que una muestra de tres datos puede no ser representativa (por eso hemos definido nuestro conjunto como la población).
Esto nos facilitará varias cosas: la visualización de los datos, los cálculos para los ejemplos y, en última instancia la compresión de los conceptos, más allá de la aplicación directa de las fórmulas sin entender de donde salen, o qué significan.
Planteamiento del problema
Supongamos que nuestro alumno ha hecho tres exámenes en el trimestre, y su nota media es de 5. Esto nos dice que ha aprobado el trimestre, pero poco más. Sus notas podrían haber sido, por ejemplo:
- 5, 5 y 5
- 4, 5 y 6
- 0, 5, 10
En los tres casos, la media aritmética es la misma: μ = 5. Recordemos que la media poblacional, matemáticamente hablando, se define como:
Es decir, la suma de todos los valores que adquiere la variable x, divida por el número total de valores.
Sin embargo, la dispersión de los datos es muy distinta en cada caso. Comprobémoslo primero visualmente:
import matplotlib.pyplot as plt
# Puntos a destacar en el eje X
x_highlight = [5, 5, 5]
y_highlight = [0, 0, 0]
# Configuración de la gráfica
plt.figure(figsize=(8, 1))
plt.axhline(0, color='black') # Dibuja el eje X
plt.scatter(x_highlight, y_highlight,
color='red') # Destaca los puntos seleccionados
# Etiquetas para los puntos destacados
for i, txt in enumerate(x_highlight):
plt.annotate(f'{txt}', (x_highlight[i], y_highlight[i]),
textcoords="offset points", xytext=(0,10), ha='center')
# Configuración del eje X de 0 a 10
plt.xlim(0, 10)
plt.xticks(range(-1, 12)) # Marca todos los puntos de 0 a 10
plt.yticks([]) # Elimina los valores en el eje Y
plt.xlabel("Eje X")
plt.title("Notas 5, 5 y 5")
plt.show()
import matplotlib.pyplot as plt
# Puntos a destacar en el eje X
x_highlight = [4, 5, 6]
y_highlight = [0, 0, 0]
# Configuración de la gráfica
plt.figure(figsize=(8, 1))
plt.axhline(0, color='black') # Dibuja el eje X
plt.scatter(x_highlight,
y_highlight, color='red') # Destaca los puntos seleccionados
# Etiquetas para los puntos destacados
for i, txt in enumerate(x_highlight):
plt.annotate(f'{txt}', (x_highlight[i], y_highlight[i]),
textcoords="offset points", xytext=(0,10), ha='center')
# Configuración del eje X de 0 a 10
plt.xlim(0, 10)
plt.xticks(range(-1, 12)) # Marca todos los puntos de 0 a 10
plt.yticks([]) # Elimina los valores en el eje Y
plt.xlabel("Eje X")
plt.title("Notas 4, 5 y 6")
plt.show()
import matplotlib.pyplot as plt
# Puntos a destacar en el eje X
x_highlight = [0, 5, 10]
y_highlight = [0, 0, 0]
# Configuración de la gráfica
plt.figure(figsize=(8, 1))
plt.axhline(0, color='black') # Dibuja el eje X
plt.scatter(x_highlight,
y_highlight, color='red') # Destaca los puntos seleccionados
# Etiquetas para los puntos destacados
for i, txt in enumerate(x_highlight):
plt.annotate(f'{txt}', (x_highlight[i], y_highlight[i]),
textcoords="offset points", xytext=(0,10), ha='center')
# Configuración del eje X de 0 a 10
plt.xlim(0, 10)
plt.xticks(range(-1, 12)) # Marca todos los puntos de 0 a 10
plt.yticks([]) # Elimina los valores en el eje Y
plt.xlabel("Eje X")
plt.title("Notas 0, 5 y 10")
plt.show()
Cuando las notas son todas la misma (5, 5 y 5), están todas en el mismo punto. La dispersión es cero.
Si las notas son 4, 5 y 6, la media también es 5, pero existe cierta distancia entre las notas. Si tomamos como referencia la media, 4 y 6 están a una unidad de distancia, y 5 está a cero unidades de distancia.
Finalmente, si las notas son 0, 5 y 10, y de nuevo tomando como punto de referencia la media, el 0 y el 10 están a 5 unidades de distancia, aunque el 5 permanece a 0 unidades de distancia.
Rango
Si pintamos una raya recta entre el valor mínimo y el máximo (en términos matemáticos, sería un segmento o intervalo), y medimos la longitud de la misma:
- En el primer caso (5, 5, 5) dicha longitud es 0.
- En el segundo, con las notas 4, 5 y 6, la longitud es 2
- En el tercero (0, 5, 10), la longitud es 10.
El rango es, por tanto, la diferencia entre el valor máximo (H) y el valor mínimo (L), y «nos dice el tamaño del intervalo en el que caen todos los datos» (Johnson & Kuby).
- En el primer caso es de 0 unidades: 5 – 5 = 0
- En el segundo caso es de 2 unidades: 6 – 4 = 2
- En el tercer caso es de 10 unidades: 10 – 0 = 10
Ya con esto podemos decir que los datos del segundo caso están más dispersos que el primero, pero menos dispersos que el tercero.
Varianza
La fórmula de la varianza de la población está en todas partes. Vamos a empezar por ella para entender el concepto, y luego ampliarlo a casos más comunes, pero teóricamente más complejos, como la varianza muestral (o cuasivarianza).
La varianza poblacional se define matemáticamente como:
Es decir, la varianza es la suma de las diferencias con respecto a la media elevadas al cuadrado, divida por el número de datos. No deja de ser un «promedio» de las distancias con respecto a la media. En concreto, es la media aritmética de dichas diferencias.
La distancia de cada valor con respecto a la media se eleva al cuadrado, para evitar que las distancias positivas cancelen a las negativas. Como la media
- Con las notas 5, 5, 5:
- Con las notas 4, 5 y 6:
- Con las notas 0, 5, 10:
Elevando al cuadrado estas distancias, el problema se resuelve. La varianza, en cada uno de los casos de ejemplo, es:
Notas 5, 5 y 5
Notas 4, 5 y 6
Notas 0, 5 y 10
Desviación estándar
También conocida como desviación típica, la desviación estándar es la raíz cuadrada de la varianza:
Debemos tener en cuenta que la varianza, al calcularse elevando al cuadrado las diferencias con respecto a la media, está en «unidades cuadradas». Siguiendo con nuestro ejemplo, en el que las unidades de las notas son «puntos», la varianza en el caso de las notas 4,5 y 6 sería 0.67 puntos cuadrados, lo que no tiene mucho sentido.
Al calcular la raíz cuadrada de la varianza, estamos de alguna manera deshaciendo el «truco» matemático que usamos para evitar que las diferencias se cancelen entre sí, y volviendo a las unidades originales. Así, las desviaciones típicas de nuestros ejemplos son:
- Notas 5,5 y 5:
- Notas 4, 5 y 6:
- Notas 0,5 y 10:
Si nos fijamos, esto ya tiene cierta lógica. Analicemos los casos segundo y tercero.
Cuando las notas son 4, 5 y 6 la desviación típica es aproximadamente de 0,82 puntos. Recordemos que tenemos tres notas, dos de las cuales se alejan de la media 1 punto (el 4 y el 6), y otra que no se aleja en absoluto (el 5). Es lógico que la desviación típica sea cercana a 1, pero no 1, porque hay un caso en el que la distancia es cero.
Cuando las notas son 0, 5 y 10 la desviación típica es un poco mayor de 4 puntos. De nuevo, recordemos que tenemos tres notas, dos de las cuales se alejan de la media 5 puntos (el 0 y el 10), y otra que no se aleja nada (el 5).
Otras medidas de dispersión
Existen otras medidas de dispersión, menos conocidas y menos utilizadas, pero que vale la pena mencionar de pasada:
- Desviación absoluta respecto a la media
- Desviación absoluta respecto a la mediana
Desviación absoluta respecto a la media
Su fórmula es similar a la de la varianza, pero en lugar de elevar al cuadrado las diferencias con respecto a la media, se calcula el valor absoluto de la diferencia:
Según explican Johnson & Kuby, «aun cuando esta medida particular de dispersión no se usa con frecuencia, nos indica la «distancia» media a la que están los datos desde la media«.
Desviación absoluta respecto a la mediana
Como la variancia y la desviación típica dependen de la media, y la media es muy sensible a los valores extremos, a veces es conveniente analizar la dispersión de los datos con respecto a la mediana, que es una medida de tendencia central que no es tan sensible a ese tipo de valores. La expresión matemática de la desviación absoluta respecto a la mediana de la población es:
Medidas de dispersión con respecto a la muestra
Por lo general, uno no trabaja con los datos de la población. Lo más habitual es contar con datos muestrales. Por ello, en la bibliografía, es común ver estas fórmulas con respecto a la muestra, no a la población.
En el caso de la desviación absoluta con respecto a la media, por ejemplo, lo común es ver:
Sin embargo, el cambio más importante es el que se produce en la varianza muestral.
Varianza muestral
La expresión matemática de la varianza muestral (o cuasivarianza) presenta varios cambios con respecto a la varianza poblacional:
Aquí, se sustituyen:
(la varianza de la población), por (la varianza de la muestra). (la media de la población), por (la media de la muestra). (el tamaño de la población), por (el tamaño de la muestra, menos 1).
La respuesta a esta cuestión la intentaremos dar más adelante. Antes, definiremos la desviación estándar (o típica), de la muestra.
Desviación muestral estándar
La desviación muestral estándar (o típica), se expresa matemáticamente como:
¿Porqué n−1?
En general, el problema de todas las materias relacionadas con las matemáticas, y la estadística es una de ellas, es que se explican a menudo empezando la casa por el tejado. Se nos enseñan las fórmulas directamente, y no para qué sirven, o de dónde vienen.
En este caso, vamos a intentar solventar esto mediante ejemplos cuyo fin es transmitir el concepto del problema. Para un enfoque matemático facilitaremos un grupo de vídeos que explican perfectamente porqué lo correcto es usar n−1 en lugar de n cuando se trata de la varianza muestral.
Vamos a recuperar nuestro dataset simulado con las notas de matemáticas que hemos estado utilizando en esta serie de artículos. Recordemos que estamos trabajando con 1600 notas.
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]
Calculamos ahora la media aritmética de las notas, y su varianza. Para que el cáculo de la varianaza resulte evidente, no usaremos ahora la función correspondiente de Python, si no la fórmula de forma explícita.
# Cálculo de la media con aproximación a 2 decimales
media_nota = round(df['nota'].mean(), 2)
# Cálculo de la varianza poblacional con aproximación a 2 decimales
N = len(df)
varianza_poblacional = round(((df['nota'] - media_nota) ** 2).sum() / N, 2)
print("Media poblacional de la columna 'nota':", media_nota)
print("Varianza poblacional de la columna 'nota':", varianza_poblacional)
Media poblacional de la columna 'nota': 6.48 Varianza poblacional de la columna 'nota': 1.03
La media poblacional es 6.48, y la varianza es 1.03. Hasta aquí todo bien. Ahora, vamos a seleccionar una muestra de 40 alumnos, y calcularemos la media y la varianza, usando n.
Muestra 1
# Establecer semillas para la reproducibilidad
np.random.seed(1)
random.seed(1)
# Lista de grupos
grupos = df['grupo'].unique()
# Seleccionar al azar 10 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 10)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra_1 = pd.concat(dfs_alumnos_temas, ignore_index=True)
# Calcular la nota media de la muestra
nota_media_muestra_1 = df_muestra_1['nota'].mean()
# Mostrar el DataFrame de muestra y la nota media
print("DataFrame de muestra:")
print(df_muestra_1.head())
print()
print(f"Nota media de la muestra: {nota_media_muestra_1:.2f}")
DataFrame de muestra: id_estudiante asignatura tema fecha nota calificacion \ 0 UZFK8UT0 Matemáticas Tema 4 2024-11-24 5.09 aprobado 1 GNZS43C5 Matemáticas Tema 8 2025-03-16 6.11 aprobado 2 PSEIMVIH Matemáticas Tema 5 2024-12-22 5.74 aprobado 3 RIXA11DP Matemáticas Tema 1 2024-09-01 7.79 notable 4 874FRHOC Matemáticas Tema 7 2025-02-16 7.09 notable estatura sexo tiempo_estudio grupo aprobado 0 158.597615 M 375 C 1 1 181.919921 M 350 C 1 2 171.303577 H 301 C 1 3 174.639418 M 263 C 1 4 173.611798 M 268 C 1 Nota media de la muestra: 6.58
# Cálculo de la media
media_nota = df_muestra_1['nota'].mean()
# Cálculo de la varianza
n = len(df_muestra_1)
varianza = round(((df_muestra_1['nota'] - media_nota) ** 2).sum() / n, 2)
print("Varianza de la columna 'nota':", varianza)
Varianza de la columna 'nota': 1.04
La primera muesta, con las notas de 40 alumnos, tiene una media de 6.58, y una varianza, dividiendo por n, de 1.04.
Si repetimos el experimento varias veces, obtendremos distintas medias muestrales, y distintas varianzas.
Muestra 2
# Establecer semillas para la reproducibilidad
np.random.seed(2)
random.seed(2)
# Lista de grupos
grupos = df['grupo'].unique()
# Seleccionar al azar 10 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 10)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra_2 = pd.concat(dfs_alumnos_temas, ignore_index=True)
# Calcular la nota media de la muestra
nota_media_muestra_2 = df_muestra_2['nota'].mean()
# Mostrar el DataFrame de muestra y la nota media
print("DataFrame de muestra:")
print(df_muestra_2.head())
print()
print(f"Nota media de la muestra: {nota_media_muestra_2:.2f}")
DataFrame de muestra: id_estudiante asignatura tema fecha nota calificacion \ 0 9T84AZYT Matemáticas Tema 9 2025-04-13 6.74 aprobado 1 OM5IGQPK Matemáticas Tema 3 2024-10-27 7.32 notable 2 CSLQC3C5 Matemáticas Tema 8 2025-03-16 4.75 suspenso 3 1V2ISQP4 Matemáticas Tema 7 2025-02-16 7.03 notable 4 X7EEDTJV Matemáticas Tema 9 2025-04-13 7.15 notable estatura sexo tiempo_estudio grupo aprobado 0 182.511983 M 269 C 1 1 169.934711 M 425 C 1 2 166.367303 H 199 C 0 3 157.809624 M 204 C 1 4 158.524188 H 309 C 1 Nota media de la muestra: 6.32
# Cálculo de la media
media_nota = df_muestra_2['nota'].mean()
# Cálculo de la varianza
n = len(df_muestra_2)
varianza = round(((df_muestra_2['nota'] - media_nota) ** 2).sum() / n, 2)
print("Varianza de la columna 'nota':", varianza)
Varianza de la columna 'nota': 0.84
Muestra 3
# Establecer semillas para la reproducibilidad
np.random.seed(3)
random.seed(3)
# Lista de grupos
grupos = df['grupo'].unique()
# Seleccionar al azar 10 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 10)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra_3 = pd.concat(dfs_alumnos_temas, ignore_index=True)
# Calcular la nota media de la muestra
nota_media_muestra_3 = df_muestra_3['nota'].mean()
# Mostrar el DataFrame de muestra y la nota media
print("DataFrame de muestra:")
print(df_muestra_3.head())
print()
print(f"Nota media de la muestra: {nota_media_muestra_3:.2f}")
DataFrame de muestra: id_estudiante asignatura tema fecha nota calificacion \ 0 A7TZ0YNC Matemáticas Tema 3 2024-10-27 5.41 aprobado 1 GSES6M03 Matemáticas Tema 8 2025-03-16 5.53 aprobado 2 JGZLMA5U Matemáticas Tema 4 2024-11-24 5.66 aprobado 3 UZFK8UT0 Matemáticas Tema 5 2024-12-22 5.58 aprobado 4 1V2ISQP4 Matemáticas Tema 7 2025-02-16 7.03 notable estatura sexo tiempo_estudio grupo aprobado 0 181.281124 H 202 C 1 1 162.128791 M 161 C 1 2 167.869059 H 279 C 1 3 151.247325 M 231 C 1 4 157.809624 M 204 C 1 Nota media de la muestra: 6.36
# Cálculo de la media
media_nota = df_muestra_3['nota'].mean()
# Cálculo de la varianza
n = len(df_muestra_3)
varianza = round(((df_muestra_3['nota'] - media_nota) ** 2).sum() / n, 2)
print("Varianza de la columna 'nota':", varianza)
Varianza de la columna 'nota': 0.96
Repasemos los resultados de medias y varianzas (dividiendo por
- Población: media 6.48, varianza 1.03
- Muestra 1: media 6.58, varianza 1.04
- Muestra 2: media 6.32, varianza 0.84
- Muestra 3: media 6.36, varianza 0.96
Todas las muestras tienen medias y varianzas distintas a las de la población, lo que no es sorprendente. En unos casos la media y/o la varianza son mayores que los de la población, y en otros son menores.
Recordemos que, cuando trabajamos con una muestra, lo hacemos porque trabajar con la población a menudo es imposible, o muy poco práctico. Pero lo que realmente nos interesa es estimar el valor de la población, a partir de esa muestra.
En el caso de las medias muestrales, aunque no son iguales que la media poblacional, es posible demostrar que se trata de un estimador insesgado. El valor esperado, la esperanza matemática, de la media muestral es igual al valor de la media poblacional.
En lenguaje coloquial podríamos decir que la media de las medias muestrales, si obtenemos un número suficientemente grande de muestras, será igual a la de la población.
Sin embargo, esto no pasa con la varianza, si dividimos por
En concreto, si dividimos por
Montecarlo 1
Para ilustrar esto, realizaremos simulaciones de Montecarlo, que es «un tipo de algoritmo computacional que utiliza un muestreo aleatorio repetido para obtener la probabilidad de que ocurra una serie de resultados» (ibm.com).
Esto nos permite jugar con muestras de distintos tamaños, muchas veces, y comparar resultados.
En esta primera Montecarlo, vamos obtener 10 muestras de 4 notas cada una, y promediaremos los resultados correspondientes a la media y la varianza (dividiendo por
import random
import pandas as pd
import numpy as np
# Establecer semillas para la reproducibilidad
np.random.seed(5)
random.seed(5)
def generar_muestra(df):
grupos = df['grupo'].unique()
# Seleccionar al azar 1 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 1)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) &
(df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra = pd.concat(dfs_alumnos_temas, ignore_index=True)
return df_muestra
# Número de simulaciones
num_simulaciones = 10
# Listas para almacenar las métricas de cada muestra
medias = []
varianzas = []
for _ in range(num_simulaciones):
df_muestra = generar_muestra(df)
nota_media = df_muestra['nota'].mean()
varianza = df_muestra['nota'].var(ddof=0) # Varianza con n, no n-1
medias.append(nota_media)
varianzas.append(varianza)
# Convertir los resultados a arrays de NumPy
medias = np.array(medias)
varianzas = np.array(varianzas)
# Mostrar resultados
print(f"Media estimada: {medias.mean():.2f}")
print(f"Varianza estimada: {varianzas.mean():.2f}")
Media estimada: 6.70 Varianza estimada: 0.73
Como podemos ver, la media estimada (la media de las medias) es 6.70, y la varianza estimada (la media de las variazas), es 0.73. Ninguno de los dos valores es igual a los de la población. Pero recordemos que estamos trabajando con muestras muy pequeñas (de 4 notas), y estimar un parámetro con muestras tan pequeñas no es aconsejable.
En otro artículo hablaremos del tamaño óptimo de las muestras. Aquí simplemente diremos que si la distribución de la población (o de las muestras) es normal, las estimaciones son adecuadas a partir de 30 datos.
Aunque estemos trabajando con muestras de 4 notas, podemos a repetir el experimento, aumentando el número de simulaciones. En la siguiente Montecarlo, obtendremos 50 muestras distintas de 4 notas, y promediaremos la media y la varianza (calculada dividiendo por n).
Montecarlo 2
import random
import pandas as pd
import numpy as np
# Establecer semillas para la reproducibilidad
np.random.seed(5)
random.seed(5)
def generar_muestra(df):
grupos = df['grupo'].unique()
# Seleccionar al azar 1 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 1)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra = pd.concat(dfs_alumnos_temas, ignore_index=True)
return df_muestra
# Número de simulaciones
num_simulaciones = 50
# Listas para almacenar las métricas de cada muestra
medias = []
varianzas = []
for _ in range(num_simulaciones):
df_muestra = generar_muestra(df)
nota_media = df_muestra['nota'].mean()
varianza = df_muestra['nota'].var(ddof=0) # Varianza con n, no n-1
medias.append(nota_media)
varianzas.append(varianza)
# Convertir los resultados a arrays de NumPy
medias = np.array(medias)
varianzas = np.array(varianzas)
# Mostrar resultados
print(f"Media estimada: {medias.mean():.2f}")
print(f"Varianza estimada: {varianzas.mean():.2f}")
Media estimada: 6.54 Varianza estimada: 0.74
Perfecto. Ahora, la media estimada es de 6.54, y la varianza estimada es de 0.74. Vamos a volver a repetir el experimento con una muestra de 4 notas, pero trabajando con 100 muestras distintas:
Montecarlo 3
import random
import pandas as pd
import numpy as np
# Establecer semillas para la reproducibilidad
np.random.seed(5)
random.seed(5)
def generar_muestra(df):
grupos = df['grupo'].unique()
# Seleccionar al azar 1 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 1)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra = pd.concat(dfs_alumnos_temas, ignore_index=True)
return df_muestra
# Número de simulaciones
num_simulaciones = 100
# Listas para almacenar las métricas de cada muestra
medias = []
varianzas = []
for _ in range(num_simulaciones):
df_muestra = generar_muestra(df)
nota_media = df_muestra['nota'].mean()
varianza = df_muestra['nota'].var(ddof=0) # Varianza con n, no n-1
medias.append(nota_media)
varianzas.append(varianza)
# Convertir los resultados a arrays de NumPy
medias = np.array(medias)
varianzas = np.array(varianzas)
# Mostrar resultados
print(f"Media estimada: {medias.mean():.2f}")
print(f"Varianza estimada: {varianzas.mean():.2f}")
Media estimada: 6.50 Varianza estimada: 0.72
Ahora la media estimada es de 6.50, y la varianza estimada es de 0.72.
¿Se observa cierto patrón? A medida que aumentamos el número de simulaciones (de muestras), la media estimada se acerca más y más al parámetro de la población. Aunque cada muestra tenga cuatro datos, al promediar las medias muestrales nos acercamos cada vez más a la media población. No parece suceder lo mismo con la varianza estimada.
Los siguientes resultados se obtienen tras repetir la simulación de Montecarlo con 500, 1000 y 10000 muestras:
- 500 muestras de 4 notas:
- Media estimada: 6.46
- Variaza estimada: 0.74
- 1000 muestras de 4 notas
- Media estimada: 6.47
- Variaza estimada: 0.77
- 10000 muestras de 4 notas
- Media estimada: 6.48
- Variaza estimada: 0.77
Aún trabajando con 4 notas de muestra, si sacamos 10000 muestras aleatorias, obtenemos la media, y promediamos esas medias, el valor que obtendremos será prácticamente igual del parámetro poblacional.
Pero la varianza está siempre por debajo. Estamos subestimando la varianza poblacional, porque al dividir por
Un acercamiento simple al problema
Si nos quedamos con los resultados de las simulaciones a partir de 1000 muestras, cuya varianza estimada es de 0.77, podemos hacer un ejercicio muy interesante.
Recordemos que la varianza de la población es 1.03. Nosotros, al dividir por
Si dividimos 0.77 entre 1.03, obtenemos 0.7475. Aproximando, estamos hablando del 75% de la varianza real. En otras palabras, al dividir por
Recordemos que nuestra muestra era de 4 datos, y lo que hemos obtenido es el equivalente a multiplicar la varianza poblacional (
Pero
Puede comprobarse que si en lugar de usar muestras de 4 datos, hubiésemos usado muestras de 2 datos, nuestra estimación de la varianza hubiese sido equivalente a
Es posible «ver» esto si se modifica el código con las simulaciones para obtener muestras con distintos números de datos.
Por lo tanto, si al intentar estimar la variaza poblacional a partir de los datos de la muesta, estamos subestimando esa varianza por un factor igual a
debemos hacer algo para contrarrestar ese sesgo.
Matemáticamente, lo que hemos estado haciendo es esto:
Si lo que quiero es estimar
De esta forma, en el lado derecho de la ecuación se cancelan todos los términos, menos
Así llegamos a la fórmula de la varianza muestral (
El siguiente código recoge muestras aleatorias de 8 datos (
import random
import pandas as pd
import numpy as np
# Establecer semillas para la reproducibilidad
np.random.seed(5)
random.seed(5)
def generar_muestra(df):
grupos = df['grupo'].unique()
# Seleccionar al azar 1 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 2)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra = pd.concat(dfs_alumnos_temas, ignore_index=True)
return df_muestra
# Número de simulaciones
num_simulaciones = 10000
# Listas para almacenar las métricas de cada muestra
medias = []
varianzas = []
for _ in range(num_simulaciones):
df_muestra = generar_muestra(df)
nota_media = df_muestra['nota'].mean()
varianza = df_muestra['nota'].var(ddof=0) # Varianza con n, no n-1
medias.append(nota_media)
varianzas.append(varianza)
# Convertir los resultados a arrays de NumPy
medias = np.array(medias)
varianzas = np.array(varianzas)
# Mostrar resultados
print(f"Media estimada: {medias.mean():.2f}")
print(f"Varianza estimada: {varianzas.mean():.2f}")
Media estimada: 6.47 Varianza estimada: 0.89
Esta forma de explicar la razón por la que se debe dividir por
Para terminar, vamos a repetir la Montecarlo con la muestra de 8 notas, 10000 veces, pero dividiendo por
import random
import pandas as pd
import numpy as np
# Establecer semillas para la reproducibilidad
np.random.seed(5)
random.seed(5)
def generar_muestra(df):
grupos = df['grupo'].unique()
# Seleccionar al azar 1 alumnos de cada grupo (sin reemplazo)
alumnos_muestra = []
for grupo in grupos:
alumnos_grupo = df[df['grupo'] == grupo]['id_estudiante'].unique()
muestra_grupo = random.sample(list(alumnos_grupo), 2)
alumnos_muestra.extend(muestra_grupo)
# Seleccionar al azar un tema para cada alumno de la muestra (con reemplazo)
temas_muestra = {}
for alumno in alumnos_muestra:
notas_alumno = df[df['id_estudiante'] == alumno]
tema_elegido = random.choice(notas_alumno['tema'].unique())
temas_muestra[alumno] = tema_elegido
# Crear el DataFrame de muestra con los temas seleccionados
dfs_alumnos_temas = []
for alumno, tema in temas_muestra.items():
df_alumno_tema = df[(df['id_estudiante'] == alumno) & (df['tema'] == tema)]
dfs_alumnos_temas.append(df_alumno_tema)
df_muestra = pd.concat(dfs_alumnos_temas, ignore_index=True)
return df_muestra
# Número de simulaciones
num_simulaciones = 10000
# Listas para almacenar las métricas de cada muestra
medias = []
varianzas = []
for _ in range(num_simulaciones):
df_muestra = generar_muestra(df)
nota_media = df_muestra['nota'].mean()
varianza = df_muestra['nota'].var(ddof=1) # Varianza con n-1, no n
medias.append(nota_media)
varianzas.append(varianza)
# Convertir los resultados a arrays de NumPy
medias = np.array(medias)
varianzas = np.array(varianzas)
# Mostrar resultados
print(f"Media estimada: {medias.mean():.2f}")
print(f"Varianza estimada: {varianzas.mean():.2f}")
Media estimada: 6.47 Varianza estimada: 1.02
# Número de simulaciones
num_simulaciones = 100000
¿Qué pasa cuando la muestra es grande?
Como hemos visto, si intentamos estimar la varianza poblacional con los datos de una muesta, y dividimos por
Con muestras pequeñas, ese sesgo es muy grande, pero con muestras grandes, el factor puede llegar a ser despreciable.
Por ejemplo, para una muestra con 1000 datos, el sesgo es
Multiplicar
Pero no siempre sucede que las muestras sean grandes, y siempre es más correcto operar con estimadores insesgados. El problema está en que es común ver estimaciones de la varianza poblacional a partir de una muestra, utilizando
Dos enfoques distintos
A los que nos gustan las matemáticas, nos sorprende cómo se puede llegar a los mismos resultados por caminos completamente distintos.
Enfoque 1
En este vídeo de KhanAcademy, el acercamiento al problema que hemos estado tratando es similar al mostrado en este artículo. Se basa en una simulación computacional con muestras de distintos tamaños: Simulación que muestra el sesgo en la varianza muestral.
Enfoque 2
Esta serie de videos de AkademiaUFM, en cambio, se aborda el problema desde una perspectiva muy ortodoxa en términos de demostraciones matemáticas. Aquí no hay simulaciones que se aprovechen de la potencia de los ordenadores, sino que se demuestra porqué es necesario dividir por
Bibliografía y referencias
- Johnson, R. & Kuby, P. (2008). Estadistica elemental: lo esencial (10a ed.). Cengage Learning Editores S.A.
- ¿Qué es la simulación de Montecarlo? [Internet]. Ibm.com. 2024 [citado 12 de marzo de 2025]. Disponible en: https://www.ibm.com/es-es/topics/monte-carlo-simulation
- KhanAcademyEspañol. Simulación que muestra el sesgo en la varianza muestral [Internet]. Youtube; [citado 12 de marzo de 2025]. Disponible en: https://www.youtube.com/watch?v=dE1pLPDuh-w
- akademeiaUFM. Demostración del sesgo en la varianza Parte I [Internet]. Youtube; [citado 12 de marzo de 2025]. Disponible en: https://www.youtube.com/watch?v=sD7UmKGRLFI