En estadística, los datos se comportan de formas distintas según su naturaleza. Si un valor no cambia a lo largo de un experimento (como el número de caras que tiene un dado), se denomina constante. Por el contrario, si el valor cambia debido al azar, estamos ante una variable aleatoria.
Imaginemos que lanzamos un dado común de 6 caras un total de 10 veces. Definimos nuestra variable aleatoria (\(X\)) como: “El número de veces que sale el número 1”.
Al realizar este experimento, el resultado final es impredecible: * Puede ocurrir que en una serie de lanzamientos el “1” salga 5 veces. * En otra serie diferente, puede aparecer solo 2 o 3 veces. * En el caso extremo de que no salga nunca, el valor de la variable será 0. * Si tenemos una suerte increíble y sale en absolutamente todos los tiros, el valor será 10.
Por lo tanto, nuestra variable aleatoria discreta puede tomar cualquier valor entero dentro del rango:
\[X \in \{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10\}\]
Para entender cómo se reparten las opciones de que ocurra cada uno de estos escenarios, utilizamos las herramientas de distribución.
2 Distribución de la muestra o distribución de probabilidad
La distribución de probabilidad es un modelo teórico que define la probabilidad de que una variable aleatoria tome determinados valores. En este modelo, la suma de todas las probabilidades posibles siempre debe ser exactamente el 100%. Por otro lado, la distribución de la muestra (o distribución empírica) refleja las frecuencias de los valores que realmente ocurrieron y se registraron en un experimento o base de datos real.
Por ejemplo, si lanzamos tres monedas trucadas donde la probabilidad de obtener cara es del 70% y la de cruz es del 30%, la distribución de probabilidad teórica para el número de caras es:
3 caras: 34.3%
2 caras y 1 cruz: 44.1%
1 cara y 2 cruces: 18.9%
0 caras (3 cruces): 2.7%
A partir de estos parámetros teóricos, podemos modelar la curva de probabilidad y compararla con un experimento muestral real mediante programación.
import matplotlibimport matplotlib.pyplot as pltimport numpy as npmatplotlib.rcParams["svg.fonttype"] ="none"valores_caras = [0, 1, 2, 3]probabilidades_teoricas = [0.027, 0.189, 0.441, 0.343]np.random.seed(42) n_experimentos =100# Simulamos el lanzamiento de 3 monedas trucadas (70% Cara = 1, 30% Cruz = 0)monedas = np.random.choice([1, 0], size=(n_experimentos, 3), p=[0.7, 0.3])# Sumamos las caras obtenidas en cada lanzamiento (valores de 0 a 3)muestra_datos = np.sum(monedas, axis=1)# Contamos cuántas veces ocurrió REALMENTE cada resultado (Frecuencias Absolutas)valores_muestra, conteos = np.unique(muestra_datos, return_counts=True)# Aseguramos un mapeo completo por si algún valor tuviera 0 repeticionesconteo_completo = {0: 0, 1: 0, 2: 0, 3: 0}for v, c inzip(valores_muestra, conteos): conteo_completo[v] = c# Extraemos la lista ordenada de apariciones absolutasapariciones_reales = [conteo_completo[i] for i inrange(4)]# Calculamos las frecuencias relativas para el gráfico de proporcionesprob_muestral_completa = [conteo_completo[i] / n_experimentos for i inrange(4)]print("MEDIA TEÓRICA (VALOR ESPERADO - USANDO PROBABILIDADES)")media_teorica =sum( val * prob for val, prob inzip(valores_caras, probabilidades_teoricas))print(f"Fórmula: (0 * {probabilidades_teoricas[0]}) + (1 * {probabilidades_teoricas[1]}) + (2 * {probabilidades_teoricas[2]}) + (3 * {probabilidades_teoricas[3]})")print(f"Media Teórica = {media_teorica:.3f} caras\n")print("MEDIA MUESTRAL HABITUAL (SUMAR TODOS LOS DATOS UNO A UNO)")suma_bruta_datos = np.sum(muestra_datos)media_habitual = suma_bruta_datos / n_experimentosprint(f"Datos reales (primeros 15 lanzamientos): {list(muestra_datos[:15])}...")print(f"Fórmula: (Dato_1 + Dato_2 + ... + Dato_{n_experimentos}) / {n_experimentos}")print(f"Operación: {suma_bruta_datos} / {n_experimentos}")print(f"Media Habitual = {media_habitual:.3f} caras\n")print("MEDIA MUESTRAL PONDERADA (AGRUPANDO POR CONTEOS)")suma_ponderada =sum(val * count for val, count in conteo_completo.items())media_ponderada = suma_ponderada / n_experimentosprint(f"Fórmula: ((0 * {conteo_completo[0]}) + (1 * {conteo_completo[1]}) + (2 * {conteo_completo[2]}) + (3 * {conteo_completo[3]})) / {n_experimentos}")print(f"Operación: {suma_ponderada} / {n_experimentos}")print(f"Media Ponderada = {media_ponderada:.3f} caras\n")# CREACIÓN DE LOS GRÁFICOS (SUBPLOTS EN PARALELO)x = np.arange(len(valores_caras))nombres_eje = ["0 Caras", "1 Cara", "2 Caras", "3 Caras"]# Creamos un lienzo que contiene 1 fila y 2 columnas de gráficosfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))# Gráfico de Probabilidades / Proporcioneswidth =0.35barra1 = ax1.bar( x - width /2, probabilidades_teoricas, width, label="Teórica (Probabilidad)", color="#3498db",)barra2 = ax1.bar( x + width /2, prob_muestral_completa, width, label="Empírica (Muestra)", color="#e67e22",)ax1.set_ylabel("Probabilidad / Frecuencia Relativa")ax1.set_xlabel("Número de Caras")ax1.set_title("Comparativa de Proporciones (%)")ax1.set_xticks(x)ax1.set_xticklabels(nombres_eje)ax1.legend()ax1.grid(axis="y", linestyle="--", alpha=0.5)def etiquetar_porcentajes(barras, axis):for barra in barras: alto = barra.get_height() axis.annotate(f"{alto:.1%}", xy=(barra.get_x() + barra.get_width() /2, alto), xytext=(0, 3), textcoords="offset points", ha="center", va="bottom", )etiquetar_porcentajes(barra1, ax1)etiquetar_porcentajes(barra2, ax1)# Gráfico de Apariciones Reales (Conteos Absolutos)barra3 = ax2.bar( x, apariciones_reales, width=0.5, label="Apariciones en Muestra", color="#e67e22", alpha=0.9,)ax2.set_ylabel("Número de Apariciones (Frecuencia Absoluta)")ax2.set_xlabel("Número de Caras")ax2.set_title("Recuento Real del Experimento (Conteo de Datos)")ax2.set_xticks(x)ax2.set_xticklabels(nombres_eje)ax2.grid(axis="y", linestyle="--", alpha=0.5)def etiquetar_conteos(barras, axis):for barra in barras: alto = barra.get_height() axis.annotate(f"{int(alto)} veces", xy=(barra.get_x() + barra.get_width() /2, alto), xytext=(0, 3), textcoords="offset points", ha="center", va="bottom", fontweight="bold", )etiquetar_conteos(barra3, ax2)plt.tight_layout()plt.show()
MEDIA TEÓRICA (VALOR ESPERADO - USANDO PROBABILIDADES)
Fórmula: (0 * 0.027) + (1 * 0.189) + (2 * 0.441) + (3 * 0.343)
Media Teórica = 2.100 caras
MEDIA MUESTRAL HABITUAL (SUMAR TODOS LOS DATOS UNO A UNO)
Datos reales (primeros 15 lanzamientos): [np.int64(1), np.int64(3), np.int64(2), np.int64(1), np.int64(2), np.int64(3), np.int64(3), np.int64(3), np.int64(2), np.int64(3), np.int64(3), np.int64(0), np.int64(3), np.int64(3), np.int64(2)]...
Fórmula: (Dato_1 + Dato_2 + ... + Dato_100) / 100
Operación: 209 / 100
Media Habitual = 2.090 caras
MEDIA MUESTRAL PONDERADA (AGRUPANDO POR CONTEOS)
Fórmula: ((0 * 4) + (1 * 18) + (2 * 43) + (3 * 35)) / 100
Operación: 209 / 100
Media Ponderada = 2.090 caras
En este análisis, se simula un mismo experimento: el lanzamiento de tres monedas trucadas repetido 100 veces (n=100). A partir de los datos generados, podemos estudiar el comportamiento de la variable aleatoria desde dos perspectivas visuales complementarias:
Diagrama de Frecuencias Absolutas (Gráfico de la derecha): Muestra el recuento puro y directo de los datos. Nos indica cuántos datos (apariciones o eventos de éxito) pertenecen a cada valor específico de la variable aleatoria dentro del conjunto de datos generado.
Diagrama de Frecuencias Relativas y Probabilidad Teórica (Gráfico de la izquierda): Aquí se realiza el proceso de relativización. Tomamos los conteos absolutos de la derecha y los dividimos entre el tamaño total de la muestra (n=100) para convertirlos en proporciones o porcentajes y compararlo con las probabilidades teóricas.
Además de la comparación gráfica, el análisis numérico nos permite contrastar el comportamiento del centro de la distribución a través de tres enfoques distintos:
La Media Teórica o Valor Esperado (\(E[X]\)): Se calcula utilizando exclusivamente las probabilidades teóricas. Multiplicamos cada valor posible de la variable aleatoria por su probabilidad matemática de ocurrir y sumamos los resultados. Nos da el centro ideal e inamovible del modelo (\(2,100\) caras).
La Media Muestral Habitual: Es el cálculo tradicional de la estadística descriptiva. Se toman los 100 datos individuales del experimento, se suman uno a uno y se divide el resultado entre el total de observaciones (\(n=100\)).
La Media Muestral Ponderada: Se calcula utilizando los valores de frecuencia (conteos). En lugar de sumar los 100 datos individualmente, agrupamos el problema multiplicando cada valor de la variable aleatoria por el número de veces que apareció en la muestra y dividiendo por \(n\).
Este ejercicio demuestra que la media habitual y la media ponderada ofrecen exactamente el mismo resultado empírico (\(2,120\) caras), ya que la frecuencia absoluta es simplemente un atajo aritmético para resumir la suma masiva de datos. Al mismo tiempo, al observar que este valor está sumamente cerca del \(2,100\) teórico, se evidencia cómo las frecuencias de una muestra real convergen con precisión hacia los valores esperados de la teoría de la probabilidad
3 Distribuciones de probabilidad de variables discretas
3.1 Probability Mass Function (PMF)
La Función de Masa de Probabilidad (PMF) describe matemáticamente cómo se distribuyen las probabilidades sobre los valores reales de una variable aleatoria discreta.
La PMF tiene la propiedad de asignar una probabilidad exacta y específica a cada uno de los valores posibles del sistema.
Se denota comúnmente como \(P(X = x)\), donde: * \(X\) (mayúscula) representa el nombre de la variable aleatoria. * \(x\) (minúscula) representa el valor numérico particular que estamos evaluando (por ejemplo, \(x = 5\)).
Para que una función sea considerada una PMF válida, debe cumplir estrictamente con dos axiomas fundamentales:
Primer axioma: Acotación: La probabilidad de cualquier valor particular debe ser realista; no existen probabilidades negativas ni superiores al 100%. \[0 \le P(X = x) \le 1\]Segundo axioma Exhaustividad: Si sumamos las probabilidades de todos y cada uno de los valores posibles que puede tomar la variable, el resultado debe ser exactamente el 100%. \[\sum P(X = x) = 1\]
Al lanzar dos dados, si la variable aleatoria discreta \(X\) es la suma de los dos dados. Los valores posibles van desde el 2 (sacar 1 y 1) hasta el 12 (sacar 6 y 6). Para este caso estas serian las probabilidades teoricas, se divide el número de casos favorables para cada valor de X entre el número de casos posibles.
Para cualquier valor \(x\) que sea imposible de obtener (por ejemplo, sacar una suma de \(x = 13\) o \(x = 5.5\)), la función devuelve un valor exacto de cero: \(P(X = 13) = 0\).
import randomfrom collections import Counterdef simular_dados(num_lanzamientos=100):""" Simula el lanzamiento de dos dados N veces y muestra una tabla comparativa entre la probabilidad simulada y la teórica (PMF). """ resultados_simulacion = []# Simular el lanzamiento de dos dadosfor _ inrange(num_lanzamientos): dado1 = random.randint(1, 6) dado2 = random.randint(1, 6) suma = dado1 + dado2 resultados_simulacion.append(suma) conteo = Counter(resultados_simulacion)# Probabilidades teóricas exactas (casos favorables / 36) prob_teorica_dict = {2: 1/36, 3: 2/36, 4: 3/36, 5: 4/36, 6: 5/36, 7: 6/36,8: 5/36, 9: 4/36, 10: 3/36, 11: 2/36, 12: 1/36 }# Imprimir cabecera con el número de lanzamientos actualprint(f"\n=== SIMULACIÓN CON {num_lanzamientos:,} LANZAMIENTOS ===")print(f"{'Suma (X)':<10} | {'Prob. Simulación':<18} | {'Prob. Teórica':<15}")print("-"*55)for suma inrange(2, 13): prob_sim = conteo[suma] / num_lanzamientos prob_teo = prob_teorica_dict[suma]print(f"{suma:<10} | {prob_sim*100:>15.2f}% | {prob_teo*100:>12.2f}%")simular_dados(10)simular_dados(20)simular_dados(50)simular_dados(100000)
Como se observa en los resultados impresos, cuando realizamos pocas repeticiones (10, 20 o 50 lanzamientos), la probabilidad simulada es inestable y se aleja de la teoría debido al “ruido” del azar. Sin embargo, a medida que se incrementa el número de experimentos, la frecuencia empírica converge y tiende a igualar la probabilidad teórica calculada.
Las barras muestran de forma visual cómo la “masa” de probabilidad no se reparte equitativamente, sino que se concentra de forma simétrica alrededor del valor central (7). Los huecos vacíos entre columnas nos recuerdan la naturaleza discreta de la variable:
import matplotlibimport matplotlib.pyplot as pltmatplotlib.rcParams['svg.fonttype'] ='none'# 1. Definir los datos de la PMF Teóricasumas =list(range(2, 12+1))# Casos favorables para cada suma (del 2 al 12) dividido entre 36 posiblesprobabilidades = [1/36, 2/36, 3/36, 4/36, 5/36, 6/36, 5/36, 4/36, 3/36, 2/36, 1/36]# Convertir a porcentajes para el eje Yporcentajes = [p *100for p in probabilidades]# 2. Crear el gráficoplt.figure(figsize=(5, 3), dpi=50)# plt.stem es perfecto para PMF porque dibuja líneas discretas con un marcador en la puntamarkerline, stemlines, baseline = plt.stem( sumas, porcentajes, linefmt='b-', # Línea azul sólida markerfmt='bo', # Punto azul en la punta basefmt='r-'# Línea base roja en el cero)# Ajustar el grosor de las líneas para que se vea más limpioplt.setp(stemlines, linewidth=2)plt.setp(markerline, markersize=8)# 3. Configurar los ejes y etiquetas# Forzar a que el eje X muestre exactamente los números del 2 al 12plt.xticks(sumas)# Añadir una cuadrícula horizontal suave para leer mejor los porcentajesplt.grid(axis='y', linestyle='--', alpha=0.5)# 4. Mostrar el gráfico en pantallaplt.tight_layout()plt.show()
3.2 Cumulative Distribution Function (CDF)
Si PMF (Función de Masa de Probabilidad) da la probabilidad de el valor de la variable aleatoria sea exactamente igual a un número (\(P(X = x)\)), la CDF o Función de Distribución Acumulada (Cumulative Distribution Function) te dice la probabilidad de que el valor de la variable aleatoria sea menor o igual a un valor determinado.
\[F(x) = P(X \le x)\]
En lugar de mirar solo una columna del gráfico, se va “acumulando” (sumando) la masa de probabilidad desde la izquierda hasta el punto deseado.
Imagina que estás jugando a un juego de mesa y necesitas sacar como máximo un 5 para salvarte. Te interesa saber \(F(5) = P(X \le 5)\). Para calcularlo, tu CDF lo que hace por detrás es sumar las probabilidades exactas de tu lista:
import matplotlibimport matplotlib.pyplot as pltimport numpy as np # Usamos numpy para calcular la suma acumulada fácilmentematplotlib.rcParams['svg.fonttype'] ='none'# 1. Definir los datos basesumas =list(range(2, 13))probabilidades = [1/36, 2/36, 3/36, 4/36, 5/36, 6/36, 5/36, 4/36, 3/36, 2/36, 1/36]# Convertir a porcentajes y CALCULAR LA ACUMULADA (CDF)porcentajes_pmf = [p *100for p in probabilidades]porcentajes_cdf = np.cumsum(porcentajes_pmf) # Va sumando: 2.78, 8.33, 16.67...# 2. Crear el gráficoplt.figure(figsize=(5, 3), dpi=50)# plt.step dibuja la "escalera" perfecta para una CDF discreta# where='post' asegura que el salto de probabilidad ocurra justo en el número enteroplt.step( sumas, porcentajes_cdf, where='post', color='b', linewidth=2, label='CDF Teórica')# Añadimos los puntos (marcadores) en cada escalón para que se vea el valor exactoplt.plot(sumas, porcentajes_cdf, 'bo', markersize=6)# 3. Configurar los ejes y etiquetasplt.xticks(sumas)# Forzamos que el eje Y vaya de 0% a 100% de forma limpiaplt.ylim(-5, 105) plt.grid(axis='y', linestyle='--', alpha=0.5)# 4. Guardar en formato SVG ultra ligero y mostrarplt.tight_layout()plt.show()
4 Distribuciones de probabilidad de variables continuas
4.1 Probability Density Function
En el caso de las variables continuas (como el peso, la estatura, el tiempo o la distancia), el hecho de que puedan tomar cualquier valor dentro de un rango implica que, aunque dicho rango sea limitado, el número de valores potenciales es, en realidad, infinito.
La regla de Laplace nos dice que tenemos que dividir \(1\) (nuestro caso exacto) entre \(\infty\) (los casos posibles). Y en matemáticas, cualquier número dividido entre infinito tiende a cero, por eso la probabilidad en un punto exacto es cero.
\[P(X = x) = 0\]
Como la probabilidad en un punto exacto ya no nos sirve porque siempre es cero, dejamos de usar la PMF (Masa) y pasamos a usar la PDF (en inglés, Probability Density Function o Función de Densidad de Probabilidad).
En las variables continuas, para obtener una probabilidad real que no sea cero, siempre tenemos que preguntar por un rango o intervalo (por ejemplo, ¿cuál es la probabilidad de que alguien mida entre 1.74 y 1.76 metros?).
Imagina que te sientas en la cafetería con un cronómetro y registras cuánto tarda el cajero en atender a 1,000 clientes. Al final del mes, tienes una lista de 1,000 tiempos en minutos: [0.5, 1.2, 4.3, 2.1, 0.8, 1.5, …]. Para ver qué forma tienen tus datos, creas un histograma con intervalos: cuentas cuántos clientes tardaron entre 0 y 1 minuto, cuántos entre 1 y 2, cuántos entre 2 y 3, etc.Al graficarlo, ves que 500 personas fueron atendidas en menos de un minuto, 250 entre el minuto 1 y 2, 125 entre el 2 y el 3…
Si sumas las personas que cayeron en el intervalo de 1 a 3 minutos (250 + 125 = 375 personas) y lo divides entre el total (1,000), tus datos te dicen: \[P(1 \le X \le 3) = \frac{375}{1000} = \mathbf{37.5\%}\]
Lo que calculamos es la probabilidad dentro de un intervalo y no un valor exacto.
El script genera una lista de 1,000 números que simulan tus datos de Excel, busca cuáles están entre 1 y 3, calcula el porcentaje de forma manual y dibuja las barras pintando de naranja el intervalo.
import matplotlibimport matplotlib.pyplot as pltimport numpy as npimport scipy.stats as stats# Configuración SVG peso plumamatplotlib.rcParams['svg.fonttype'] ='none'# 1. GENERAR LOS DATOS (Rango de 1 a 5 minutos)RANGO_MINIMO =1.0RANGO_MAXIMO =5.0media_cafeteria=2interes_min =2.0interes_max =3.5rng = np.random.default_rng(42)datos_excel = rng.exponential(scale=media_cafeteria, size=1000)total_clientes =len(datos_excel)# Crear un lienzo horizontal con 3 sub-gráficos (1 fila, 3 columnas)fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 4.5), dpi=60)# El truco para que los histogramas tengan la misma escala que la curva de área# es usar density=True. Así el eje Y mide "Densidad" en los tres gráficos.intervalos_1 = np.arange(RANGO_MINIMO, RANGO_MAXIMO +1.0, 1.0)conteos, bins, parches = ax1.hist(datos_excel, bins=intervalos_1, density=True, color='gainsboro', edgecolor='white')for i inrange(len(bins) -1):if bins[i] >= interes_min and bins[i+1] <= interes_max: parches[i].set_facecolor('orange')ax1.set_title('1. Barras Gruesas\n(Poca Granularidad)', fontsize=11, fontweight='bold')ax1.set_ylabel('Densidad de Probabilidad')intervalos_2 = np.arange(RANGO_MINIMO, RANGO_MAXIMO +0.05, 0.05)conteos, bins, parches = ax2.hist(datos_excel, bins=intervalos_2, density=True, color='gainsboro', edgecolor='white', linewidth=0.2)for i inrange(len(bins) -1):if bins[i] >= interes_min and bins[i+1] <= interes_max: parches[i].set_facecolor('orange')ax2.set_title('2. Barras Finas\n(Mucha Granularidad)', fontsize=11, fontweight='bold')ax2.set_xlabel('Tiempo de atención (minutos)', fontsize=10)kde = stats.gaussian_kde(datos_excel)x_suelo = np.linspace(RANGO_MINIMO, RANGO_MAXIMO, 500)y_densidad = kde(x_suelo)ax3.plot(x_suelo, y_densidad, color='darkgray', linewidth=1.5)ax3.fill_between(x_suelo, y_densidad, color='gainsboro', alpha=0.4)# Pintar el bloque de áreax_intervalo = np.linspace(interes_min, interes_max, 200)y_intervalo = kde(x_intervalo)ax3.fill_between(x_intervalo, y_intervalo, color='orange', alpha=0.8)ax3.set_title('3. Área Continua\n(Granularidad Infinita)', fontsize=11, fontweight='bold')# Configuración común para que los tres se vean exactamente igualesfor ax in [ax1, ax2, ax3]: ax.set_xlim(RANGO_MINIMO -0.2, RANGO_MAXIMO +0.2) ax.set_ylim(0, 0.35) # Misma escala vertical para notar el cambio ax.grid(axis='y', linestyle='--', alpha=0.3)plt.tight_layout()plt.show()
Si reduces el ancho de las barras hasta el infinito, las líneas verticales desaparecen por completo. Las barras se fusionan y se convierten en un gráfico de área. El área total es de 1 que representa el 100% de la probabilidad total.
Matemáticamente, esa probabilidad ya no se calcula sumando columnas con un sumatorio (∑), sino calculando el área bajo la curva entre esos dos puntos mediante una integral:
\[P(a \le X \le b) = \int_{a}^{b} f(x) \,dx\]
La Altura (\(f(x)\)): Es la función, la línea curva del ária. Cambia según dónde mires.
La Base (\(dx\)): Es el ancho de la tirita. Como es una tirita infinitamente delgada, los matemáticos la llaman \(dx\) (que significa “un diferencial de \(x\)”, o sea, un trocito microscópico de la base \(x\)).
Imagina que la PDF de tu cafetería es una rampa que va subiendo en lugar de bajar. La fórmula de esa curva es:\[f(x) = 2x\], esta seria la función que repsenta la curva del gráfico de área. ¿Cuál es la probabilidad de que un cliente tarde entre 0.5 y 1 minuto?
\[P(0.5 \le X \le 1) = \int_{0.5}^{1} 2x \,dx\]
\[P(0.5 \le X \le 1) = [x^2]_{0.5}^{1}\]
Ahora, el último paso de la integral es hacer la resta de los dos puntos (el límite de arriba menos el límite de abajo). Sustituimos los minutos en nuestra nueva fórmula:Evaluamos en el punto final (1): \(1^2 = \mathbf{1}\)Evaluamos en el punto inicial (0.5): \(0.5^2 = \mathbf{0.25}\)Hacemos la resta mágica de la integral:
\[\text{Área del trozo} = 1 - 0.25 = \mathbf{0.75}\]
import scipy.integrate as integrate# 1. Definimos la función de nuestra montaña (f(x) = 2x)def f(x):return2* x# 2. Le pedimos a Python que haga la integral (el área bajo la curva)# Le pasamos: la función (f), el punto de inicio (0.5) y el punto final (1)resultado, error = integrate.quad(f, 0.5, 1)# 3. Mostramos el resultado por pantallaprint(f"La probabilidad (área) calculada por Python es: {resultado}")print(f"En porcentaje es el: {resultado *100:.0f}%")
La probabilidad (área) calculada por Python es: 0.75
En porcentaje es el: 75%
Una integral es, literalmente, una máquina de medir el área de un trozo de gráfico.
El Gráfico (PDF) te dice dónde se acumulan los datos
El Intervalo son las dos paredes donde pones el ojo.
La Integral es la herramienta que mide el área de esa porción.
El Área resultante es la probabilidad que estás buscando.
En variables continuas también se puede usar Cumulative Distribution Function (CDF). Con la PDF (el gráfico de área), tendrías que calcular el área del bloque naranja usando una integral. Con la CDF, el proceso es visualmente brillante:
Buscas en la CDF cuánta arena se ha acumulado hasta el minuto 3.5 (\(F(3.5)\)). Supongamos que es el \(62.5\%\).
Buscas cuánta arena se había acumulado hasta el minuto 2 (\(F(2)\)). Supongamos que es el \(25\%\).
Haces una resta: Le restas al bloque grande el bloque pequeño.
Toda distribución cuenta con una función base \(f(x)\):
PMF (Masa): Si la variable es discreta.
PDF (Densidad): Si la variable es continua.
En ambos casos, la CDF (Acumulada) está disponible para integrar o sumar los valores de forma consecutiva.
Imagina que en tu cafetería ahora mides dos variables continuas de cada cliente:
Variable \(X\): El tiempo que tarda en decidir qué pedir (entre 0 y 1 minuto).
Variable \(Y\): El tiempo que tarda el cajero en prepararle el café (entre 0 y 1 minuto).
Tu equipo de datos ha analizado los miles de clientes del histórico y ha encontrado la función teórica (la PDF conjunta) que describe este comportamiento en 3D. La fórmula de la montaña es:
\[f(x, y) = 4xy\]
Esta fórmula dibuja una superficie en 3D sobre el suelo. Si calculáramos el volumen de toda esta montaña entre 0 y 1 para ambas variables, el volumen total sería exactamente 1 (el 100% de los clientes). ¿Cuál es la probabilidad de que un cliente tarde menos de medio minuto (0.5) en decidir (\(X\)) Y ADEMÁS el cajero tarde menos de medio minuto (0.5) en prepararlo (\(Y\))?
\[P(0 \le X \le 0.5, 0 \le Y \le 0.5) = \int_{0}^{0.5} \int_{0}^{0.5} 4xy \,dx\,dy\]
import scipy.integrate as integrate# 1. Definimos la función de la montaña en 3D (f(x, y) = 4 * x * y)# Nota: SciPy requiere que pongas primero la 'y' y luego la 'x' en los argumentosdef f(y, x):return4* x * y# 2. Definimos los límites de integración# Para X: va de 0 a 0.5x_min, x_max =0, 0.5# Para Y: va de 0 a 0.5y_min, y_max =0, 0.5# 3. Le pedimos a Python que calcule la Integral Doble (el volumen)# dblquad necesita: la función, límites de X (numéricos), y los límites de Y# (que en este caso simple también son números fijos)volumen, error = integrate.dblquad(f, x_min, x_max, lambda x: y_min, lambda x: y_max)# 4. Mostramos el resultadoprint(f"El volumen del trozo (probabilidad) es: {volumen}")print(f"En porcentaje: {volumen *100:.2f}%")
El volumen del trozo (probabilidad) es: 0.06249999999999999
En porcentaje: 6.25%
Si lo piensas de forma analítica, resolver esta integral doble a mano significa integrar primero respecto a una variable y luego respecto a la otra:
La integral respecto a \(x\) de \(4xy\) se convierte en \(2x^2y\). Evaluando de 0 a 0.5 nos da \(0.5y\).
Luego integramos ese resultado respecto a \(y\): la integral de \(0.5y\) es \(0.25y^2\). Evaluando de 0 a 0.5 nos da 0.0625.
El resultado nos dice que solo el 6.25% de tus clientes son tan ultra rápidos que tanto ellos como el cajero tardan menos de 30 segundos. El 93.75% restante cae en otras zonas de la pizza 3D (tardan más en decidir, o el cajero tarda más en preparar).
Esto mismo se podría calcular con más dimensiones.
import scipy.integrate as integrate# 1. Definimos una función de densidad con 4 variables (x1, x2, x3, x4)# Imaginemos que la fórmula matemática "inventada" es esta:def f(x1, x2, x3, x4):return x1 + x2 + x3 + x4# 2. Definimos los límites para cada una de las 4 variables# Queremos el intervalo [0, 1] para las cuatro dimensiones a la vezlimites = [ [0, 1], # Límites para x1 [0, 1], # Límites para x2 [0, 1], # Límites para x3 [0, 1] # Límites para x4]# 3. Python calcula la integral de 4 dimensiones (nquad) de un solo golpe# Solo necesita la función y la lista de límites organizadahipervolumen, error = integrate.nquad(f, limites)# 4. Mostramos el resultadoprint(f"El hipervolumen calculado es: {hipervolumen}")print(f"La probabilidad de este rango de 4D es: {hipervolumen *100:.2f}%")
El hipervolumen calculado es: 2.0
La probabilidad de este rango de 4D es: 200.00%
5 Distribución de Bernoulli
La Distribución de Bernoulli es un modelo estadístico que se aplica exclusivamente a variables aleatorias discretas donde solo existen dos posibles resultados: el éxito (que por convención matemática se representa con el número 1) y el fracaso (que se representa con el número 0).
El caso más obvio de esta distribución es el lanzamiento de una moneda, donde los únicos resultados posibles son cara o cruz. Dependiendo de nuestros intereses, podemos definir el “éxito” como sacar cara, o bien considerar el “éxito” como sacar cruz.
Sin embargo, un experimento con más opciones iniciales, como el lanzamiento de un dado de seis caras, también puede transformarse y seguir esta distribución. Esto ocurre si cambiamos el enfoque y definimos el éxito únicamente como “sacar un 1”, y el fracaso como “sacar cualquier otro número” (2, 3, 4, 5 o 6).
La Función de Masa de Probabilidad (PMF) de este modelo se representa mediante una fórmula matemática que utiliza dos variables principales: \(p\) (la probabilidad de que ocurra el éxito) y \(k\) (el resultado numérico que queremos evaluar, el cual solo puede valer 0 o 1).
\[P(X = k) = p^k \cdot (1-p)^{1-k}\]
Si sustituyes \(k=1\) (Éxito): El exponente del fracaso se vuelve \(0\) (\((1-p)^0 = 1\)), desaparece esa parte y la fórmula te da simplemente \(p\).
Si sustituyes \(k=0\) (Fracaso): El exponente del éxito se vuelve \(0\) (\(p^0 = 1\)), desaparece esa parte y la fórmula te da simplemente \(1-p\).
import scipy.stats as stats# En tu dado, el éxito (sacar un 1) tiene 1 caso favorable de 6 totalesp_exito =1/6# aprox 0.1666 (16.67%)# Creamos la distribución de Bernoulli en Pythonbernoulli_dado = stats.bernoulli(p=p_exito)# Evaluamos la PMF sustituyendo k por 1 (Éxito) y por 0 (Fracaso)prob_exito = bernoulli_dado.pmf(k=1)prob_fracaso = bernoulli_dado.pmf(k=0)print(f"Probabilidad de ÉXITO (sacar un 1): {prob_exito *100:.2f}%")print(f"Probabilidad de FRACASO (no sacar un 1): {prob_fracaso *100:.2f}%")
Probabilidad de ÉXITO (sacar un 1): 16.67%
Probabilidad de FRACASO (no sacar un 1): 83.33%
Si lanzas el dado una sola vez, vas a sacar un 0 o un 1, es decir, un valor “k” de éxito o de fracaso. No puedes sacar un 0.16 por ejemplo. Pero imagina que lanzas ese dado millones de veces y apuntas los ceros y unos que obtienes. Si haces el promedio (la media) de todos esos millones de lanzamientos, la regla matemática nos dice esto:
La moda es el valor que más se repite, o lo que es lo mismo en una distribución: el valor que tiene la probabilidad más alta. Aquí solo hay dos posibilidades: el 0 y el 1. El que tenga la barra más alta en el gráfico es la moda.
Si la probabilidad de fracaso (\(1-p\)) es mayor que la de éxito (\(p\)), la moda es 0.
Si la probabilidad de éxito (\(p\)) es mayor que la de fracaso (\(1-p\)), la moda es 1.
La mediana es el valor que parte tus datos en dos mitades del 50%. En Bernoulli, para encontrarla, miramos los porcentajes acumulados (la CDF) de izquierda a derecha:
Si la probabilidad de fracaso (\(1-p\)) es mayor al 50%, significa que cuando ordenes tus millones de respuestas, más de la mitad de la lista van a ser ceros. Por lo tanto, el número del centro exacto será un 0.
Si la probabilidad de fracaso (\(1-p\)) es menor al 50%, significa que los ceros no llegan a cubrir la mitad de la lista, por lo que el centro ya estará inundado de unos. La mediana será 1.(Si es exactamente 50% y 50%, como una moneda perfecta, la mediana es el promedio de ambos: 0.5).
6 Distribución Binomial
La distribución Binomoial describe el número de éxitos en una secuencia de “n” experimentos de Bernoulli. Por tanto, afecta a variables discretas y cada experimentos solo puede tener dos resultados: éxito o fracaso.
Si Bernoulli es lanzar una sola moneda (un experimento con éxito o fracaso), la Binomial es lanzar \(n\) monedas a la vez (o lanzar la misma moneda \(n\) veces) y contar cuántos éxitos obtienes en total.
Lanzas el dado 1 sola vez (\(n = 1\)).
Éxito (\(1\)): Sacar el número 1 (Probabilidad \(p = 1/6\)).
Fracaso (\(0\)): No sacar el número 1 (Probabilidad \(1-p = 5/6\)).
Esto es Bernoulli. El resultado solo puede ser un solo número: \(0\) o \(1\). No confundir con la probabiidad de obtener el resultado 1 o el resultado 0.
Ahora decides lanzar el dado 10 veces seguidas (\(n = 10\)). Cada uno de esos 10 lanzamientos por separado sigue siendo un experimento de Bernoulli independiente. Pero no importa el resultado de cada tiro individual, o que te interesa es el conteo final.
Defines tu nueva variable aleatoria \(X\) como: “El número total de veces que saqué un 1 en los 10 lanzamientos”.Ahora los resultados posibles ya no son solo 0 o 1. Puedes sacar el número “1” cero veces, 1 vez, 2 veces, 3 veces… o incluso las 10 veces. Binomial es la suma de \(n\) Bernoullis independientes, donde todos tienen la misma probabilidad de éxito \(p\).
Lanzamos el dado \(n = 10\) veces y queremos calcular la probabilidad exacta de sacar \(k = 3\) éxitos (es decir, que el número “1” salga exactamente 3 veces).La fórmula de la PMF Binomial es esta: \[P(X = k) = \binom{n}{k} \cdot p^k \cdot (1-p)^{n-k}\]
Vamos a dividir la resolución en dos bloques:
Bloque 1: Las probabilidades de los éxitos y los fracasos
Imagina que los 3 primeros tiros son un éxito y los 7 siguientes son un fracaso.
\(p^k\) (La probabilidad de los éxitos): Como queremos 3 éxitos, multiplicamos la probabilidad de éxito tres veces:
\((1-p)^{n-k}\) (La probabilidad de los fracasos): Si de los 10 tiros, 3 son éxitos, obligatoriamente los otros 7 (\(10 - 3 = 7\)) tienen que ser fracasos:
El cálculo anterior solo sirve si los 3 éxitos salen al principio. Pero en la vida real, los tres “1” pueden salir en cualquier combinación: en los tiros (1, 4, 8), en los tiros (2, 5, 9), o en los últimos tres.
El símbolo \(\binom{n}{k}\) se lee “n sobre k” y sirve para calcular de cuántas formas distintas podemos organizar esos 3 éxitos en los 10 huecos disponibles. Su fórmula matemática usa factoriales (\(!\)):
\[\binom{n}{k} = \frac{n!}{k!(n-k)!}\]
Vamos a calcularlo con tus números (\(10\) sobre \(3\)):\[\binom{10}{3} = \frac{10!}{3!(10-3)!} = \frac{10!}{3! \cdot 7!}\]
El truco para resolver factoriales a mano es desarrollar el número de arriba hasta que choque con el más grande de abajo para poder tacharlos:
Si lo pasamos a porcentaje, hay exactamente un \(15.50\%\) de probabilidad de que al lanzar el dado 10 veces, el número “1” aparezca exactamente 3 veces.
import scipy.stats as statsn_intentos =10# Lanzamos el dado 10 veces (n Bernoullis)p_exito =1/6# La probabilidad de éxito de cada Bernoulli individual# Creamos la distribución Binomialbinomial = stats.binom(n=n_intentos, p=p_exito)# Propiedades de la distribuciónmedia = binomial.mean()mediana = binomial.median()# Probabilidad exacta de sacar exactamente 3 veces el número "1" (PMF)prob_3_exitos = binomial.pmf(k=3)print(f"Media de éxitos esperados: {media:.2f}")print(f"Mediana de éxitos: {mediana}")print(f"Probabilidad de sacar exactamente tres '1': {prob_3_exitos *100:.2f}%")
Media de éxitos esperados: 1.67
Mediana de éxitos: 2.0
Probabilidad de sacar exactamente tres '1': 15.50%
7 Distribución de Poisson
La Distribución de Poisson (pronunciada Puassón) es otra distribución para variables discretas, pero sirve para contar el número de veces que ocurre un evento raro dentro de un intervalo continuo. Mientras que en la Binomial se conocen el número de intentos totales (\(n\)), en Poisson no hay un número máximo de intentos. Solo sabes con qué frecuencia suele pasar ese evento en promedio.
Imagina que quieres calcular la probabilidad de cuántos clientes van a entrar por la puerta entre las 10:00 y las 11:00 de la mañana.¿Por qué no usamos la Binomial? Porque no existe un número \(n\) de “intentos de entrar a la cafetería”. Toda la población de tu ciudad podría potencialmente entrar. El número de intentos es, en teoría, infinito.
¿Cuándo usamos Poisson? Cuando miras el histórico y dices: “Oye, sé que en promedio, los jueves a esta hora suelen entrar \(\lambda = 8\) clientes”.Ese promedio o tasa media se representa con la letra griega lambda (\(\lambda\)). Y a partir de ese único dato, Poisson hace su magia.
La fórmula de la PMF de Poisson para saber la probabilidad de que ocurran exactamente \(k\) eventos es:
Resultado: Hay un \(9.16\%\) de probabilidad de que entren exactamente 5 clientes en esa hora.
import scipy.stats as stats# Definimos nuestra tasa media de clientes por horamedia_lambda =8# Creamos la distribución de Poissonpoisson_cafeteria = stats.poisson(mu=media_lambda)# Calculamos la probabilidad exacta para k = 5 clientesprob_5 = poisson_cafeteria.pmf(k=5)print(f"Media: {poisson_cafeteria.mean()}")print(f"Mediana: {poisson_cafeteria.median()}")print(f"Probabilidad de que vengan exactamente 5 clientes: {prob_5 *100:.2f}%")
Media: 8.0
Mediana: 8.0
Probabilidad de que vengan exactamente 5 clientes: 9.16%
El hecho de que ocurra un evento en un instante no puede alterar la probabilidad de que ocurra otro evento en el instante siguiente. En una cafeteria, que Juan entre a las 10:05 a por un café es totalmente independiente de que Marta entre a las 10:06. Los datos fluyen de forma libre.
El promedio de eventos por unidad de tiempo debe ser estable a lo largo de todo el intervalo que estás midiendo. Si mides el intervalo de 10:00 a 11:00 de la mañana de un martes normal, asumes que la intensidad de llegada de la gente se mantiene más o menos constante a una tasa de, por ejemplo, 8 clientes/hora. No puedes meter en el mismo saco de Poisson los datos de las 10:00 AM (hora punta) con los datos de las 3:00 AM (cafetería cerrada o vacía). Si tu promedio cambia drásticamente según la hora, necesitas romper tus datos en ventanas de tiempo más pequeñas donde \(\lambda\) sí sea constante.
Si tienes un conjunto de datos en Python (una columna en un DataFrame de Pandas) y quieres saber en un segundo si tiene estructura de Poisson, hay una propiedad matemática que los delata de inmediato, “la media debe ser igual a la varianza”. Cuando calculas la media y la varianza, lo que estás comprobando es si la dispersión o variabilidad de los datos coincide con el promedio.
Entonces si en el intervalo de tiempo quiero saber si puedo apicar Poisson para calcular la probabilidad de que ocurra algo, debo asegurar que la media y la varianza de los datos en ese intervalo sea muy similar o igual.
Imagina que quieres aplicar Poisson para calcular la probabilidad de averías en las máquinas de una fábrica en el intervalo de 1 día. Para asegurarte de que puedes aplicarlo, vas a tu histórico de datos y extraes una muestra de los últimos 15 días:
Datos por día: [2, 3, 1, 2, 4, 2, 1, 3, 2, 2, 3, 1, 2, 4, 2]
Si calculas la media y la varianza de esa lista de días, verás que la media es 2.26 averías/día y la varianza es 0.92. Son números de la misma magnitud, están cerca. ¡Perfecto! Tus datos pasan el filtro y puedes usar Poisson usando como \(\lambda = 2.26\).
Ahora mira este otro histórico de otra fábrica diferente, midiendo exactamente en el mismo intervalo de 1 día:
Datos por día: [0, 0, 1, 0, 18, 0, 0, 1, 0, 12, 0, 1, 0, 0, 1]
Si calculas la media de esta segunda lista, el resultado vuelve a ser 2.26 averías/día. Si te fijaras solo en la media, dirías: “¡Ah, pues uso la misma fórmula de Poisson con \(\lambda = 2.26\)!”.Pero mira la varianza: La varianza de estos datos es 31.3. ¡Es casi 14 veces más grande que la media!
“¿Cuál es la probabilidad de que un día tengamos 5 o más averías?” La matemática te dirá, por ejemplo, que hay un 8% de probabilidad. Entonces tú decides: “Para estar cubierto al 95% de los escenarios posibles, el almacén debe tener siempre un stock mínimo de 6 piezas”. Poisson te da el número exacto para no gastar de más en almacén pero jamás quedarte desabastecido.
8 Distribución normal
La Distribución Normal es un modelo de probabilidad continua que se aplica exclusivamente a variables continuas. Visualmente, se caracteriza por tener una forma de campana, popularmente conocida como la Curva de Bell o Campana de Gauss.
Esta curva es simétrica, lo que implica una propiedad fundamental: su media, su mediana y su moda son valores idénticos o similares en datos reales.El hecho de que la mediana se ubique exactamente en el mismo lugar que la media implica que el \(50\%\) de los datos se distribuye a la izquierda de la curva (valores por debajo del promedio) y el otro \(50\%\) se distribuye a la derecha (valores por encima).
La estructura matemática de esta distribución se expresa mediante dos parámetros: la media (que determina la ubicación del centro de la campana) y la varianza (que determina su forma o anchura). A medida que aumenta la varianza, la curva experimenta un ensanchamiento y un aplanamiento. Este cambio visual indica directamente una mayor dispersión en los datos, lo que significa que los valores se alejan del promedio y la variabilidad del fenómeno analizado es mucho mayor.
La PDF (Función de Densidad de Probabilidad) de la Distribución Normal es:
\(\mu\) (Media): Observa la resta \((x - \mu)\). Si metes en la fórmula un valor de \(x\) que sea exactamente igual a la media, esta resta da \(0\). Al elevar \(e^0\), esa parte se convierte en \(1\) que es el valor máximo. A medida que \(x\) se aleja de la media, la resta se hace más grande, el exponente se vuelve más negativo y la curva cae simétricamente hacia el suelo.
\(\sigma\) (Desviación estándar / Raíz de la Varianza): Está dividiendo abajo de la resta. Si la varianza (\(\sigma^2\)) aumenta, el número \(\sigma\) es más grande, lo que amortigua la caída del exponente y hace que la campana se ensanche.
En esta parte se está utilizando el valor Z, que indica a cuántas desviaciones estándar de distancia se encuentra un valor respecto de la media.
\[Z = \frac{x - \mu}{\sigma}\]
Ahora el normalizador.
\(\frac{1}{\sigma \sqrt{2\pi}}\)¿
Si solo usáramos el bloque de la derecha, el área bajo la curva no daría 1. Esta fracción de la izquierda actúa como un “regulador de volumen”. Su único trabajo es encoger o estirar la campana verticalmente para asegurarse de que, sin importar cuánto se ensanche o se mueva, el área total encerrada bajo la curva sea exactamente igual a 1.
Por ejemplo, si tienes una población con una media de estatura \(\mu = 170\text{ cm}\) y una desviación estándar \(\sigma = 10\text{ cm}\), y quieres saber la altura de la campana justo en el punto de los \(180\text{ cm}\):
import scipy.stats as statsmedia_mu =170desviacion_sigma =10# Creamos la distribución normal con nuestros parámetrosdistribucion = stats.norm(loc=media_mu, scale=desviacion_sigma)# Calculamos el valor de la PDF para x = 180altura_campana = distribucion.pdf(180)probabilidad_acumulada = distribucion.cdf(180)print(f"La altura de la curva (densidad) en 180cm es: {altura_campana:.5f}")print(f"La probabilidad acumulada de llegar hasta 180 cm es: {probabilidad_acumulada *100:.2f}%")
La altura de la curva (densidad) en 180cm es: 0.02420
La probabilidad acumulada de llegar hasta 180 cm es: 84.13%
Hay una propiedad matemática universal que cumple toda distribución normal, se llama la Regla 68-95-99.7. Si tomas la media de tus datos y te mueves hacia los lados usando la desviación estándar (\(\sigma\)): * Entre la media \(\pm 1\) desviación estándar, se encuentra el \(68.2\%\) de todos tus datos. * Entre la media \(\pm 2\) desviaciones estándar, se encuentra el \(95.4\%\) de los datos. * Entre la media \(\pm 3\) desviaciones estándar, tienes el \(99.7\%\) de los datos. Lo que quede fuera de aquí son casos raros o anomalías.
El Test de Shapiro-Wilk devuelve un valor llamado p-value. Si el p-value es mayor a 0.05, los datos siguen una distribución normal
La distribución normal estándar es aquella que tiene como media 0 y como desviación estándar 1. Por tanto la varianza también es de 1.
Cualquier set de datos que siguen una distribución normal se pueden convertir a una distribución normal estandard. Para ello se realiza un proceso de estandarización, dónde se aplica a cada valor la fomrula de Z.
\[Z = \frac{x - \mu}{\sigma}\]
Consiguiendo por tanto un nuevo set de datos con media 0 y varianza junto adesviació estandard de 1.
import numpy as npimport matplotlib.pyplot as pltimport scipy.stats as statsplt.rcParams['svg.fonttype'] ='none'# Configuración de los datos (Reducimos a solo 40 puntos para aligerar el SVG)media_orig, desviacion_orig =170, 10media_est, desviacion_est =0, 1x_orig = np.linspace(135, 205, 40)x_est = np.linspace(-3.5, 3.5, 40)fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))# --- 1. GRÁFICO ORIGINAL ---pdf_o = stats.norm.pdf(x_orig, media_orig, desviacion_orig)ax1.plot(x_orig, pdf_o, color='#1e3d59', linewidth=2)# Sombreado ultra ligero (solo 15 puntos de resolución para el polígono)x_acum_o = np.linspace(135, 180, 15)ax1.fill_between(x_acum_o, stats.norm.pdf(x_acum_o, media_orig, desviacion_orig), color='#1e3d59', alpha=0.1)ax1.axvline(x=180, color='#ff6e40', linestyle=':', linewidth=2)ax1.set_title('Escala Original (cm)', fontsize=11, color='#333333', pad=10)ax1.set_xticks([140, 170, 180, 200])ax1.set_xlim(135, 205)ax1.yaxis.set_visible(False)for spine in ['top', 'right', 'left']: ax1.spines[spine].set_visible(False)ax1.spines['bottom'].set_color('#cccccc')# --- 2. GRÁFICO ESTÁNDAR ---pdf_e = stats.norm.pdf(x_est, media_est, desviacion_est)ax2.plot(x_est, pdf_e, color='#17b978', linewidth=2)# Sombreado ultra ligero (solo 15 puntos de resolución para el polígono)x_acum_e = np.linspace(-3.5, 1, 15)ax2.fill_between(x_acum_e, stats.norm.pdf(x_acum_e, media_est, desviacion_est), color='#17b978', alpha=0.1)ax2.axvline(x=1, color='#ff6e40', linestyle=':', linewidth=2)ax2.set_title('Escala Estándar (Z)', fontsize=11, color='#333333', pad=10)ax2.set_xticks([-3, 0, 1, 3])ax2.set_xlim(-3.5, 3.5)ax2.yaxis.set_visible(False)for spine in ['top', 'right', 'left']: ax2.spines[spine].set_visible(False)ax2.spines['bottom'].set_color('#cccccc')plt.tight_layout()# Guardar directamente en SVG optimizado (elimina metadatos basura)# plt.savefig('grafico_ligero.svg', format='svg', bbox_inches='tight')plt.show()
9 Distribución uniforme
Existen dos tipos de distribución uniforme:
Distribución uniforme continua
Distribución uniforme discreta
La diferencia entre una y otra es el tipo de variable aleatoria que se utiliza, en un caso es discreta y en el otro continua entonces esto afecta a qué tipo de función de distribución se utiliza, si PMF o PDF.
En este tipo de distribución todos los resultados posibles tienen exactamente la misma probabilidad de ocurrir.
9.1 Distribución uniforme continua
Se aplica a variables discretas. Como el número de resultados posibles es finito y conocido, puedes asignar una probabilidad exacta a cada uno usando la PMF.
La fórmula de la PMF: Si tienes \(n\) resultados posibles, la probabilidad de que ocurra cualquiera de ellos es fija:
\[P(X = x) = \frac{1}{n}\]
El ejemplo clásico de un dado: Un dado de 6 caras tiene un conjunto discreto de resultados: \(\{1, 2, 3, 4, 5, 6\}\). Hay \(n = 6\) opciones. La probabilidad de sacar un 3 es \(\frac{1}{6}\), y la de sacar un 6 también es \(\frac{1}{6}\).
9.2 Distribución Uniforme Continua
Se aplica a variables continuas, pueden tomar cualquier valor con infinitos decimales dentro de un rango determinado entre un mínimo \(a\) y un máximo \(b\).
La fórmula de la PDF: La altura de la distribución es una línea recta horizontal constante entre el punto \(a\) y el punto \(b\):
\[f(x) = \frac{1}{b - a}\]
El ejemplo clásico de la espera del autobús: Sabes que el autobús pasa exactamente cada 15 minutos, pero no tienes reloj. Llegas a la parada en un momento aleatorio. Tu tiempo de espera (\(X\)) es una variable continua entre \(a = 0\) y \(b = 15\) minutos. La probabilidad de esperar exactamente 7 minutos y 2 segundos es cero, pero la PDF te permite calcular áreas, como la probabilidad de esperar entre 5 y 10 minutos.
10 Distribución log-normal
Es una distribución de probabilidad continua de una variable aleatoria continua cuyo logaritmo natural sigue una distribución normal
Su característica principal es que, si aplicamos el logaritmo natural (\(\ln\)) a la variable aleatoria, los valores resultantes pasan a seguir una Distribución Normal tradicional.
La distribución en su origen es una curva totalmente asimétrica y “sesgada” hacia la derecha. Entonces, la curva log-normal tiene una fuerte asimetría positiva (una cola larga hacia la derecha), lo que la hace ideal para modelar datos donde los valores pequeños son muy comunes pero existen valores extremadamente grandes y poco frecuentes (como los ingresos económicos).
¿Qué hace el logaritmo? Tiene la propiedad de encoger los números grandes y estirar los pequeños.
Imagina que analizas los servidores de tu empresa y descubres que el tiempo de carga sigue una distribución Log-Normal.Cuando trabajamos con la Log-Normal, los parámetros de la media (\(\mu\)) y la desviación estándar (\(\sigma\)) no se refieren a los segundos reales, sino a los datos después de aplicarles el logaritmo.
Digamos que los parámetros de nuestra web son:
\(\mu\) (media del logaritmo) = \(0.5\)
\(\sigma\) (desviación del logaritmo) = \(0.6\)
import numpy as npimport scipy.stats as stats# Parámetros de nuestro logaritmomu =0.5sigma =0.6# Pasamos los parámetros al formato que entiende SciPys = sigmascale = np.exp(mu)# Punto que queremos evaluar (3 segundos)segundos =3# 1. Calcular la altura de la curva (PDF) - NO es probabilidadaltura_pdf = stats.lognorm.pdf(segundos, s=s, scale=scale)# 2. Calcular la Probabilidad Acumulada (CDF) - SÍ es probabilidad (mide 3s o menos)prob_acumulada = stats.lognorm.cdf(segundos, s=s, scale=scale)print(f"Altura de la curva (PDF) a los 3 segundos: {altura_pdf:.4f}")print(f"Probabilidad de que cargue en 3 segundos o menos (CDF): {prob_acumulada *100:.2f}%")
Altura de la curva (PDF) a los 3 segundos: 0.1347
Probabilidad de que cargue en 3 segundos o menos (CDF): 84.08%
Esto significa que, bajo este modelo, tienes un 65% de certeza de que tus usuarios experimentarán una carga rápida de 3 segundos o menos, mientras que el 35% restante sufrirá cargas más lentas debido a esa cola larga de la distribución.
import numpy as npimport matplotlib.pyplot as pltimport scipy.stats as statsplt.rcParams['svg.fonttype'] ='none'# Parámetros base del logaritmomu, sigma =0.5, 0.6s = sigmascale = np.exp(mu)# Configuración de los ejes (Solo 40 puntos de resolución para aligerar el SVG)x_lognorm = np.linspace(0, 6, 40)x_norm = np.linspace(-1.5, 2.5, 40)fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))# --- 1. GRÁFICO LOG-NORMAL ORIGINAL (Tiempo Real en Segundos) ---pdf_lognorm = stats.lognorm.pdf(x_lognorm, s=s, scale=scale)ax1.plot(x_lognorm, pdf_lognorm, color='#d9534f', linewidth=2)# Sombreado ultra ligero hasta los 3 segundos (solo 15 puntos)x_acum_ln = np.linspace(0.01, 3, 15)ax1.fill_between(x_acum_ln, stats.norm.pdf(np.log(x_acum_ln), mu, sigma) / x_acum_ln, color='#d9534f', alpha=0.1)ax1.axvline(x=3, color='#333333', linestyle=':', linewidth=2)ax1.set_title('1. Escala Real: Tiempo de Carga (s)', fontsize=11, color='#333333', pad=10)ax1.set_xticks([0, 1.65, 3, 5]) # 1.65 es la moda aprox.ax1.set_xticklabels(['0s', 'Moda', '3s', '5s'])ax1.set_xlim(0, 6)ax1.yaxis.set_visible(False)for spine in ['top', 'right', 'left']: ax1.spines[spine].set_visible(False)ax1.spines['bottom'].set_color('#cccccc')# --- 2. GRÁFICO NORMAL TRANSFORMADO (Logaritmo de los Segundos) ---pdf_norm = stats.norm.pdf(x_norm, mu, sigma)ax2.plot(x_norm, pdf_norm, color='#2b5c8f', linewidth=2)# Sombreado ultra ligero hasta log(3) ≈ 1.098 (solo 15 puntos)x_acum_n = np.linspace(-1.5, np.log(3), 15)ax2.fill_between(x_acum_n, stats.norm.pdf(x_acum_n, mu, sigma), color='#2b5c8f', alpha=0.1)ax2.axvline(x=np.log(3), color='#333333', linestyle=':', linewidth=2)ax2.set_title('2. Escala Transformada: ln(Segundos)', fontsize=11, color='#333333', pad=10)ax2.set_xticks([-1, 0.5, np.log(3), 2])ax2.set_xticklabels(['-1', '0.5 (Media)', '1.1 (ln(3))', '2'])ax2.set_xlim(-1.5, 2.5)ax2.yaxis.set_visible(False)for spine in ['top', 'right', 'left']: ax2.spines[spine].set_visible(False)ax2.spines['bottom'].set_color('#cccccc')plt.tight_layout()plt.show()
11 Distribución de ley de potencias
Una ley de potencias describe una relación entre dos variables en la que un cambio relativo en una implica un cambio proporcional relativo en la otra. Esta relación presenta invariancia de escala, lo que significa que la forma funcional se mantiene al multiplicar la variable independiente por un factor constante.
Matemáticamente, si tenemos dos variables, \(y\) y \(x\), la relación se expresa mediante la siguiente ecuación:\[y = a \cdot x^k\]
\(x\) e \(y\) son las variables aleatorias o continuas que estás analizando.
\(a\) es una constante de proporcionalidad (un simple multiplicador).
\(k\) es el exponente de la potencia (el verdadero motor de la relación).
Las leyes de potencias aparecen en fenómenos naturales, económicos y sociales. Por ejemplo, en sismología la energía liberada por un terremoto crece aproximadamente un factor 32 por cada punto de magnitud, lo que explica por qué los eventos de magnitud alta son tan destructivos pese a ser poco frecuentes.
Cuando representamos una ley de potencias en ejes cartesianos, observamos una curva con cola pesada, donde la probabilidad de eventos extremos decrece lentamente según una función polinómica. Esto implica que los valores muy grandes, aunque raros, siguen teniendo una probabilidad significativa.
Si graficamos una Ley de Potencias en los ejes cartesianos tradicionales, observaremos una curva con una asimetría extrema y una cola pesada (heavy tail). Esta cola decrece de forma polinómica (muy lentamente), lo que explica por qué los eventos extremos (los pocos elementos de gran impacto) siguen teniendo una probabilidad real de ocurrir.
Para analizar estas distribuciones en Ciencia de Datos, aplicamos el logaritmo natural a ambos lados de la ecuación original:
\[\ln(y) = \ln(a) + k \cdot \ln(x)\]
En el ámbito social, económico y digital, la ley de potencias se manifiesta de forma muy popular a través del Principio de Pareto. Este principio describe que, en sistemas complejos, aproximadamente el 80% de los efectos provienen del 20% de las causas.
Cuando usamos la Ley de Potencias en estadística para medir probabilidades (que formalmente se conoce como Distribución de Pareto), nos enfocamos en el Caso 1 (el exponente negativo), porque es el que sirve para modelar cosas que van disminuyendo (como que hay poca gente con mucho dinero).
Imagina que tienes una plataforma de series, abres tu base de datos y extraes las 4 series más vistas de la semana. Los datos reales de consumo son estos:
Puesto 1: 745 horas reproducidas.
Puesto 2: 186 horas reproducidas.
Puesto 3: 83 horas reproducidas.
Puesto 4: 47 horas reproducidas.
Si intentas hacer un gráfico con esto, la serie del Puesto 1 (745 horas) se come toda la pantalla. Las series del puesto 3 y 4 se ven minúsculas y aplastadas en comparación. Es una curva en forma de “L” muy difícil de estudiar.
Para tratar datos tan distantes, le aplicamos el logaritmo natural tanto al puesto del ranking (\(x\)) como a las horas (\(y\)).Vamos a transformar los datos uno a uno:
Para el Puesto 1:
\(\ln(1) = \mathbf{0}\)
\(\ln(745) = \mathbf{6.61}\)
Para el Puesto 2:
\(\ln(2) = \mathbf{0.69}\)
\(\ln(186) = \mathbf{5.23}\)
Para el Puesto 3:
\(\ln(3) = \mathbf{1.10}\)
\(\ln(83) = \mathbf{4.42}\)
Para el Puesto 4:
\(\ln(4) = \mathbf{1.38}\)
\(\ln(47) = \mathbf{3.85}\)
Si calculamos el ritmo al que van bajando estos nuevos números (la pendiente), mira lo que pasa:
De la serie 1 a la serie 2: el eje X avanzó \(0.69\) y el eje Y bajó \(1.38\) (de \(6.61\) a \(5.23\)). \[\text{Ritmo de bajada} = \frac{-1.38}{0.69} = \mathbf{-2}\]
De la serie 3 a la serie 4: el eje X avanzó \(0.28\) (de \(1.10\) a \(1.38\)) y el eje Y bajó \(0.57\) (de \(4.42\) a \(3.85\)). \[\text{Ritmo de bajada} = \frac{-0.57}{0.28} = \mathbf{-2}\]
Aunque los números originales eran \(745, 186, 83 \dots\) y parecían no tener ninguna relación clara, al aplicarles el logaritmo descubrimos que todos bajan exactamente al mismo ritmo constante de \(-2\).
Con esto ahora podemos aproximar cuántas horas de reproducción tendrá la serie que quede en el puesto 10. Con los números sin procesar (\(745, 186, 83...\)) la curva cae tan rápido y de forma tan rara que no se pueden calcular bien. Procesandolos tenemos una linea recta que hace más fácil el cálculo.
Ahora vamos a calcular el número de horas aproximado de la serie que está en posición 10. Como vimos que el ritmo de bajada constante (la pendiente) es \(-2\), y la línea empieza en el valor del Puesto 1 (que es \(6.61\)), la ecuación de nuestra línea recta es:
Este número (\(2.01\)) es el resultado en el “mundo de los logaritmos” (la línea recta). Pero queremos saber horas reales.
\[\text{Horas} = e^{2.01} = \mathbf{7.46}\]
import numpy as npimport matplotlib.pyplot as pltplt.rcParams['svg.fonttype'] ='none'# Datos Realesranking = np.array([1, 2, 3, 4])horas_reales = np.array([745, 186, 83, 47])# Transformación Logarítmica (Los binoculares mágicos)log_ranking = np.log(ranking)log_horas = np.log(horas_reales)# Mostrar en consola los números transformados exactos del ejemplo anteriorprint("--- DATOS TRANSFORMADOS POR EL LOGARITMO ---")for r, h, lr, lh inzip(ranking, horas_reales, log_ranking, log_horas):print(f"Puesto {r} ({h} horas) -> ln(X) = {lr:.2f}, ln(Y) = {lh:.2f}")fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))# Gráfico Left: Los datos tal cualax1.scatter(ranking, horas_reales, color='red', s=80, zorder=5, label='Series reales')ax1.plot(ranking, horas_reales, color='red', linestyle='--', alpha=0.6)ax1.set_title("1. Escala Real (Datos Crudos)\nEl éxito aplasta a las demás")ax1.set_xlabel("Puesto en el Ranking")ax1.set_ylabel("Horas de Reproducción (en millones)")ax1.set_xticks(ranking)ax1.grid(True, alpha=0.3)ax1.legend()# Gráfico Right: Escala Log-Logax2.scatter(log_ranking, log_horas, color='blue', s=80, zorder=5, label='Datos transformados')# Dibujamos la línea recta que une los puntosax2.plot(log_ranking, log_horas, color='blue', alpha=0.6)ax2.set_title("2. Escala Log-Log (Transformados)\n¡Ritmo constante! Una línea recta perfecta")ax2.set_xlabel("ln(Ranking)")ax2.set_ylabel("ln(Horas)")ax2.grid(True, alpha=0.3)ax2.legend()plt.tight_layout()plt.show()
Si elijo una hora de reproducción al azar , ¿qué probabilidad hay de que pertenezca a una serie del Top 1 frente a una serie del Top 4?
Para que la PDF sea matemáticamente válida, el área total bajo su curva debe sumar \(1\) (es decir, el 100% de los datos). En nuestro ejemplo simplificado de las 4 series: Sumamos todas las horas del catálogo: \(745 + 186 + 83 + 47 = \mathbf{1061 \text{ horas}}\).
La PDF para cada puesto es simplemente el “peso” o la densidad de ese puesto sobre el total:PDF del Puesto 1: \(745 / 1061 = \mathbf{0.702}\)PDF del Puesto 2: \(186 / 1061 = \mathbf{0.175}\)PDF del Puesto 3: \(83 / 1061 = \mathbf{0.078}\)PDF del Puesto 4: \(47 / 1061 = \mathbf{0.044}\)
12 Teoría del Límite Central (CLT)
La distribución muestral de la media será aproximadamente normal, sin importar la distribución de la población original, siempre que el tamaño de la muestra sea suficientemente grande.
Imagina que la población original de la web de series, es esa Ley de Potencias donde casi todo el mundo ve la serie del Puesto 1 (745 horas) y casi nadie ve las demás.
Mandamos a un robot a preguntar a 30 usuarios al azar cuántas horas han visto esta semana.
Por pura probabilidad, unos 22 usuarios habrán visto la serie Top 1 (745 horas).
Unos 5 habrán visto la serie Top 2 (186 horas).
Unos 3 habrán visto las series bajas.
Si sumas todo y calculas la media de este primer grupo, te dará, por ejemplo, 550 horas. Mandamos al robot a hacer lo mismo con otro grupo de 30 personas, y luego con otro, y con otro… hasta tener 1.000 grupos distintos. Cada grupo tendrá su propia media:
El Grupo 2 da una media de 510 horas.
El Grupo 3 da una media de 580 horas.
El Grupo 4 da una media de 545 horas.
Si te olvidas de las personas individuales y haces un histograma solo con las medias de los 1.000 grupos, como resultado habrá una distribución normal de medias.
Los servidores de Netflix reciben millones de peticiones por segundo (datos salvajes tipo Ley de Potencias). Monitorizar cada petición individual es imposible y volvería loca a la computadora.
¿Qué hacen los ingenieros? Agrupan las peticiones en bloques de 5 minutos y calculan la velocidad media de cada bloque.Esas medias de 5 minutos forman una Campana de Gauss perfecta.
Los ingenieros configuran una alarma: “Si la velocidad media de un bloque cae a la zona extrema izquierda de la campana (donde la probabilidad es de solo 0.1%), avisa al equipo porque el servidor se está cayendo”.