---
id: categorical_top_bar_figure_py_datascience
name: categorical_top_bar_figure
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def categorical_top_bar_figure(top: list, n_distinct: int = 0, title: str = \"\", top_k: int = 6, n_rows=None) -> \"matplotlib.figure.Figure\""
description: "Construye una figura matplotlib de barras horizontales de las top_k categorías más frecuentes de una columna categórica, con la mayor arriba y agregando el resto en una barra gris \"Otros (N categorías)\". Contrato de entrada idéntico a categorical_top_pie_figure (swap directo donut↔barras): consume el bloque `top` de summarize_categorical y devuelve un matplotlib.figure.Figure listo para rasterizar por el renderer del informe EDA. Backend Agg sin pyplot global; defensivo total ante top vacío/None, nunca lanza."
tags: [eda, categorical, bar, barh, matplotlib, figure, visualization, datascience, impure]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [matplotlib]
example: |
  from categorical_top_bar_figure import categorical_top_bar_figure
  top = [
      {"value": "rojo", "count": 40, "pct": 0.4},
      {"value": "azul", "count": 30, "pct": 0.3},
      {"value": "verde", "count": 20, "pct": 0.2},
  ]
  fig = categorical_top_bar_figure(top, n_distinct=12, title="color", top_k=6, n_rows=100)
tested: true
tests:
  - "test_returns_figure"
  - "test_ten_items_topk_six_yields_seven_bars"
  - "test_empty_top_does_not_raise_and_returns_figure"
  - "test_long_value_truncated"
  - "test_none_value_and_none_count_are_handled"
  - "test_n_rows_adds_exact_others_bar"
test_file_path: "python/functions/datascience/categorical_top_bar_figure_test.py"
file_path: "python/functions/datascience/categorical_top_bar_figure.py"
params:
  - name: top
    desc: "Lista de dicts {value, count, pct} ordenada de mayor a menor por count (salida del bloque `top` de summarize_categorical). Puede venir vacía o con dicts incompletos: items no-dict, sin count, con count None o count <= 0 se descartan. value None se admite (etiqueta vacía)."
  - name: n_distinct
    desc: "Nº total de categorías distintas de la columna. Etiqueta la barra agregada como \"Otros (n_distinct - top_k)\" (mínimo 0). Si no supera el nº de barras mostradas, se usa el overflow real de `top` como nº de categorías agregadas. Default 0."
  - name: title
    desc: "Título de la figura (nombre de la columna). Se trunca a ~48 chars con elipsis si es muy largo. Default \"\" (sin título)."
  - name: top_k
    desc: "Nº máximo de barras explícitas. Default 6. La barra \"Otros\" no cuenta contra este límite. Con top_k <= 0 se muestra al menos la categoría mayor."
  - name: n_rows
    desc: "Opcional. Total de filas del dataset. Si se da y la suma de counts mostrados < n_rows, la barra \"Otros\" usa (n_rows - suma_mostrada) como count para que sea exacta respecto al total real. Si se omite, \"Otros\" usa la suma de counts fuera del top_k mostrado (solo cuando top trae más de top_k items). Default None."
output: "Un matplotlib.figure.Figure (figsize 6.4 x altura escalada con el nº de barras, dpi 150) con un Axes de barras horizontales: la categoría más frecuente arriba, la barra gris \"Otros (N categorías)\" abajo, cada barra anotada con su conteo y porcentaje al final y etiquetas de categoría (yticklabels) truncadas a ~22 chars. Si no hay counts válidos devuelve igualmente una Figure con un texto centrado \"sin datos categóricos\" (nunca lanza); cualquier error inesperado cae a una Figure con el texto del error. El caller rasteriza/cierra la figura; la función no la muestra ni la guarda."
---

## Ejemplo

```python
from categorical_top_bar_figure import categorical_top_bar_figure

# `top` es la salida del bloque "top" de summarize_categorical (ya ordenado desc).
top = [
    {"value": "rojo", "count": 40, "pct": 0.40},
    {"value": "azul", "count": 30, "pct": 0.30},
    {"value": "verde", "count": 20, "pct": 0.20},
    {"value": "amarillo", "count": 5, "pct": 0.05},
]

fig = categorical_top_bar_figure(
    top,
    n_distinct=12,            # 12 categorías distintas en total
    title="color_producto",
    top_k=6,                  # hasta 6 barras explícitas
    n_rows=100,               # "Otros" = 100 - 95 = 5, sobre 8 categorías agregadas
)

# El renderer del informe lo rasteriza; aquí solo persistimos para inspección.
fig.savefig("/tmp/barras_color.png")
```

## Cuando usarla

Úsala dentro de un informe EDA cuando quieras comparar **magnitudes** de las
categorías dominantes de una columna categórica: qué categoría manda y por
cuánto frente a las siguientes. Pásale directamente el bloque `top` de
`summarize_categorical` (ya ordenado de mayor a menor) más `n_distinct` para que
la barra "Otros" indique cuántas categorías quedan agrupadas. Es el clon "de
barras" del donut `categorical_top_pie_figure` con **contrato de entrada
idéntico**: puedes intercambiar una por otra sin tocar el caller. Elige barras
cuando importe comparar tamaños exactos; el donut cuando importe la proporción
del total.

## Gotchas

- **Impura por matplotlib.** Toca la maquinaria de render. Usa el backend `Agg`
  y la API orientada a objetos `Figure`/`add_subplot` — NUNCA `pyplot.*` aquí,
  para no tocar el estado global ni filtrar figuras entre llamadas. `pyplot` NO
  es thread-safe; esta función evita ese riesgo construyendo el `Figure`
  directamente, así que es segura de llamar en bucle desde el renderer.
- **El caller cierra la figura.** La función devuelve el `Figure` pero no lo
  muestra ni lo guarda. Quien la consume debe rasterizarla y luego liberarla
  (`fig.clf()` / `matplotlib.pyplot.close(fig)` si se usó pyplot en el caller)
  para no acumular memoria en lotes grandes de columnas.
- **`barh` dibuja de abajo arriba.** La categoría más frecuente va arriba porque
  el orden de display se invierte antes de plotear; la barra "Otros" queda
  siempre al fondo. No reordenes `top` esperando otro layout: la función asume
  que ya viene ordenado desc por count.
- **Magnitud exacta de "Otros" solo con `n_rows`.** Sin `n_rows`, la barra
  "Otros" se calcula con el overflow presente en `top`; si `top` ya viene
  recortado a `top_k` por el productor, no habrá "Otros" aunque existan más
  categorías. Pasa `n_rows` (total de filas del dataset) para una barra correcta
  respecto al total real.
- **Defensiva, nunca lanza.** `top=[]`, `value=None`, `count=None` o counts no
  numéricos se manejan sin error: en el peor caso devuelve una `Figure` con
  "sin datos categóricos", y cualquier excepción inesperada cae a una `Figure`
  con el texto del error. No envuelvas la llamada en try/except por miedo a un
  raise — no lo hay.
