---
id: render_table_as_figure_py_datascience
name: render_table_as_figure
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def render_table_as_figure(header, rows, title=None, note=None, fontsize=9.0, max_cell_chars=40) -> \"matplotlib.figure.Figure\""
description: "Dibuja un bloque tabular (cabecera + filas) como una matplotlib.figure.Figure nítida, lista para rasterizar a DPI alto. Pensada para tablas que NO caben como texto en una página/slide del informe EDA: se rasteriza a alta resolución (el caller usa dpi=220, bbox_inches='tight') y el usuario hace zoom en el móvil para leerla entera sin perder datos. Cabecera sombreada (#eef3f6) y en negrita, filas pares (1-based) con zebra suave (#f6f8fa), tinta oscura (#1b1b1b) sobre blanco, rejilla gris muy fina (#cccccc). Trunca cada celda a max_cell_chars con elipsis y str()-ea cada valor (None -> \"\"). figsize proporcional al contenido (ancho por nº y longitud de columnas, alto por nº de filas) para que sea legible con zoom. Backend Agg sin pyplot global. Defensiva: header/rows vacíos o None, filas irregulares o cualquier error interno devuelven una Figure placeholder con texto centrado \"(tabla no disponible)\". NUNCA lanza."
tags: [eda, table, figure, matplotlib, visualization, rasterize, zoom, render, datascience, impure]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [matplotlib]
example: |
  from datascience.render_table_as_figure import render_table_as_figure
  header = ["columna", "n_nulos", "%_nulos", "distintos", "tipo", "ejemplo"]
  rows = [
      ["ingresos", 12, "1.2%", 980, "float64", "2345.67"],
      ["edad", 0, "0.0%", 88, "int64", "37"],
      ["ciudad", 5, "0.5%", 412, "object", "Madrid"],
  ]
  fig = render_table_as_figure(header, rows, title="Resumen de columnas",
                               note="rasteriza a dpi=220 y haz zoom")
  fig.savefig("/tmp/tabla.png", dpi=220, bbox_inches="tight")
tested: true
tests:
  - "test_returns_figure_with_table"
  - "test_rows_none_does_not_raise"
  - "test_header_none_does_not_raise"
  - "test_empty_lists_return_placeholder_figure"
  - "test_both_none_return_placeholder_figure"
  - "test_long_cell_is_truncated"
  - "test_none_cells_become_empty_strings"
  - "test_can_rasterize_to_png_high_dpi"
  - "test_placeholder_can_rasterize"
  - "test_ragged_rows_are_padded"
test_file_path: "python/functions/datascience/render_table_as_figure_test.py"
file_path: "python/functions/datascience/render_table_as_figure.py"
params:
  - name: header
    desc: "Lista de nombres de columna (puede ser [] o None). Cada nombre se str()-ea, se trunca a max_cell_chars y se pinta en la fila cabecera sombreada en negrita. Si está vacío/None no se dibuja fila de cabecera (solo cuerpo)."
  - name: rows
    desc: "Lista de filas; cada fila es una lista de celdas con valores cualesquiera (se str()-ean; None -> \"\"). Admite None (se trata como []), filas escalares (se envuelven en una celda) y filas de distinta longitud (la rejilla se rectangulariza al ancho máximo, rellenando con celdas vacías). Saltos de línea/tabs en una celda se colapsan a espacios para que no desborde a otras filas."
  - name: title
    desc: "Título opcional dibujado encima de la tabla, en negrita tinta #1b1b1b, alineado a la izquierda. None o \"\" => sin título. Default None."
  - name: note
    desc: "Nota opcional al pie de la figura, en gris #8a8a8a e itálica. None o \"\" => sin nota. Default None."
  - name: fontsize
    desc: "Tamaño de fuente base (pt) de las celdas del cuerpo. La cabecera usa fontsize+3 y la nota max(7, fontsize-1). Un valor no numérico o <= 0 cae a 9.0. Default 9.0."
  - name: max_cell_chars
    desc: "Trunca el texto de cada celda a este nº de chars (con … final cuando se recorta) para que el ancho no explote. Un valor no entero cae a 40; <= 0 deja las celdas vacías. Default 40."
