feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: summarize_table_duckdb
|
||||
kind: function
|
||||
lang: py
|
||||
domain: datascience
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def summarize_table_duckdb(db_path: str, table: str, high_card_ratio: float = 0.9) -> dict"
|
||||
description: "Perfila una tabla DuckDB en una sola pasada SQL (SUMMARIZE, push-down sin traer filas a RAM) y devuelve el esqueleto de un TableProfile con el perfil base por columna. Corazon del grupo eda: base barata sobre la que otras funciones anaden lo estadistico fino (skew/kurtosis/histograma sobre muestra)."
|
||||
tags: [eda, duckdb, profiling, datascience, exploratory-data-analysis, table-profile]
|
||||
params:
|
||||
- name: db_path
|
||||
desc: "Ruta al archivo DuckDB. Debe existir (lectura read-only via duckdb_query_readonly; no se crea)."
|
||||
- name: table
|
||||
desc: "Nombre de la tabla a perfilar. Se valida contra ^[A-Za-z_][A-Za-z0-9_]*$ y se cita en el SQL (SUMMARIZE no admite parametros posicionales para el identificador)."
|
||||
- name: high_card_ratio
|
||||
desc: "Umbral de unicidad (unique_pct, 0-1) a partir del cual una columna categorical recibe el flag high_cardinality. Default 0.9."
|
||||
output: "dict dict-no-throw. En exito {status:'ok', profile: TableProfile} con perfil base por columna (n_rows/n_cols, type_breakdown, constant_cols, all_null_cols, null_cell_pct y columns[] de ColumnProfile). En error {status:'error', error:str}. Claves estadisticas finas (skew, kurtosis, histograma, percentiles finos, moda, outliers, correlaciones, key_candidates, quality_score) quedan en None/[] para que otras funciones del grupo eda las completen."
|
||||
uses_functions: [duckdb_query_readonly_py_infra]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: true
|
||||
tests: ["test_shape_y_metadatos_tabla", "test_column_profile_shape", "test_type_breakdown", "test_tabla_invalida_devuelve_error", "test_tabla_inexistente_devuelve_error", "test_distinct_no_excede_filas", "test_columna_unica_da_possible_id"]
|
||||
test_file_path: "python/functions/datascience/summarize_table_duckdb_test.py"
|
||||
file_path: "python/functions/datascience/summarize_table_duckdb.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from datascience import summarize_table_duckdb
|
||||
|
||||
# Perfila la tabla `keywords` de una base DuckDB de SEO.
|
||||
res = summarize_table_duckdb(
|
||||
db_path=os.path.expanduser("~/.fn_seo/seo.duckdb"),
|
||||
table="keywords",
|
||||
high_card_ratio=0.9,
|
||||
)
|
||||
|
||||
if res["status"] == "ok":
|
||||
p = res["profile"]
|
||||
print(f"{p['table']}: {p['n_rows']} filas x {p['n_cols']} cols")
|
||||
print("type_breakdown:", p["type_breakdown"])
|
||||
for col in p["columns"]:
|
||||
print(col["name"], col["inferred_type"], "nulls=", col["null_pct"], col["flags"])
|
||||
else:
|
||||
print("error:", res["error"])
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando empieces a explorar una tabla DuckDB que no conoces y necesites el esqueleto barato de su perfil (tipos inferidos, nulos, cardinalidad, flags) **antes** de gastar en estadistica fina.
|
||||
- Como primer paso del grupo `eda`: construye el TableProfile base que `describe_numeric` y otras funciones del grupo enriquecen luego sobre una muestra.
|
||||
- Cuando quieras perfilar tablas grandes sin traer filas a RAM: `SUMMARIZE` hace push-down en el motor de DuckDB.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Impura**: lee de disco via `duckdb_query_readonly` (modo read-only, no crea ni modifica la base). El `db_path` debe existir.
|
||||
- **`distinct_count` exacto para tablas <=200k filas, aproximado+capado por encima**: `SUMMARIZE` usa HyperLogLog (`approx_unique`), que SOBREESTIMA y en tablas pequenas puede reportar mas distintos que filas (inflando `unique_pct` por encima de 1.0 y disparando flags `possible_id` falsos). Por eso, para `n_rows <= 200000` la funcion calcula `COUNT(DISTINCT)` EXACTO en una sola query combinada (barata) y usa ese valor. Para tablas mas grandes mantiene `approx_unique` pero lo CAPA a `n_rows` (`distinct_count = min(approx_unique, n_rows)`). En ambos casos `unique_pct = min(distinct_count / n_rows, 1.0)`, asi que `distinct_count` nunca supera las filas ni `unique_pct` pasa de 1.0. Los flags `possible_id` / `high_cardinality` derivan de ese `distinct_count` ya corregido (exacto y fiable por debajo de 200k filas; aproximado y conservador por encima).
|
||||
- **`SUMMARIZE` NO da skew, kurtosis ni histograma**, ni percentiles finos (p1/p5/p95/p99), moda, outliers, correlaciones, key_candidates ni quality_score. Esas claves quedan en `None`/`[]` a proposito: las rellena otra funcion del grupo `eda` sobre una muestra. El sub-dict `numeric` solo trae min, max, mean, std, p25, p50, p75.
|
||||
- **`SUMMARIZE.count` es el total de filas, no el no-nulo**: la funcion deriva el `count` no-nulo del ColumnProfile como `n_rows - null_count` (con `null_count` redondeado de `null_percentage`).
|
||||
- **min/max/avg/std/q25/q50/q75 vienen como strings** desde DuckDB; se convierten a float (None si la columna no es numerica).
|
||||
- **Requiere DuckDB 1.5.2** (columnas de `SUMMARIZE` validadas con esa version: column_name, column_type, min, max, approx_unique, avg, std, q25, q50, q75, count, null_percentage).
|
||||
- **El identificador de tabla se interpola** (no parametrizable en `SUMMARIZE`): por eso se valida contra `^[A-Za-z_][A-Za-z0-9_]*$` antes de citarlo. Un nombre invalido (p.ej. con `;` o espacios) devuelve `{status:'error'}` sin tocar la base.
|
||||
|
||||
## Notas
|
||||
|
||||
Contrato compartido por todo el grupo `eda` (mantener estable):
|
||||
|
||||
```text
|
||||
TableProfile = {
|
||||
table, source, profiled_at, n_rows, n_cols, size_bytes, duplicate_rows,
|
||||
duplicate_pct, constant_cols:[str], all_null_cols:[str], null_cell_pct,
|
||||
type_breakdown:{numeric, categorical, datetime, text, boolean},
|
||||
columns:[ColumnProfile], correlations, key_candidates:[str], quality_score,
|
||||
llm, models
|
||||
}
|
||||
ColumnProfile = {
|
||||
name, physical_type, inferred_type, semantic_type, count, n_rows, null_count,
|
||||
null_pct, empty_count, empty_pct, distinct_count, unique_pct, flags:[str],
|
||||
quality_score, numeric:<sub>|None, categorical:<sub>|None, datetime:<sub>|None
|
||||
}
|
||||
numeric_sub = {
|
||||
min, max, mean, median, mode, std, variance, cv, p1, p5, p25, p50, p75, p95,
|
||||
p99, iqr, skew, kurtosis, n_outliers, outlier_pct, zero_pct, negative_pct,
|
||||
distribution_type, histogram
|
||||
}
|
||||
```
|
||||
|
||||
Mapeo de `column_type` fisico DuckDB a `inferred_type`: enteros/decimales/float
|
||||
-> numeric; date/time/timestamp -> datetime; boolean -> boolean; varchar/text ->
|
||||
categorical si `approx_unique <= 50` o `approx_unique/n_rows < 0.5`, si no text.
|
||||
|
||||
Flags por columna: `constant` (distinct_count<=1), `possible_id` (unique_pct>=0.99
|
||||
y null_pct==0), `high_cardinality` (categorical con unique_pct>=high_card_ratio),
|
||||
`mostly_null` (null_pct>0.5).
|
||||
Reference in New Issue
Block a user