Números Pseudoaleatorios · Tema 34

Sesgos en la generación

Desviaciones sistemáticas y sus causas: el catálogo de los 5 sesgos que ya viste, con firma y cura.

01 · Punto de partida

El error que no se diluye

El ruido se achica con N (Tema 1); el sesgo, no: es una desviación sistemática del valor esperado que persiste con N = 12 millones. Si tu dado da al 6 con prob. 0,18 en vez de 0,1667, ninguna réplica más lo arregla: hay que encontrar la causa en la cadena semilla → entero → mapeo → rango.

Este tema cosecha lo sembrado en los Temas 19–23 y 33: los 5 sesgos clásicos con su firma (cómo se ve) y su cura (cómo se arregla).

  • ¿Cómo distingo sesgo de mala suerte?
  • ¿Cuáles son las 5 causas y en qué eslabón viven?
  • ¿Qué firma deja cada uno en barras y χ²?
  • ¿Por qué el sesgo no se compensa con más N?

02 · Definición

Ruido frente a sesgo

◈

Ruido

Desvío aleatorio ±1/√N que se achica: con N ×4 cae a la mitad. Frecuencia 0,17 con N = 60 es ruido.

⇄

Sesgo

Desvío fijo δ que no se mueve: 0,17 con N = 12000 frente a 0,1667 ± 0,003 es sesgo de resto 4 (Tema 22).

▣

Cadena

Todo sesgo nace en un eslabón: fuente, divisor, redondeo, rango o selección. La firma dice cuál.

Catálogo de sesgos con firma y cura.
CausaFirmaCura
% k con resto rr caras altas en 1+r/qRechazo (Tema 22)
Dividir por m−1El 1/b aparece; int(u·k) carga el topeDividir por m (Temas 19–20)
round en discretosBordes a la mitadfloor (Tema 22)
Off-by-one en rangoUn valor falta o sobrak = high−low+1 (Temas 22–23)
/ RAND_MAX en CEl 1 sale 1/(R+1)/ (RAND_MAX+1.0) (Tema 20)

03 · Firmas y diagnóstico

Leer las barras como un médico

Cada causa deja huella distinta en el histograma de k caras: el % eleva las primeras r; el round hunde los bordes; el off-by-one vacía o duplica un extremo; el /(m−1) crea un átomo en el tope.

Protocolo N×4
Medir el desvío con N y con 4N: si cae a la mitad, ruido (seguir); si queda, sesgo (diagnosticar por firma y corregir la causa, no los datos).
No maquillar
Reponderar a posteriori (“le resto 0,01 al 6”) esconde el χ² y rompe independencia: se corrige el código, no la salida.
Sesgo heredado
Un mapeo viciado contamina sorteos (Tema 23), muestras (Tema 25) y simulaciones enteras (Tema 44): el eslabón más barato de arreglar es el primero.

Bien diagnosticado

Firma → causa → cura

Barras dicen “primeras altas” → resto → rechazo. Un cambio, χ² en verde, informe con antes/después.

Mal diagnosticado

Culpar al generador

Cambiar MT por PCG cuando el bug era un round: el sesgo sobrevive al cambio y se pierde una semana.

04 · Ejemplos

Tres historiales clínicos

Barras + χ²→Firma→Cura
  1. 1
    % 6 con m = 100.

    Cuatro caras en 0,17, dos en 0,16, χ² ≈ 25: resto 4 → rechazo.

  2. 2
    round(u·6)+1.

    1 y 6 en ≈ 0,083, resto en ≈ 0,167: bordes a la mitad → floor.

  3. 3
    randint(0, len).

    IndexError 1/7 de las veces + el último “sale menos”: off-by-one → randrange/choice.

05 · Implementación en Python

Medir tres sesgos de una vez

Comparamos sano (floor), % con resto y round sobre el mismo N = 12000: el χ² y las firmas hablan solas.

Python en tu navegador. Bajá N a 600 y mirá cómo el % se esconde en el ruido: el protocolo N×4 lo desenmascara.

import math
import random

def chi6(obs):
    e = sum(obs) / 6
    return round(sum((o - e) ** 2 / e for o in obs), 2)


rng = random.Random(34)
N = 12000
us = [rng.random() for _ in range(N)]
floor = [1 + math.floor(u * 6) for u in us]
redon = [1 + round(u * 6) for u in us]
xs = [rng.randrange(100) for _ in range(N)]
mod = [(x % 6) + 1 for x in xs]
for nombre, dados in [("floor", floor), ("round", redon), ("mod100", mod)]:
    obs = [dados.count(c) for c in range(1, 7)]
    print(nombre, [round(o / N, 4) for o in obs], "chi2:", chi6(obs))

floor ≈ 0,1667 (χ² ≈ 5, pasa); round hunde bordes; mod eleva 4 caras (χ² ≈ 40+, rechaza).

