--- name: pivot_table_duckdb kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def pivot_table_duckdb(db_path: str, table: str, index: str, columns: str, value: str, agg: str = 'mean', top_rows: int = 10, top_cols: int = 8) -> dict" description: "Pivot table (index x columns -> agg(value)) calculada con push-down SQL en DuckDB (GROUP BY en el motor, sin traer filas a RAM) y recortada a las top_rows filas y top_cols columnas con mas observaciones para que quepa entera en un PDF movil / slide PPTX sin cortarse. Version push-down para tablas grandes de la funcion pura `pivot` (que pivota list[dict] en memoria)." tags: [eda, pivot, duckdb, aggregate, datascience, push-down, report] uses_functions: [duckdb_query_readonly_py_infra] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] params: - name: db_path desc: "Ruta al archivo DuckDB. Debe existir; el modo read_only NO crea la base." - name: table desc: "Nombre de la tabla a pivotar. Se interpola citado con dobles comillas (DuckDB no admite parametros para identificadores)." - name: index desc: "Columna cuyos valores forman las filas de la pivot (eje vertical)." - name: columns desc: "Columna cuyos valores forman las columnas de la pivot (eje horizontal)." - name: value desc: "Columna numerica a agregar en cada celda. Ignorada cuando agg='count'." - name: agg desc: "Funcion de agregacion: mean, sum, count, min, max, median. mean->avg(), count->COUNT(*). Otro valor devuelve {status:'error'}." - name: top_rows desc: "Numero maximo de filas a conservar, elegidas por mayor numero de observaciones (suma de COUNT(*) por valor de index). Default 10." - name: top_cols desc: "Numero maximo de columnas a conservar, elegidas por mayor numero de observaciones (suma de COUNT(*) por valor de columns). Default 8." output: "dict. En exito {status:'ok', index, columns, value, agg, row_labels:[...], col_labels:[...], matrix:[[...]], truncated_rows:bool, truncated_cols:bool, note:str}. matrix tiene len(row_labels) filas y cada fila len(col_labels) celdas (valor agregado o None si la combinacion no existe). truncated_* indica si hubo mas filas/columnas que el top. En error {status:'error', error:str} (no lanza)." tested: true tests: ["pivot mean labels y celda conocida", "pivot trunca a top rows y top cols", "pivot count no necesita value real", "pivot db inexistente devuelve error sin lanzar", "pivot agg invalido devuelve error"] test_file_path: "python/functions/datascience/pivot_table_duckdb_test.py" file_path: "python/functions/datascience/pivot_table_duckdb.py" --- ## Ejemplo ```python import duckdb from datascience import pivot_table_duckdb # Tabla DuckDB de prueba estilo titanic: sex x pclass -> mean(fare). db = "/tmp/pivot_demo.duckdb" con = duckdb.connect(db) con.execute( "CREATE TABLE titanic AS SELECT * FROM (VALUES " "('male',1,211.3),('female',1,151.5),('male',3,7.9)," "('female',3,16.7),('male',1,52.0),('female',2,41.6)" ") t(sex, pclass, fare)" ) con.close() res = pivot_table_duckdb(db, "titanic", index="sex", columns="pclass", value="fare", agg="mean") print(res["status"]) # ok print(res["row_labels"]) # ['female', 'male'] (orden por nÂș de observaciones desc; empate -> etiqueta) print(res["col_labels"]) # [1, 3, 2] (pclass=1 tiene 3 obs, pclass=3 -> 2, pclass=2 -> 1) print(res["matrix"]) # [[151.5, 16.7, 41.6], [131.65, 7.9, None]] (male/pclass=2 no existe -> None) ``` ## Cuando usarla Cuando quieres una pivot table (`index` x `columns` -> `agg(value)`) de una tabla DuckDB con MUCHAS filas y necesitas que el resultado quepa entero en un informe: un PDF abierto en el movil o un slide PPTX, donde una matriz de 50x30 se cortaria. La agregacion se hace push-down en el motor (no traes las filas a RAM) y el resultado se limita a las `top_rows` x `top_cols` combinaciones con mas observaciones. Encaja en el flujo `eda` para resumir el cruce de dos categoricas (sexo x clase, region x producto) contra una metrica. Para pivotar un `list[dict]` ya cargado en memoria usa la funcion pura `pivot_py_datascience`; esta es la version push-down sobre disco. ## Gotchas - Funcion impura: lee un archivo DuckDB del disco (read_only, nunca lo modifica). - Recorta a `top_rows` x `top_cols` por numero de observaciones (suma de `COUNT(*)`), NO por magnitud del valor agregado. Si habia mas filas/columnas, `truncated_rows` / `truncated_cols` quedan en True y esas combinaciones NO aparecen en la matriz. - Las celdas sin datos (combinacion `index` x `columns` que no existe en la tabla) se rellenan con `None`, no con 0: distinguir "cero medido" de "sin observaciones". - `agg='count'` cuenta filas por celda con `COUNT(*)` e ignora `value` (puedes pasar cualquier nombre de columna). Para el resto de aggs, `value` debe ser una columna numerica real o la query fallara con `{status:'error'}`. - `agg` solo admite mean, sum, count, min, max, median; cualquier otro valor devuelve `{status:'error'}` sin tocar la base. - Orden de `row_labels` / `col_labels`: por numero de observaciones descendente, con desempate estable por etiqueta. No es orden alfabetico ni el de aparicion. - La query se ejecuta con `sandbox=False` en `duckdb_query_readonly` (uso interno confiable: el SQL lo construye esta funcion, no un cliente externo).