From 34d028953425344da5e6ef3581e7666f45859b24 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Thu, 6 Jun 2024 19:52:49 +0100 Subject: [PATCH] Rename to percentile. --- ext/stats/TODO.md | 8 ++--- ext/stats/{quantile.go => percentile.go} | 33 +++++++++++-------- .../{quantile_test.go => percentile_test.go} | 22 ++++++------- ext/stats/stats.go | 13 ++++---- 4 files changed, 38 insertions(+), 38 deletions(-) rename ext/stats/{quantile.go => percentile.go} (52%) rename ext/stats/{quantile_test.go => percentile_test.go} (84%) diff --git a/ext/stats/TODO.md b/ext/stats/TODO.md index f7e2a3c..d8d2f35 100644 --- a/ext/stats/TODO.md +++ b/ext/stats/TODO.md @@ -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 @@ -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 \ No newline at end of file +- [X] `PERCENTILE_CONT(expression, fraction)` +- [X] `PERCENTILE_DISC(expression, fraction)` \ No newline at end of file diff --git a/ext/stats/quantile.go b/ext/stats/percentile.go similarity index 52% rename from ext/stats/quantile.go rename to ext/stats/percentile.go index 8ebcab8..9da7646 100644 --- a/ext/stats/quantile.go +++ b/ext/stats/percentile.go @@ -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()) } @@ -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("percentile: may not be used as a window function")) +} + +func (q *percentile) Value(ctx sqlite3.Context) { if len(q.nums) == 0 { return } @@ -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") } @@ -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 } diff --git a/ext/stats/quantile_test.go b/ext/stats/percentile_test.go similarity index 84% rename from ext/stats/quantile_test.go rename to ext/stats/percentile_test.go index eb83043..9251a67 100644 --- a/ext/stats/quantile_test.go +++ b/ext/stats/percentile_test.go @@ -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:") @@ -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) @@ -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 { @@ -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 { @@ -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) diff --git a/ext/stats/stats.go b/ext/stats/stats.go index d32b323..b1bed23 100644 --- a/ext/stats/stats.go +++ b/ext/stats/stats.go @@ -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 @@ -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" @@ -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)) }