Protocolo N×4

import random

rng = random.Random(34)
for n in [3000, 12000]:
    xs = [rng.randrange(100) for _ in range(n)]
    dados = [(x % 6) + 1 for x in xs]
    f1 = dados.count(1) / n
    print(n, round(f1, 4), "desvio:", round(f1 - 1 / 6, 4))

06 · Exploración

Laboratorio: galería de sesgos

Cuatro dados con N = 12000: sano, % con resto, round y un atajo con /(m−1) que carga el 6. Las barras naranjas exceden la banda E ± 2√E; el panel trae χ² (crítico 11,07).

EXPERIMENTO 34

Firmas a la vista

el sesgo no se diluye

Los resultados numéricos aparecen debajo.
χ² (crit 11,07)—
Caras fuera—
Peor cara—
Veredicto—

Sano con N = 12000: seis caras en banda.

Cada sesgo tiene su firma: primeras altas (%), bordes hundidos (round), tope disparado (m−1).

Preguntas para explorar

  1. Con % y N = 1500: ¿χ² rechaza siempre? ¿Y con N = 12000?
  2. Con round: ¿qué caras se hunden y a cuánto? ¿Por qué justo esas?
  3. Si duplicás N en el sano, ¿el desvío máximo se achica? ¿Y en los sesgados?
Ver respuestas sugeridas
  1. Con N = 1500 a veces “pasa” (potencia justa); con N = 12000 rechaza sistemático: el sesgo emerge del ruido.
  2. 1 y 6 a ≈ 0,083 (la mitad): round les da medio intervalo ([0,1/12) y (11/12,1)) frente a 1/6 entero.
  3. En el sano sí (÷√2 por duplicación); en sesgados no: el δ fijo queda clavado.

07 · Comprensión

Confusiones frecuentes

«0,17 con N = 600 ya es sesgo»

La banda con N = 600 es ±0,012: 0,17 entra en ruido. Sesgo se declara con N grande y réplica, no con un conteo chico.

«Compenso restándole al 6»

Maquillaje: el χ² baja pero la independencia y los pares siguen rotos. Se arregla el código, no la planilla.

«Si cambio de generador se va»

Solo si la causa era la fuente (RANDU, período). Si era el mapeo (%, round), el sesgo viaja con vos al generador nuevo.

«Sesgo chico no importa»

En colas y sorteos millonarios, 1 pp de sesgo son miles de casos y plata: lo “chico” escala con N aunque el δ no se mueva.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: N×4 decide

El 1 sale 0,172 con N = 3000. ¿Ruido o sesgo? Repetilo con N = 12000 del mismo % y compará desvíos.

import random

rng = random.Random(2)
for n in [3000, 12000]:
    xs = [rng.randrange(100) for _ in range(n)]
    f1 = sum(1 for x in xs if x % 6 == 0) / n
    print(n, round(f1, 4))
Ver solución razonada

Da ≈ 0,171 con N = 3000 y ≈ 0,170 con N = 12000: el desvío (+0,004) no se achica al cuadruplicar N → sesgo (resto 4 da 17/100 = 0,17 exacto). Ruido habría caído a la mitad (≈ +0,002).

Ejercicio 2: bordes del round

Demostrá que round(u·6)+1 da al 1 prob. 1/12 con N = 12000.

import random

rng = random.Random(2)
N = 12000
dados = [1 + round(rng.random() * 6) for _ in range(N)]
print(round(dados.count(1) / N, 4), round(dados.count(3) / N, 4))
Ver solución

1 ≈ 0,083 (1/12) y 3 ≈ 0,167 (1/6): bordes a la mitad por construcción de intervalos de redondeo.

Ejercicio 3: el átomo del tope

Mostrá que dividir por m−1 con m = 16 crea un 1,0 con prob. 1/16 que luego carga el 6.

Ver una posible respuesta
import math

xs = list(range(16))
us = [x / 15 for x in xs]
dados = [min(6, 1 + math.floor(u * 6)) for u in us]
print("unos:", sum(1 for u in us if u == 1.0))
print("frec 6:", round(dados.count(6) / 16, 4))

Un 1,0 (1/16) que floor(u·6)+1 mandaría a 7, recortado a 6: el 6 queda en 2/16 = 0,125 en vez de 1/6… con m grande el átomo es 1/m pero sistemático.

09 · Síntesis

Ideas para recordar

  • Sesgo = δ fijo; ruido ∝ 1/√N. N×4 los separa.
  • 5 causas: % con resto, /(m−1), round, off-by-one, /RAND_MAX.
  • Cada una deja firma en barras: leer antes de culpar al generador.
  • Se corrige el código (rechazo, floor, k, divisor), nunca la salida.
  • Sesgo chico × N grande = daño grande.

En el próximo tema, los errores de todos los días: trampas al generar enteros (módulo, redondeo y bordes).