Files
fn_registry/python/functions/datascience/extract_text_sample.md
T
egutierrez 105e56cf05 feat(eda): capítulo text_distr (TEXTO/NLP) — primer capítulo de datos no tabulares
Añade el capítulo `text_distr` al motor AutomaticEDA: perfila columnas de texto
libre largo (reseñas, descripciones, comentarios) que la distribución categórica
no resume bien. Sigue el patrón de cat_distr/num_distr (build_text_distr(profile,
ctx) -> Chapter | None) y se registra en CHAPTER_ORDER tras cat_distr.

Activación en dos fases: gate barato desde el perfil (columna no numérica con
len_mean >= 50 chars) + confirmación con muestra cruda (mediana de palabras >= 20).
Un dataset sin texto largo (p.ej. titanic) devuelve None sin tocar el informe.

Bloques por columna (Group con page_break): resumen (longitudes, vocabulario con
TTR y % hapax, idioma dominante, % duplicados, legibilidad), histograma de
longitudes, top términos (tabla + barras), bigramas/trigramas, idiomas detectados
y nube de palabras opcional. Términos ttr/hapax enganchados al glosario clicable.

Lógica delegada a 7 funciones nuevas del registry (datascience, tag eda),
estilo dict-no-throw:
- extract_text_sample (impura, push-down SQL DuckDB/Postgres)
- compute_text_length_stats, compute_vocabulary_stats, compute_top_ngrams (puras, stdlib)
- detect_corpus_language (langdetect opcional), compute_text_readability (textstat
  opcional), compute_text_duplicates (hash + datasketch opcional)

Versión barata sin modelos pesados: las piezas que dependen de una librería
opcional (langdetect, textstat, wordcloud, datasketch) degradan a omitidas sin
lanzar. Añade langdetect y textstat (ligeras) al pyproject + uv.lock.

Verificado: golden sobre dataset de reviews multi-idioma (capítulo presente en
PDF+PPTX+MD con métricas reales), titanic sin capítulo (None), degradación sin
libs, suite automatic_eda + pipeline verde (128 passed), fn index OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:38:17 +02:00

6.6 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, params, output, tested, tests, test_file_path, file_path
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports params output tested tests test_file_path file_path
extract_text_sample function py datascience 1.0.0 impure def extract_text_sample(db_path: str, table: str, columns: list, backend: str = 'duckdb', sample: int = 2000) -> dict Muestrea columnas de texto de una tabla DuckDB/Postgres con push-down SQL (LIMIT sample), SIN traer la tabla entera a RAM. Funcion impura del grupo de capacidad `eda`: la usan los capitulos de texto/NLP del AutomaticEDA que necesitan valores crudos de texto (longitudes, tokens, ejemplos) sobre una muestra acotada. Construye el lector read-only query_fn(sql)->dict igual que build_eda_render_ctx (closure sobre duckdb_query_readonly / pg_query importados perezosamente desde infra). Escapa los identificadores con comillas dobles y lanza una sola query SELECT "c1", "c2" FROM "table" LIMIT n. Por columna, la lista de strings solo contiene valores NO None y NO vacios: cada celda no nula se convierte con str(...) y se descarta si queda cadena vacia. Estilo dict-no-throw del grupo eda: NUNCA lanza; ante cualquier fallo (query, conversion, backend desconocido) devuelve {status:'error', error:str, columns:{}, n:0}. La clave n reporta el numero de FILAS leidas por la query (antes de filtrar None/vacios).
eda
datascience
text
nlp
extraction
read-only
duckdb
postgres
python
duckdb_query_readonly_py_infra
pg_query_py_infra
false error_go_core
name desc
db_path ruta al archivo DuckDB, o DSN PostgreSQL si backend='postgres'. Se inyecta en el closure query_fn. No se valida aqui: si la base no existe o el DSN es invalido, la query devuelve status error y el resultado es {status:'error', ...} (no lanza).
name desc
table nombre de la tabla. Se escapa con comillas dobles en la query (SELECT ... FROM "table").
name desc
columns lista de nombres de columna de texto a muestrear. Se filtra a las entradas que sean str no vacio; cada nombre se escapa con comillas dobles. Si tras filtrar queda vacia -> {status:'ok', columns:{}, n:0} sin tocar la base.
name desc
backend 'duckdb' (default) o 'postgres'. Selecciona el lector read-only del registry (duckdb_query_readonly / pg_query). Cualquier otro valor -> {status:'error', error:'backend desconocido: <valor>', columns:{}, n:0}.
name desc
sample maximo de filas a muestrear (clausula LIMIT). Default 2000. Acota memoria y tiempo: con tablas grandes obtienes el primer tramo por orden fisico (sin ORDER BY), no un muestreo uniforme.
dict dict-no-throw (NUNCA lanza): {status:'ok'|'error', columns:{col_name:[str,...]}, n:int, error:str}. En exito (status='ok') columns mapea cada columna pedida a la lista de sus valores de texto NO None y NO vacios (cada celda convertida con str(...)); n es el numero de FILAS leidas por la query (antes de filtrar None/vacios). columns vacio -> {status:'ok', columns:{}, n:0}. En error (backend desconocido, query con status!='ok', o cualquier excepcion) -> {status:'error', error:str, columns:{}, n:0}; la clave error solo aparece en este caso. true
test_extract_basic
test_backend_desconocido
test_columns_vacio
test_sample_limit
python/functions/datascience/extract_text_sample_test.py python/functions/datascience/extract_text_sample.py