output: "Un matplotlib.figure.Figure (figsize proporcional al contenido: ancho ≈ 0.9-1.6\" por columna según su texto, total acotado a 3-26\"; alto ≈ 0.32\" por fila + cabecera + espacio para título/nota, acotado) con un Axes sin ejes que contiene un ax.table(...) NO cerrado. Cabecera fondo #eef3f6 texto #1b1b1b bold; filas pares (1-based) zebra #f6f8fa, impares blanco; tinta #1b1b1b; bordes/rejilla #cccccc lw 0.4; texto alineado a la izquierda. Título encima (bold) y nota debajo (gris itálica) si se pasan. Si header/rows son vacíos o None, o ante cualquier error interno, devuelve una Figure placeholder pequeña con el texto centrado \"(tabla no disponible)\". NUNCA lanza. El caller la rasteriza (dpi=220, bbox_inches='tight') y la cierra; la función no la muestra ni la guarda."
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from datascience.render_table_as_figure import render_table_as_figure

# Tabla que no cabe como texto en la slide -> se rasteriza y se lee con zoom.
header = ["columna", "n_nulos", "%_nulos", "distintos", "tipo", "ejemplo"]
rows = [
    ["ingresos", 12, "1.2%", 980, "float64", "2345.67"],
    ["edad", 0, "0.0%", 88, "int64", "37"],
    ["ciudad", 5, "0.5%", 412, "object", "Madrid"],
    ["categoria_producto", 0, "0.0%", 1840, "object",
     "un_valor_categorico_muy_largo_que_se_trunca"],
]

fig = render_table_as_figure(
    header,
    rows,
    title="Resumen de columnas",
    note="rasteriza a dpi=220 y haz zoom en el móvil",
    fontsize=9.0,
    max_cell_chars=40,
)

# El renderer del informe lo rasteriza a alta resolución; aquí lo persistimos.
fig.savefig("/tmp/tabla.png", dpi=220, bbox_inches="tight")
```

## Cuando usarla

Úsala en un informe EDA cuando una tabla **no cabe como texto** en una página o
slide y prefieres una imagen nítida que el lector pueda ampliar en el móvil para
leerla entera (perfiles de columnas, matrices de conteo, tablas de frecuencias
con muchas filas o columnas anchas). Pásale la cabecera y las filas tal cual (los
valores se `str()`-ean por ti) más un `title`/`note` opcionales; el llamante la
rasteriza a `dpi=220` con `bbox_inches='tight'`. Es la pareja "tabla-como-imagen"
de los gráficos `build_boxplots_figure` / `categorical_top_pie_figure`: misma
paleta y mismo contrato (Agg, sin `pyplot`, el caller cierra la figura).

## 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 construye el `Figure` directamente, así que es
  segura de llamar en bucle desde el renderer.
- **El caller cierra la figura.** Devuelve el `Figure` pero no lo muestra ni lo
  guarda. Quien la consume debe rasterizarla y luego liberarla
  (`matplotlib.pyplot.close(fig)`) para no acumular memoria en lotes grandes.
- **Pensada para rasterizar a DPI alto.** El `figsize` es proporcional al
  contenido pero la legibilidad real viene del DPI: rasteriza con `dpi=220` y
  `bbox_inches='tight'`. Una tabla con muchísimas filas crece en alto (capado a
  ~60") — para miles de filas, parte la tabla o resume antes de pasarla.
- **Truncación de celda visible.** Cada celda se recorta a `max_cell_chars`
  (default 40) con `…` final y los saltos de línea/tabs se colapsan a espacios,
  para que ninguna celda desborde a otras filas. Sube `max_cell_chars` si
  necesitas ver el valor completo (a costa de ancho).
- **Defensiva, nunca lanza.** `header`/`rows` vacíos o `None`, filas escalares,
  filas de distinta longitud o cualquier error interno se manejan sin propagar:
  en el peor caso devuelve una `Figure` placeholder con "(tabla no disponible)".
  No envuelvas la llamada en try/except por miedo a un raise — no lo hay.
