Skip to content

Commit

Permalink
Rename to percentile.
Browse files Browse the repository at this point in the history
  • Loading branch information
ncruces committed Jun 6, 2024
1 parent dbf764a commit d213bca
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 38 deletions.
8 changes: 2 additions & 6 deletions ext/stats/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ https://sqlite.org/lang_aggfunc.html
- [X] `RANK() OVER window`
- [X] `DENSE_RANK() OVER window`
- [X] `PERCENT_RANK() OVER window`
- [ ] `PERCENTILE_CONT(percentile) OVER window`
- [ ] `PERCENTILE_DISC(percentile) OVER window`

https://sqlite.org/windowfunctions.html#builtins

Expand All @@ -54,7 +52,5 @@ https://sqlite.org/windowfunctions.html#builtins
## Additional aggregates

- [X] `MEDIAN(expression)`
- [X] `QUANTILE_CONT(expression, quantile)`
- [X] `QUANTILE_DISC(expression, quantile)`

https://duckdb.org/docs/sql/aggregates.html
- [X] `PERCENTILE_CONT(expression, fraction)`
- [X] `PERCENTILE_DISC(expression, fraction)`
33 changes: 19 additions & 14 deletions ext/stats/quantile.go → ext/stats/percentile.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import (

const (
median = iota
quant_cont
quant_disc
percentile_cont
percentile_disc
)

func newQuantile(kind int) func() sqlite3.AggregateFunction {
return func() sqlite3.AggregateFunction { return &quantile{kind: kind} }
func newPercentile(kind int) func() sqlite3.AggregateFunction {
return func() sqlite3.AggregateFunction { return &percentile{kind: kind} }
}

type quantile struct {
type percentile struct {
nums []float64
arg1 []byte
kind int
}

func (q *quantile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
func (q *percentile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
if a := arg[0]; a.NumericType() != sqlite3.NULL {
q.nums = append(q.nums, a.Float())
}
Expand All @@ -36,7 +36,12 @@ func (q *quantile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
}
}

func (q *quantile) Value(ctx sqlite3.Context) {
func (q *percentile) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
// Implementing inverse allows certain queries that don't really need it to succeed.
ctx.ResultError(util.ErrorString("quantile: may not be used as a window function"))
}

func (q *percentile) Value(ctx sqlite3.Context) {
if len(q.nums) == 0 {
return
}
Expand All @@ -47,21 +52,21 @@ func (q *quantile) Value(ctx sqlite3.Context) {
floats []float64
)
if q.kind == median {
float, err = getQuantile(q.nums, 0.5, false)
float, err = getPercentile(q.nums, 0.5, false)
ctx.ResultFloat(float)
} else if err = json.Unmarshal(q.arg1, &float); err == nil {
float, err = getQuantile(q.nums, float, q.kind == quant_disc)
float, err = getPercentile(q.nums, float, q.kind == percentile_disc)
ctx.ResultFloat(float)
} else if err = json.Unmarshal(q.arg1, &floats); err == nil {
err = getQuantiles(q.nums, floats, q.kind == quant_disc)
err = getPercentiles(q.nums, floats, q.kind == percentile_disc)
ctx.ResultJSON(floats)
}
if err != nil {
ctx.ResultError(fmt.Errorf("quantile: %w", err))
ctx.ResultError(fmt.Errorf("percentile: %w", err))
}
}

func getQuantile(nums []float64, pos float64, disc bool) (float64, error) {
func getPercentile(nums []float64, pos float64, disc bool) (float64, error) {
if pos < 0 || pos > 1 {
return 0, util.ErrorString("invalid pos")
}
Expand All @@ -77,9 +82,9 @@ func getQuantile(nums []float64, pos float64, disc bool) (float64, error) {
return math.FMA(f, m1, -math.FMA(f, m0, -m0)), nil
}

func getQuantiles(nums []float64, pos []float64, disc bool) error {
func getPercentiles(nums []float64, pos []float64, disc bool) error {
for i := range pos {
v, err := getQuantile(nums, pos[i], disc)
v, err := getPercentile(nums, pos[i], disc)
if err != nil {
return err
}
Expand Down
22 changes: 11 additions & 11 deletions ext/stats/quantile_test.go → ext/stats/percentile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)

func TestRegister_quantile(t *testing.T) {
func TestRegister_percentile(t *testing.T) {
t.Parallel()

db, err := sqlite3.Open(":memory:")
Expand All @@ -34,8 +34,8 @@ func TestRegister_quantile(t *testing.T) {
stmt, _, err := db.Prepare(`
SELECT
median(x),
quantile_disc(x, 0.5),
quantile_cont(x, '[0.25, 0.5, 0.75]')
percentile_disc(x, 0.5),
percentile_cont(x, '[0.25, 0.5, 0.75]')
FROM data`)
if err != nil {
t.Fatal(err)
Expand All @@ -60,8 +60,8 @@ func TestRegister_quantile(t *testing.T) {
stmt, _, err = db.Prepare(`
SELECT
median(x),
quantile_disc(x, 0.5),
quantile_cont(x, '[0.25, 0.5, 0.75]')
percentile_disc(x, 0.5),
percentile_cont(x, '[0.25, 0.5, 0.75]')
FROM data
WHERE x < 5`)
if err != nil {
Expand All @@ -87,8 +87,8 @@ func TestRegister_quantile(t *testing.T) {
stmt, _, err = db.Prepare(`
SELECT
median(x),
quantile_disc(x, 0.5),
quantile_cont(x, '[0.25, 0.5, 0.75]')
percentile_disc(x, 0.5),
percentile_cont(x, '[0.25, 0.5, 0.75]')
FROM data
WHERE x < 0`)
if err != nil {
Expand All @@ -109,10 +109,10 @@ func TestRegister_quantile(t *testing.T) {

stmt, _, err = db.Prepare(`
SELECT
quantile_disc(x, -2),
quantile_cont(x, +2),
quantile_cont(x, ''),
quantile_cont(x, '[100]')
percentile_disc(x, -2),
percentile_cont(x, +2),
percentile_cont(x, ''),
percentile_cont(x, '[100]')
FROM data`)
if err != nil {
t.Fatal(err)
Expand Down
13 changes: 6 additions & 7 deletions ext/stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
// - regr_slope: slope of the least-squares-fit linear equation
// - regr_intercept: y-intercept of the least-squares-fit linear equation
// - regr_json: all regr stats in a JSON object
// - quantile_disc: discrete quantile
// - quantile_cont: continuous quantile
// - percentile_disc: discrete percentile
// - percentile_cont: continuous percentile
// - median: median value
// - every: boolean and
// - some: boolean or
Expand All @@ -37,12 +37,11 @@
// - percent_rank: relative rank of the row
// - cume_dist: cumulative distribution
//
// See: [ANSI SQL Aggregate Functions], [DuckDB Aggregate Functions]
// See: [ANSI SQL Aggregate Functions]
//
// [Built-in Aggregate Functions]: https://sqlite.org/lang_aggfunc.html
// [Built-in Window Functions]: https://sqlite.org/windowfunctions.html#builtins
// [ANSI SQL Aggregate Functions]: https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
// [DuckDB Aggregate Functions]: https://duckdb.org/docs/sql/aggregates.html
package stats

import "github.com/ncruces/go-sqlite3"
Expand All @@ -67,9 +66,9 @@ func Register(db *sqlite3.Conn) {
db.CreateWindowFunction("regr_intercept", 2, flags, newCovariance(regr_intercept))
db.CreateWindowFunction("regr_count", 2, flags, newCovariance(regr_count))
db.CreateWindowFunction("regr_json", 2, flags, newCovariance(regr_json))
db.CreateWindowFunction("median", 1, flags, newQuantile(median))
db.CreateWindowFunction("quantile_cont", 2, flags, newQuantile(quant_cont))
db.CreateWindowFunction("quantile_disc", 2, flags, newQuantile(quant_disc))
db.CreateWindowFunction("median", 1, flags, newPercentile(median))
db.CreateWindowFunction("percentile_cont", 2, flags, newPercentile(percentile_cont))
db.CreateWindowFunction("percentile_disc", 2, flags, newPercentile(percentile_disc))
db.CreateWindowFunction("every", 1, flags, newBoolean(every))
db.CreateWindowFunction("some", 1, flags, newBoolean(some))
}
Expand Down

0 comments on commit d213bca

Please sign in to comment.