Files
fn_registry/functions/datascience/pivot.go
egutierrez 9c0d24d3ef feat: funciones Go — core (cron, join_by_key, validate_struct), datascience (pivot, diff_entities), infra (http, cache, cron_ticker)
Nuevas funciones Go con tests en tres dominios:
- core: parse_cron_expr, next_cron_time, join_by_key, validate_struct_fields + tipo CronSchedule
- datascience: pivot (tabla dinámica), diff_entities (comparación de entidades)
- infra: http_get_json, http_post_json, http_download_file, cache_to_sqlite, cron_ticker

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:11:12 +02:00

111 lines
2.3 KiB
Go

package datascience
// Pivot transforma datos del formato largo al formato ancho (pivot table).
// Agrupa por index, expande los valores unicos de columns como nuevas columnas
// y agrega values con la funcion indicada.
// Funciones de agregacion soportadas: sum, count, mean, min, max, first, last.
// Valores numericos faltantes se rellenan con 0.
func Pivot(rows []map[string]any, index, columns, values, agg string) []map[string]any {
// Mantener orden de aparicion de index y column values
indexOrder := []any{}
seenIndex := map[any]bool{}
colOrder := []any{}
seenCols := map[any]bool{}
for _, row := range rows {
idx := row[index]
col := row[columns]
if !seenIndex[idx] {
seenIndex[idx] = true
indexOrder = append(indexOrder, idx)
}
if !seenCols[col] {
seenCols[col] = true
colOrder = append(colOrder, col)
}
}
// Acumular: groups[indexVal][colVal] = lista de valores
type key struct{ idx, col any }
groups := map[key][]any{}
for _, row := range rows {
idx := row[index]
col := row[columns]
val := row[values]
if val != nil {
k := key{idx, col}
groups[k] = append(groups[k], val)
}
}
aggregate := func(vals []any, fn string) any {
if len(vals) == 0 {
return 0
}
switch fn {
case "count":
return len(vals)
case "first":
return vals[0]
case "last":
return vals[len(vals)-1]
}
// Funciones numericas: sum, mean, min, max
toFloat := func(v any) float64 {
switch n := v.(type) {
case float64:
return n
case float32:
return float64(n)
case int:
return float64(n)
case int64:
return float64(n)
case int32:
return float64(n)
}
return 0
}
sum := 0.0
mn := toFloat(vals[0])
mx := toFloat(vals[0])
for _, v := range vals {
f := toFloat(v)
sum += f
if f < mn {
mn = f
}
if f > mx {
mx = f
}
}
switch fn {
case "sum":
return sum
case "mean":
return sum / float64(len(vals))
case "min":
return mn
case "max":
return mx
}
return sum
}
result := make([]map[string]any, 0, len(indexOrder))
for _, idx := range indexOrder {
record := map[string]any{index: idx}
for _, col := range colOrder {
k := key{idx, col}
vals := groups[k]
if len(vals) > 0 {
record[col.(string)] = aggregate(vals, agg)
} else {
record[col.(string)] = 0
}
}
result = append(result, record)
}
return result
}