Distribuciones de datos

Frecuencias, tablas de frecuencia, histogramas y cómo construirlos con JavaScript para visualizar cómo se distribuyen tus datos.

Saber qué tipo de variable tienes es el primer paso. El segundo es entender cómo se distribuyen sus valores: ¿se agrupan en el centro? ¿Hay valores extremos? ¿Es uniforme o sesgada? La distribución es la “forma” de los datos.


Frecuencia absoluta y relativa

La frecuencia absoluta cuenta cuántas veces aparece cada valor. La frecuencia relativa expresa eso como proporción del total.

const notas = [7, 8, 6, 9, 7, 8, 8, 10, 6, 7, 9, 7, 8, 6, 10];

function tablaFrecuencias(arr) {
  const total = arr.length;
  const conteo = {};

  for (const val of arr) {
    conteo[val] = (conteo[val] ?? 0) + 1;
  }

  return Object.entries(conteo)
    .sort(([a], [b]) => Number(a) - Number(b))
    .map(([valor, fa]) => ({
      valor: Number(valor),
      fa,                                       // frecuencia absoluta
      fr: (fa / total).toFixed(3),              // frecuencia relativa
      frPct: ((fa / total) * 100).toFixed(1) + '%',
    }));
}

console.table(tablaFrecuencias(notas));

Resultado esperado:

valor  fa   fr      frPct
  6     3   0.200   20.0%
  7     4   0.267   26.7%
  8     4   0.267   26.7%
  9     2   0.133   13.3%
 10     2   0.133   13.3%

Agrupar en intervalos (clases)

Con datos continuos o con muchos valores distintos, agrupar en intervalos es más legible que listar cada valor:

const alturas = [162, 175, 180, 158, 171, 168, 183, 177, 165, 172,
                 169, 185, 160, 174, 179, 166, 182, 170, 163, 178];

function frecuenciasIntervalos(arr, numClases = 5) {
  const min = Math.min(...arr);
  const max = Math.max(...arr);
  const amplitud = Math.ceil((max - min) / numClases);

  const clases = Array.from({ length: numClases }, (_, i) => ({
    desde: min + i * amplitud,
    hasta: min + (i + 1) * amplitud,
    fa: 0,
  }));

  for (const valor of arr) {
    const idx = Math.min(
      Math.floor((valor - min) / amplitud),
      numClases - 1   // el valor máximo va en la última clase
    );
    clases[idx].fa++;
  }

  return clases.map((c) => ({
    intervalo: `[${c.desde}, ${c.hasta})`,
    fa: c.fa,
    fr: (c.fa / arr.length).toFixed(3),
  }));
}

console.table(frecuenciasIntervalos(alturas));

Histograma en consola

Sin necesidad de ninguna librería, podemos dibujar un histograma ASCII:

function histogramaConsola(frecuencias) {
  const maxFa = Math.max(...frecuencias.map((f) => f.fa));
  const escala = 20; // longitud máxima de la barra

  for (const { intervalo, fa } of frecuencias) {
    const barra = '█'.repeat(Math.round((fa / maxFa) * escala));
    console.log(`${intervalo.padEnd(14)} ${barra} (${fa})`);
  }
}

histogramaConsola(frecuenciasIntervalos(alturas));
// [158, 163)    ████ (2)
// [163, 168)    ████████ (4)
// [168, 173)    ████████████████████ (6)
// [173, 178)    ████████████ (4)
// [178, 186)    ████████ (4)

Formas de distribución habituales

FormaQué significaEjemplo
SimétricaMedia ≈ mediana, forma de campanaAlturas de adultos
Sesgada a la derechaCola larga hacia valores altos (media > mediana)Salarios, precios de vivienda
Sesgada a la izquierdaCola larga hacia valores bajosNotas en examen fácil
UniformeTodos los valores con frecuencia similarLanzamiento de un dado
BimodalDos picos distintosMezcla de dos poblaciones
// Detectar asimetría básica comparando media y mediana
function detectarSesgo(arr) {
  const sorted = [...arr].sort((a, b) => a - b);
  const media = arr.reduce((s, x) => s + x, 0) / arr.length;
  const n = sorted.length;
  const mediana = n % 2 === 0
    ? (sorted[n / 2 - 1] + sorted[n / 2]) / 2
    : sorted[Math.floor(n / 2)];

  const diff = media - mediana;
  if (Math.abs(diff) < 0.5) return 'Simétrica';
  return diff > 0 ? 'Sesgada a la derecha' : 'Sesgada a la izquierda';
}

console.log(detectarSesgo([1, 1, 2, 2, 2, 3, 10, 15])); // Sesgada a la derecha

En la siguiente lección calculamos las medidas de centralización: media, mediana y moda, y entendemos cuándo usar cada una.