---
id: detect_distribution_type_py_datascience
name: detect_distribution_type
kind: function
lang: py
domain: datascience
version: "1.1.0"
purity: pure
signature: "def detect_distribution_type(values: list[float]) -> dict"
description: "Classifies the shape of a numeric distribution using cardinality (distinct values), number of prominent modes, skewness, excess kurtosis, tail ratio and log-skewness. Returns a type label and raw stats. Discrete/ordinal and multimodal columns are detected before the symmetric normal-ish test so they are never mislabeled normal."
tags: [statistics, distribution, classification, skewness, kurtosis, multimodal, cardinality, eda]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [math, numpy]
example: |
  from detect_distribution_type import detect_distribution_type
  import numpy as np
  result = detect_distribution_type(np.random.normal(0, 1, 200).tolist())
  # {"type": "normal-ish", "stats": {"n": 200, "skew": ..., ...}}
tested: true
tests:
  - "test_detect_too_few_samples"
  - "test_detect_normal_ish"
  - "test_detect_right_skewed"
  - "test_detect_stats_keys"
  - "test_detect_exactly_30"
  - "test_detect_discrete_low_cardinality"
  - "test_detect_multimodal"
  - "test_detect_normal_still_normal_after_fix"
  - "test_detect_stats_has_new_keys"
  - "test_detect_unimodal_skewed_not_multimodal"
test_file_path: "python/functions/datascience/tests/test_detect_distribution_type.py"
file_path: "python/functions/datascience/detect_distribution_type.py"
params:
  - name: values
    desc: "List of numeric values to classify. Minimum 30 for meaningful classification."
output: >
  Dict with "type" (str) and "stats" (dict). Type is one of: discrete,
  multimodal, heavy-tail, normal-ish, lognormal-ish, right-skewed, left-skewed,
  other, too_few_samples. Stats contains: n, skew, kurtosis, tail_ratio,
  log_skew, n_unique, n_modes, jb_stat, jb_pvalue.
source_repo: "internal:footprint_aurgi"
source_license: "internal-aurgi"
source_file: "aurgi_mapas/generar_pdf_reporte.py:133"
---

## Ejemplo

```python
from detect_distribution_type import detect_distribution_type
import numpy as np

detect_distribution_type(np.random.normal(0, 1, 200).tolist())
# {"type": "normal-ish", "stats": {"n": 200, "skew": 0.03, ...}}

detect_distribution_type([1]*5)
# {"type": "too_few_samples", "stats": {"n": 5}}
```

## Logica de clasificacion

El orden importa: cardinalidad y modalidad se evaluan **antes** del test simetrico
`normal-ish`, para que una columna discreta/ordinal o multimodal nunca se etiquete
"normal" solo porque su skewness sea pequena.

- n < 30 → too_few_samples
- n_unique <= 15 → discrete (ordinal / counts de pocos niveles)
- excess kurtosis > 3 → heavy-tail
- n >= 100 AND n_modes >= 2 → multimodal
- |skew| <= 0.5 AND |kurt| <= 1 → normal-ish
- skew > 0.5 AND log_skew cerca de 0 AND tail_ratio > 2 → lognormal-ish
- skew > 0.5 → right-skewed
- skew < -0.5 → left-skewed
- default → other

tail_ratio = p99/p50; log_skew calculado solo si hay >= 30 positivos.

`n_modes` cuenta picos prominentes de un histograma suavizado (~sqrt(n) bins,
suavizado triangular) separados por un valle profundo (cae por debajo del 60% del
pico menor). Esto evita modos espurios por ruido en continuas unimodales sesgadas.

## Cuando usarla

Cuando perfiles una columna numerica y quieras saber su forma para elegir el
estadistico/visualizacion adecuados (media+desv vs mediana+IQR, histograma vs
boxplot). Distingue discretas/ordinales y multimodales que un criterio por-skew
confunde con normales.

## Gotchas

- **Jarque-Bera NO es gate de `normal-ish`.** `jb_stat`/`jb_pvalue` se reportan en
  `stats` como senal diagnostica, pero con n grande Jarque-Bera rechaza normalidad
  para columnas perfectamente acampanadas (p.ej. pH o density del vino, n~1600,
  jb_p≈0 pese a ser normal-ish). Usarlo como umbral duro produce falsos negativos
  masivos. La robustez ante el tamano muestral la dan cardinalidad y modalidad.
- El umbral `n_unique <= 15` etiqueta como `discrete` cualquier continua con muy
  pocos valores distintos: eso es correcto (es discreta/ordinal de facto), no un
  falso positivo.
- `multimodal` solo se evalua con `n >= 100`; por debajo el histograma es demasiado
  ruidoso para afirmar multimodalidad y se cae a la logica de skew/kurt.

## Capability growth log

- v1.1.0 (2026-06-29) — H11: anade deteccion de cardinalidad (`discrete`) y
  modalidad (`multimodal`) antes del test `normal-ish`, mas `n_unique`, `n_modes`,
  `jb_stat`, `jb_pvalue` en stats. Corrige falsos "normal-ish" en discretas/ordinales
  (wine `quality`) y multimodales (precios BTC). Retrocompatible: continuas normales,
  sesgadas y heavy-tail no cambian.