Ejemplo

import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
# Import directo del submodulo (no requiere export en datascience/__init__.py).
from datascience.extract_text_sample import extract_text_sample

# Muestrea hasta 2000 filas de dos columnas de texto de una tabla DuckDB.
res = extract_text_sample(
    "data/reviews.duckdb", "reviews", ["title", "body"],
    backend="duckdb", sample=2000,
)
# res == {
#   "status": "ok",
#   "columns": {
#     "title": ["Gran producto", "No funciona", ...],   # solo no-None, no-""
#     "body":  ["Lo uso a diario...", ...],
#   },
#   "n": 2000,   # filas leidas por la query (antes de filtrar None/vacios)
# }

# Postgres: db_path es el DSN.
res_pg = extract_text_sample(
    "postgresql://user:pass@localhost:5433/trends", "comentarios", ["texto"],
    backend="postgres", sample=500,
)

Cuando usarla

Cuando necesites valores CRUDOS de texto de una o varias columnas para analisis NLP/texto (distribucion de longitudes, conteo de tokens, ejemplos representativos, deteccion de idioma) pero NO quieras cargar la tabla entera en memoria. Es el muestreador de texto del grupo eda: una sola llamada con push-down LIMIT devuelve listas de strings por columna, limpias de None y vacios, listas para alimentar un capitulo de texto del AutomaticEDA o cualquier rutina de tokenizado. Usala junto a profile_table / build_eda_render_ctx cuando el perfil agregado no basta y hace falta el texto real.

Gotchas

  • Impura: lee de la base de datos a traves de query_fn (closure sobre duckdb_query_readonly / pg_query). No abre conexiones fuera de esos wrappers del registry. Estilo dict-no-throw del grupo eda: NUNCA lanza; ante cualquier fallo devuelve {status:'error', error:str, columns:{}, n:0}.
  • error_type en el frontmatter es error_go_core por convencion del registry (toda funcion impura debe declararlo y el indexer lo exige), pero el codigo NO lanza esa excepcion: degrada al dict de error. Es metadata, no comportamiento.
  • Backend desconocido: con un backend que no sea duckdb ni postgres devuelve {status:'error', error:'backend desconocido: <valor>', columns:{}, n:0} sin tocar la base.
  • Las listas NO incluyen None ni cadenas vacias: cada celda no nula se pasa por str(...) y se descarta si queda "". Por eso len(columns[col]) puede ser menor que n (que cuenta las filas leidas). Si necesitas alineacion por fila (una entrada por fila aunque sea None), usa build_eda_render_ctx (raw_numeric), no esta funcion.
  • LIMIT sample sin ORDER BY: con tablas grandes obtienes el primer tramo por orden fisico del backend, no un muestreo uniforme ni reproducible. Sube sample para mas cobertura, o pre-ordena/aleatoriza la tabla si necesitas representatividad.
  • DuckDB en sandbox por defecto: duckdb_query_readonly abre la conexion con enable_external_access=False, asi que la query solo puede leer la propia base (no read_csv/httpfs/ATTACH a paths externos). Lee tablas ya existentes en el archivo DuckDB sin problema.
  • No loguear los datos crudos: las listas de columns pueden contener texto sensible (reviews, comentarios, PII). En trazas usa solo conteos (n, len(columns[col])) y nombres de columna, no el dict completo.