Files
fn_registry/python/functions/datascience/select_groupby_keys.md
T
egutierrez 96da9e3015 feat(eda): funciones de agregación/OLAP para AutomaticEDA (groupby/pivot push-down + selección LLM)
Cuatro funciones nuevas del grupo eda que nutren el capítulo AGREGACION:
- select_groupby_keys (pure): elige categóricas agrupables + numéricas medida desde el TableProfile.
- groupby_stats_duckdb (impure): GROUP BY push-down en DuckDB (count/mean/median/std/min/max por grupo).
- pivot_table_duckdb (impure): pivot A×B push-down, limitado a top filas/cols para no cortar.
- suggest_aggregations_llm (impure): el LLM elige las agregaciones interesantes con fallback determinista.

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

7.4 KiB

id, name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, example, tested, tests, test_file_path, file_path, params, output
id name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports example tested tests test_file_path file_path params output
select_groupby_keys_py_datascience select_groupby_keys function py datascience 1.0.0 pure def select_groupby_keys(profile: dict, max_keys: int = 3, max_card: int = 20, max_measures: int = 4) -> dict Elige deterministicamente las columnas categoricas mas interesantes para GROUP BY, las numericas medida y pares pivote a partir de un TableProfile del grupo eda. Respaldo cuantitativo para el capitulo de agregacion/OLAP de un EDA. Funcion pura, no muta el input, nunca lanza.
eda
aggregation
groupby
olap
profiling
datascience
false
from datascience import select_groupby_keys profile = { "n_rows": 891, "key_candidates": ["passenger_id"], "columns": [ {"name": "sex", "inferred_type": "categorical", "distinct_count": 2, "unique_pct": 0.002, "null_pct": 0.0, "flags": [], "categorical": {"imbalance": 1.8}, "numeric": None}, {"name": "pclass", "inferred_type": "categorical", "distinct_count": 3, "unique_pct": 0.003, "null_pct": 0.0, "flags": [], "categorical": {"imbalance": 2.5}, "numeric": None}, {"name": "fare", "inferred_type": "numeric", "distinct_count": 200, "unique_pct": 0.2, "null_pct": 0.0, "flags": [], "numeric": {"std": 49.7, "cv": 1.54}, "categorical": None}, ], } select_groupby_keys(profile) # {"group_keys": [{"col": "sex", ...}, {"col": "pclass", ...}], # "measures": ["fare"], # "pivots": [{"index": "sex", "columns": "pclass", "value": "fare"}], # "note": "2 clave(s) de grupo: sex, pclass; 1 medida(s): fare; 1 pivot(s)."} true
test_titanic_picks_good_cats_excludes_id_and_constant
test_titanic_measures_exclude_id_constant_and_keep_numerics
test_titanic_generates_one_pivot
test_empty_profile_returns_all_empty_and_does_not_crash
test_none_profile_does_not_crash
test_only_numerics_yields_empty_group_keys_and_no_pivots
test_high_cardinality_and_max_card_are_excluded
test_max_keys_limits_group_keys
test_three_keys_cap_pivots_to_two
test_does_not_mutate_input
python/functions/datascience/select_groupby_keys_test.py python/functions/datascience/select_groupby_keys.py
name desc
profile TableProfile dict del grupo eda (p.ej. salida de summarize_table_duckdb). Se lee de forma defensiva (.get / or [] / isinstance). Claves usadas: columns (list[ColumnProfile]), key_candidates (list de nombres de columna o dicts {name}), n_rows. Cada ColumnProfile usa: name, inferred_type ("numeric"|"categorical"|"datetime"|"text"|"boolean"), distinct_count, unique_pct (0..1), null_pct (0..1), flags (list[str], reconoce "possible_id"/"high_cardinality"/"constant"), numeric ({std, cv, ...}|None) y categorical ({imbalance, mode_pct, ...}|None).
name desc
max_keys Numero maximo de claves de grupo (group_keys) a devolver. Default 3.
name desc
max_card Cardinalidad maxima (distinct_count) que una columna categorica puede tener para seguir siendo candidata a clave de grupo. Default 20.
name desc
max_measures Numero maximo de columnas medida (nombres) a devolver. Default 4.
dict con group_keys (list de {col, cardinality, score} ordenada por score desc), measures (list[str] de nombres de columnas numericas ordenadas por dispersion), pivots (list de {index, columns, value}, hasta 2 pares categorica x categorica con la primera measure como valor) y note (str, resumen corto en espanol de lo elegido). Ante profile vacio/None devuelve todas las listas vacias y una note descriptiva; nunca lanza.

