blob: 77bb78eb5633fe5645e601d52c78d9ed52b26c7f [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 ggstat import ( "math" "github.com/aclements/go-gg/generic/slice" "github.com/aclements/go-gg/table" "github.com/aclements/go-moremath/stats" ) // A FunctionDomainer computes the domain over which to evaluate a // statistical function. type FunctionDomainer interface { // FunctionDomain computes the domain of a particular column // within a table. It takes a Grouping and a column in that // Grouping to compute the domain of and returns a function // that computes the domain for a specific group in the // Grouping. This makes it possible for FunctionDomain to // easily compute either Grouping-wide domains, or per-Table // domains. // // The returned domain may be (NaN, NaN) to indicate that // there is no data and the domain is vacuous. FunctionDomain(g table.Grouping, col string) func(gid table.GroupID) (min, max float64) } // DomainFixed is a FunctionDomainer that returns a fixed domain. type DomainFixed struct { Min, Max float64 } var _ FunctionDomainer = DomainFixed{} func (r DomainFixed) FunctionDomain(g table.Grouping, col string) func(gid table.GroupID) (min, max float64) { return func(table.GroupID) (min, max float64) { return r.Min, r.Max } } // DomainData is a FunctionDomainer that computes domains based on the // bounds of the data. type DomainData struct { // Widen expands the domain by Widen times the span of the // data. // // A value of 1.0 means to use exactly the bounds of the data. // If Widen is 0, it is treated as 1.1 (that is, widen the // domain by 10%, or 5% on the left and 5% on the right). Widen float64 // SplitGroups indicates that each group in the table should // have a separate domain based on the data in that group // alone. The default, false, indicates that the domain should // be based on all of the data in the table combined. This // makes it possible to stack functions and easier to compare // them across groups. SplitGroups bool } var _ FunctionDomainer = DomainData{} const defaultWiden = 1.1 func (r DomainData) FunctionDomain(g table.Grouping, col string) func(gid table.GroupID) (min, max float64) { widen := r.Widen if widen <= 0 { widen = defaultWiden } var xs []float64 if !r.SplitGroups { // Compute combined bounds. gmin, gmax := math.NaN(), math.NaN() for _, gid := range g.Tables() { t := g.Table(gid) slice.Convert(&xs, t.MustColumn(col)) xmin, xmax := stats.Bounds(xs) if xmin < gmin || math.IsNaN(gmin) { gmin = xmin } if xmax > gmax || math.IsNaN(gmax) { gmax = xmax } } // Widen bounds. span := gmax - gmin gmin, gmax = gmin-span*(widen-1)/2, gmax+span*(widen-1)/2 return func(table.GroupID) (min, max float64) { return gmin, gmax } } return func(gid table.GroupID) (min, max float64) { // Compute bounds. slice.Convert(&xs, g.Table(gid).MustColumn(col)) min, max = stats.Bounds(xs) // Widen bounds. span := max - min min, max = min-span*(widen-1)/2, max+span*(widen-1)/2 return } }