blob: 6f488f97ca460b458020c4d0198b2206aa93d9b0 [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package table
import (
"reflect"
"github.com/aclements/go-gg/generic"
)
// Pivot converts rows of g into columns. label and value must name
// columns in g, and the label column must have type []string. Pivot
// returns a Grouping with a new column named after each distinct
// value in the label column, where the values in that column
// correspond to the values from the value column. All other columns
// (besides label and value) are copied to the output. If, for a given
// column in an output row, no input row has that column in the label
// column, the output cell will have the zero value for its type.
func Pivot(g Grouping, label, value string) Grouping {
// Find all unique values of label. These are the new columns.
labels := []string{}
lset := map[string]int{}
for _, gid := range g.Tables() {
for _, l := range g.Table(gid).MustColumn(label).([]string) {
if _, ok := lset[l]; !ok {
lset[l] = len(lset)
labels = append(labels, l)
}
}
}
// Get all columns that are not label or value.
groupCols := []string{}
for _, col := range g.Columns() {
if col != label && col != value {
groupCols = append(groupCols, col)
}
}
return MapTables(g, func(_ GroupID, t *Table) *Table {
var nt Builder
// Group by all other columns. Each group in gg
// becomes an output row.
gg := GroupBy(t, groupCols...)
// Copy grouped-by values.
for _, groupCol := range groupCols {
cv := reflect.MakeSlice(reflect.TypeOf(t.Column(groupCol)), len(gg.Tables()), len(gg.Tables()))
for i, gid := range gg.Tables() {
sub := gg.Table(gid)
cv.Index(i).Set(reflect.ValueOf(sub.Column(groupCol)).Index(0))
}
nt.Add(groupCol, cv.Interface())
}
// Initialize new columns.
newCols := make([]reflect.Value, len(lset))
vt := reflect.TypeOf(t.MustColumn(value))
for i := range newCols {
newCols[i] = reflect.MakeSlice(vt, len(gg.Tables()), len(gg.Tables()))
}
// Fill in new columns.
for i, gid := range gg.Tables() {
sub := gg.Table(gid)
vcol := reflect.ValueOf(sub.MustColumn(value))
for j, l := range sub.MustColumn(label).([]string) {
val := vcol.Index(j)
newCols[lset[l]].Index(i).Set(val)
}
}
// Add new columns to output table.
for i, newCol := range newCols {
nt.Add(labels[i], newCol.Interface())
}
return nt.Done()
})
}
// Unpivot converts columns of g into rows. The returned Grouping
// consists of the columns of g *not* listed in cols, plus two columns
// named by the label and value arguments. For each input row in g,
// the returned Grouping will have len(cols) output rows. The i'th
// such output row corresponds to column cols[i] in the input row. The
// label column will contain the name of the unpivoted column,
// cols[i], and the value column will contain that column's value from
// the input row. The values of all other columns in the input row
// will be repeated across the output rows. All columns in cols must
// have the same type.
func Unpivot(g Grouping, label, value string, cols ...string) Grouping {
if len(cols) == 0 {
panic("Unpivot requires at least 1 column")
}
colSet := map[string]bool{}
for _, col := range cols {
colSet[col] = true
}
return MapTables(g, func(_ GroupID, t *Table) *Table {
var nt Builder
// Repeat all other columns len(cols) times.
ntlen := t.Len() * len(cols)
for _, name := range t.Columns() {
if colSet[name] || name == label || name == value {
continue
}
col := reflect.ValueOf(t.Column(name))
ncol := reflect.MakeSlice(col.Type(), ntlen, ntlen)
for i, l := 0, col.Len(); i < l; i++ {
v := col.Index(i)
for j := range cols {
ncol.Index(i*len(cols) + j).Set(v)
}
}
nt.Add(name, ncol.Interface())
}
// Get input columns.
var vt reflect.Type
colvs := make([]reflect.Value, len(cols))
for i, col := range cols {
colvs[i] = reflect.ValueOf(t.MustColumn(col))
if i == 0 {
vt = colvs[i].Type()
} else if vt != colvs[i].Type() {
panic(&generic.TypeError{vt, colvs[i].Type(), "; cannot Unpivot columns with different types"})
}
}
// Create label and value columns.
lcol := make([]string, 0, ntlen)
vcol := reflect.MakeSlice(vt, ntlen, ntlen)
for i := 0; i < t.Len(); i++ {
lcol = append(lcol, cols...)
for j, colv := range colvs {
vcol.Index(i*len(cols) + j).Set(colv.Index(i))
}
}
nt.Add(label, lcol).Add(value, vcol.Interface())
return nt.Done()
})
}