Ejemplo

from datascience import select_groupby_keys

# TableProfile estilo titanic: 2 categoricas buenas, 1 numerica medida,
# 1 id secuencial (descartado) y un key_candidate declarado.
profile = {
    "n_rows": 891,
    "key_candidates": ["passenger_id"],
    "columns": [
        {"name": "sex", "inferred_type": "categorical", "distinct_count": 2,
         "unique_pct": 0.002, "null_pct": 0.0, "flags": [],
         "categorical": {"imbalance": 1.8}, "numeric": None},
        {"name": "pclass", "inferred_type": "categorical", "distinct_count": 3,
         "unique_pct": 0.003, "null_pct": 0.0, "flags": [],
         "categorical": {"imbalance": 2.5}, "numeric": None},
        {"name": "fare", "inferred_type": "numeric", "distinct_count": 200,
         "unique_pct": 0.2, "null_pct": 0.0, "flags": [],
         "numeric": {"std": 49.7, "cv": 1.54}, "categorical": None},
        {"name": "passenger_id", "inferred_type": "numeric", "distinct_count": 891,
         "unique_pct": 1.0, "null_pct": 0.0, "flags": ["possible_id"],
         "numeric": {"std": 257.4, "cv": 0.58}, "categorical": None},
    ],
}

select_groupby_keys(profile)
# {
#   "group_keys": [
#       {"col": "sex",    "cardinality": 2, "score": 0.5556},
#       {"col": "pclass", "cardinality": 3, "score": 0.4},
#   ],
#   "measures": ["fare"],            # passenger_id excluido (id secuencial)
#   "pivots": [{"index": "sex", "columns": "pclass", "value": "fare"}],
#   "note": "2 clave(s) de grupo: sex, pclass; 1 medida(s): fare; 1 pivot(s).",
# }

Cuando usarla

Cuando hayas perfilado una tabla con el grupo eda (p.ej. summarize_table_duckdb) y necesites decidir, sin mirar los datos, por qué columnas merece la pena agrupar (GROUP BY) y qué métricas numéricas agregar: el respaldo cuantitativo del capítulo de agregación/OLAP de un AutomaticEDA, o para proponer pivotes en un dashboard. Es la capa de selección sobre el TableProfile crudo: lee el perfil, ordena candidatos de forma determinista y no toca los datos.

Notas

Función pura, sin I/O ni dependencias externas (solo stdlib), no muta profile. Lectura defensiva total (.get, or [], isinstance): un {} o None produce {"group_keys": [], "measures": [], "pivots": [], "note": ...} y nunca lanza.

Criterios de selección (deterministas):

  • group_keys — candidatas con inferred_type en ("categorical","boolean"). Se descartan las que estén en key_candidates, con flag possible_id/high_cardinality/constant, con distinct_count fuera de [2, max_card], o all-null (null_pct >= 0.999). score = card_score * balance_score: card_score mantiene un plateau para cardinalidad moderada (2..12) y decae hacia max_card; balance_score = 1/imbalance usando categorical.imbalance si está, aproximando con mode_pct si no, o un valor neutro (0.5) en último caso. Devuelve hasta max_keys, ordenadas por score desc (empates por orden de columna).
  • measures — candidatas con inferred_type en ("numeric","integer","float"). Se descartan id-like (flag possible_id y unique_pct >= 0.99) y constantes (numeric.std == 0 o None). Se rankean por dispersión informativa: abs(cv) si está, si no abs(std). Devuelve hasta max_measures nombres (strings).
  • pivots — hasta 2 pares (group_keys[i].col, group_keys[j].col) con i<j y la primera measure como valor. Vacío si hay menos de 2 group_keys.

Caveat de ranking de measures: mezclar cv (adimensional) con std (en unidades de la columna) cuando una columna carece de cv puede dar órdenes poco comparables entre columnas; se prefiere cv siempre que esté disponible.