From aaa36569ded69399f73848440e18329f7457354b Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Mon, 26 Sep 2016 16:26:34 -0400 Subject: [PATCH 01/13] first add --- src/pg/sql/18_outliers.sql | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/pg/sql/18_outliers.sql diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql new file mode 100644 index 0000000..48588ce --- /dev/null +++ b/src/pg/sql/18_outliers.sql @@ -0,0 +1,52 @@ +CREATE OR REPLACE FUNCTION CDB_StaticOutlier(attr numeric, threshold numeric) +RETURNS numeric +AS $$ +BEGIN + + RETURN attr > threshold; + +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION CDB_PercentOutlier(attr numeric[], outlier_fraction numeric, ids int[]) +RETURNS TABLE(outlier boolean, rowid int) +AS $$ +DECLARE + avg_val numeric; + out_vals boolean[]; +BEGIN + + SELECT avg(i) INTO avg_val FROM unnest(attr) As x(i); + + SELECT array_agg( i > avg_val * outlier_fraction) INTO out_vals + FROM unnest(attr) As x(i); + + RETURN QUERY + SELECT unnest(out_vals) As outlier, + unnest(ids) As rowid; + +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION CDB_StdDevOutlier(attrs numeric[], num_deviations numeric, ids int[]) +RETURNS TABLE(outlier boolean, rowid int) +AS $$ +DECLARE + stddev_val numeric; + avg_val numeric; + out_vals boolean[]; +BEGIN + + SELECT stddev(i), avg(i) INTO stddev_val, avg_val + FROM unnest(attrs) As x(i); + + SELECT array_agg(abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals + FROM unnest(attrs) As x(i); + + + RETURN QUERY + SELECT unnest(out_vals) As outlier, + unnest(ids) As rowid; +END; +$$ LANGUAGE plpgsql; From f2bb0b496bc9f86d2d09db583cb4677d73d94a50 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Mon, 26 Sep 2016 16:51:22 -0400 Subject: [PATCH 02/13] small fixes --- src/pg/sql/18_outliers.sql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index 48588ce..7d4b3b2 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -1,5 +1,8 @@ + +-- Find outliers using a static threshold +-- CREATE OR REPLACE FUNCTION CDB_StaticOutlier(attr numeric, threshold numeric) -RETURNS numeric +RETURNS boolean AS $$ BEGIN @@ -8,6 +11,7 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- Find outliers by a percentage above the threshold CREATE OR REPLACE FUNCTION CDB_PercentOutlier(attr numeric[], outlier_fraction numeric, ids int[]) RETURNS TABLE(outlier boolean, rowid int) @@ -29,6 +33,8 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- Find outliers above a given number of standard deviations from the mean + CREATE OR REPLACE FUNCTION CDB_StdDevOutlier(attrs numeric[], num_deviations numeric, ids int[]) RETURNS TABLE(outlier boolean, rowid int) AS $$ From b8accb48fc15fa6ed235700f363e33a7483621c0 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Wed, 28 Sep 2016 15:55:56 -0400 Subject: [PATCH 03/13] adds tests --- src/pg/sql/18_outliers.sql | 16 +++++-- src/pg/test/expected/18_outliers.out | 16 +++++++ src/pg/test/sql/18_outliers.sql | 70 ++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 src/pg/test/expected/18_outliers.out create mode 100644 src/pg/test/sql/18_outliers.sql diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index 7d4b3b2..3ae7a5e 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -12,6 +12,7 @@ END; $$ LANGUAGE plpgsql; -- Find outliers by a percentage above the threshold +-- TODO: add symmetric option? `symmetric boolean DEFAULT false` CREATE OR REPLACE FUNCTION CDB_PercentOutlier(attr numeric[], outlier_fraction numeric, ids int[]) RETURNS TABLE(outlier boolean, rowid int) @@ -21,10 +22,15 @@ DECLARE out_vals boolean[]; BEGIN - SELECT avg(i) INTO avg_val FROM unnest(attr) As x(i); + SELECT avg(i) INTO avg_val + FROM unnest(attr) As x(i); - SELECT array_agg( i > avg_val * outlier_fraction) INTO out_vals - FROM unnest(attr) As x(i); + SELECT array_agg( + CASE WHEN avg_val = 0 THEN null + ELSE outlier_fraction > i / avg_val + END + ) INTO out_vals + FROM unnest(attr) As x(i); RETURN QUERY SELECT unnest(out_vals) As outlier, @@ -45,10 +51,10 @@ DECLARE BEGIN SELECT stddev(i), avg(i) INTO stddev_val, avg_val - FROM unnest(attrs) As x(i); + FROM unnest(attrs) As x(i); SELECT array_agg(abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals - FROM unnest(attrs) As x(i); + FROM unnest(attrs) As x(i); RETURN QUERY diff --git a/src/pg/test/expected/18_outliers.out b/src/pg/test/expected/18_outliers.out new file mode 100644 index 0000000..1c0f186 --- /dev/null +++ b/src/pg/test/expected/18_outliers.out @@ -0,0 +1,16 @@ +outlier|rowid +t|11 +t|16 +t|17 +outlier|rowid +t|16 +t|17 +outlier|rowid +t|8 +t|11 +t|16 +outlier|rowid +t|8 +t|9 +t|11 +t|15 diff --git a/src/pg/test/sql/18_outliers.sql b/src/pg/test/sql/18_outliers.sql new file mode 100644 index 0000000..432d4c1 --- /dev/null +++ b/src/pg/test/sql/18_outliers.sql @@ -0,0 +1,70 @@ +SET client_min_messages TO WARNING; +\set ECHO none +\pset format unaligned + +-- +-- postgres=# select round(avg(i), 3) as avg, +-- round(stddev(i), 3) as stddev, +-- round(avg(i) + stddev(i), 3) as one_stddev, +-- round(avg(i) + 2 * stddev(i), 3) As two_stddev +-- from unnest(ARRAY[1,3,2,3,5,1,2,32,12,3,57,2,1,4,2,100]) As x(i); +-- avg | stddev | one_stddev | two_stddev +-- --------+--------+------------+------------ +-- 14.375 | 27.322 | 41.697 | 69.020 + + +-- With an threshold of 1.0 standard deviation, ids 11, 16, and 17 are outliers +WITH a AS ( + SELECT + ARRAY[1,3,2,3,5,1,2,32,12, 3,57, 2, 1, 4, 2,100,-100]::numeric[] As vals, ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids +), b As ( + SELECT + (cdb_StdDevOutlier(vals, 1.0, ids)).* + FROM a + ORDER BY ids) +SELECT * +FROM b +WHERE outlier IS TRUE; + +-- With a threshold of 2.0 standard deviations, id 16 is the only outlier +WITH a AS ( + SELECT + ARRAY[1,3,2,3,5,1,2,32,12, 3,57, 2, 1, 4, 2,100,-100]::numeric[] As vals, + ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids +), b As ( + SELECT + (CDB_StdDevOutlier(vals, 2.0, ids)).* + FROM a + ORDER BY ids) +SELECT * +FROM b +WHERE outlier IS TRUE; + +-- With a ratio threshold of 2.0 threshold (100% above or below the mean) +-- which is greater than ~21, which are values +WITH a AS ( + SELECT + ARRAY[1,3,2,3,5,1,2,32,12, 3,57, 2, 1, 4, 2,100,-100]::numeric[] As vals, + ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids +), b As ( + SELECT + (CDB_PercentOutlier(vals, 2.0, ids)).* + FROM a + ORDER BY ids) +SELECT * + FROM b + WHERE outlier IS TRUE; + +-- With a static threshold of 11, what are the outliers +WITH a AS ( + SELECT + ARRAY[1,3,2,3,5,1,2,32,12, 3,57, 2, 1, 4, 2,100,-100]::numeric[] As vals, + ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids + ), b As ( + SELECT unnest(vals) As v, unnest(ids) as i + FROM a + ) +SELECT CDB_StaticOutlier(v, 11.0), i + FROM b +WHERE CDB_StaticOutlier(v, 11.0) is True +ORDER BY i; From acde384157bf0ffe516c6ab3c5504e61fd28dce9 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Wed, 28 Sep 2016 16:27:41 -0400 Subject: [PATCH 04/13] update tests --- src/pg/sql/18_outliers.sql | 2 +- .../{18_outliers.out => 18_outliers_test.out} | 8 +++++++- .../sql/{18_outliers.sql => 18_outliers_test.sql} | 11 ++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) rename src/pg/test/expected/{18_outliers.out => 18_outliers_test.out} (54%) rename src/pg/test/sql/{18_outliers.sql => 18_outliers_test.sql} (86%) diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index 3ae7a5e..6b6d943 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -27,7 +27,7 @@ BEGIN SELECT array_agg( CASE WHEN avg_val = 0 THEN null - ELSE outlier_fraction > i / avg_val + ELSE outlier_fraction < i::numeric / avg_val::numeric END ) INTO out_vals FROM unnest(attr) As x(i); diff --git a/src/pg/test/expected/18_outliers.out b/src/pg/test/expected/18_outliers_test.out similarity index 54% rename from src/pg/test/expected/18_outliers.out rename to src/pg/test/expected/18_outliers_test.out index 1c0f186..a329e07 100644 --- a/src/pg/test/expected/18_outliers.out +++ b/src/pg/test/expected/18_outliers_test.out @@ -1,16 +1,22 @@ +SET client_min_messages TO WARNING; +\set ECHO none outlier|rowid t|11 t|16 t|17 +(3 rows) outlier|rowid t|16 t|17 +(2 rows) outlier|rowid t|8 t|11 t|16 +(3 rows) outlier|rowid t|8 t|9 t|11 -t|15 +t|16 +(4 rows) diff --git a/src/pg/test/sql/18_outliers.sql b/src/pg/test/sql/18_outliers_test.sql similarity index 86% rename from src/pg/test/sql/18_outliers.sql rename to src/pg/test/sql/18_outliers_test.sql index 432d4c1..c12c889 100644 --- a/src/pg/test/sql/18_outliers.sql +++ b/src/pg/test/sql/18_outliers_test.sql @@ -19,7 +19,7 @@ WITH a AS ( ARRAY[1,3,2,3,5,1,2,32,12, 3,57, 2, 1, 4, 2,100,-100]::numeric[] As vals, ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids ), b As ( SELECT - (cdb_StdDevOutlier(vals, 1.0, ids)).* + (cdb_crankshaft.cdb_StdDevOutlier(vals, 1.0, ids)).* FROM a ORDER BY ids) SELECT * @@ -33,7 +33,7 @@ WITH a AS ( ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids ), b As ( SELECT - (CDB_StdDevOutlier(vals, 2.0, ids)).* + (cdb_crankshaft.CDB_StdDevOutlier(vals, 2.0, ids)).* FROM a ORDER BY ids) SELECT * @@ -48,7 +48,7 @@ WITH a AS ( ARRAY[1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15, 16, 17]::int[] As ids ), b As ( SELECT - (CDB_PercentOutlier(vals, 2.0, ids)).* + (cdb_crankshaft.CDB_PercentOutlier(vals, 2.0, ids)).* FROM a ORDER BY ids) SELECT * @@ -64,7 +64,8 @@ WITH a AS ( SELECT unnest(vals) As v, unnest(ids) as i FROM a ) -SELECT CDB_StaticOutlier(v, 11.0), i +SELECT cdb_crankshaft.CDB_StaticOutlier(v, 11.0) As outlier, i As rowid FROM b -WHERE CDB_StaticOutlier(v, 11.0) is True +WHERE cdb_crankshaft.CDB_StaticOutlier(v, 11.0) is True ORDER BY i; + From 8bc6f69a1bf992dce2668652e722c42de06715d0 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 29 Sep 2016 10:12:32 -0400 Subject: [PATCH 05/13] adding exceptions to improve robustness --- src/pg/sql/18_outliers.sql | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index 3ae7a5e..1fa8d93 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -14,7 +14,7 @@ $$ LANGUAGE plpgsql; -- Find outliers by a percentage above the threshold -- TODO: add symmetric option? `symmetric boolean DEFAULT false` -CREATE OR REPLACE FUNCTION CDB_PercentOutlier(attr numeric[], outlier_fraction numeric, ids int[]) +CREATE OR REPLACE FUNCTION CDB_PercentOutlier(column_values numeric[], outlier_fraction numeric, ids int[]) RETURNS TABLE(outlier boolean, rowid int) AS $$ DECLARE @@ -23,14 +23,15 @@ DECLARE BEGIN SELECT avg(i) INTO avg_val - FROM unnest(attr) As x(i); + FROM unnest(column_values) As x(i); + + IF avg_val = 0 THEN + RAISE EXCEPTION 'Mean value is zero. Try another outlier method.'; + END IF; SELECT array_agg( - CASE WHEN avg_val = 0 THEN null - ELSE outlier_fraction > i / avg_val - END - ) INTO out_vals - FROM unnest(attr) As x(i); + outlier_fraction > i / avg_val) INTO out_vals + FROM unnest(column_values) As x(i); RETURN QUERY SELECT unnest(out_vals) As outlier, @@ -53,7 +54,12 @@ BEGIN SELECT stddev(i), avg(i) INTO stddev_val, avg_val FROM unnest(attrs) As x(i); - SELECT array_agg(abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals + IF stddev_val = 0 THEN + RAISE EXCEPTION 'Standard deviation of input data is zero'; + END IF; + + SELECT array_agg( + abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals FROM unnest(attrs) As x(i); From 5754087140da57a5e716a1e259294053ab229ede Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 29 Sep 2016 11:09:10 -0400 Subject: [PATCH 06/13] adds symmetric option for stddev outlier --- src/pg/sql/18_outliers.sql | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index 1fa8d93..454e383 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -12,7 +12,7 @@ END; $$ LANGUAGE plpgsql; -- Find outliers by a percentage above the threshold --- TODO: add symmetric option? `symmetric boolean DEFAULT false` +-- TODO: add symmetric option? `is_symmetric boolean DEFAULT false` CREATE OR REPLACE FUNCTION CDB_PercentOutlier(column_values numeric[], outlier_fraction numeric, ids int[]) RETURNS TABLE(outlier boolean, rowid int) @@ -42,7 +42,7 @@ $$ LANGUAGE plpgsql; -- Find outliers above a given number of standard deviations from the mean -CREATE OR REPLACE FUNCTION CDB_StdDevOutlier(attrs numeric[], num_deviations numeric, ids int[]) +CREATE OR REPLACE FUNCTION CDB_StdDevOutlier(attrs numeric[], num_deviations numeric, ids int[], is_symmetric boolean DEFAULT true) RETURNS TABLE(outlier boolean, rowid int) AS $$ DECLARE @@ -58,10 +58,15 @@ BEGIN RAISE EXCEPTION 'Standard deviation of input data is zero'; END IF; - SELECT array_agg( - abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals - FROM unnest(attrs) As x(i); - + IF is_symmetric THEN + SELECT array_agg( + abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals + FROM unnest(attrs) As x(i); + ELSE + SELECT array_agg( + (i - avg_val) / stddev_val > num_deviations) INTO out_vals + FROM unnest(attrs) As x(i); + END IF; RETURN QUERY SELECT unnest(out_vals) As outlier, From bd05e7739d7bafc15ad7d3f0c9045953103f7210 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 29 Sep 2016 11:10:54 -0400 Subject: [PATCH 07/13] add test to produce error --- src/pg/test/sql/18_outliers.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/pg/test/sql/18_outliers.sql b/src/pg/test/sql/18_outliers.sql index 432d4c1..c84c614 100644 --- a/src/pg/test/sql/18_outliers.sql +++ b/src/pg/test/sql/18_outliers.sql @@ -40,6 +40,21 @@ SELECT * FROM b WHERE outlier IS TRUE; +-- With a Stddev of zero, should throw back error +-- With a threshold of 2.0 standard deviations, id 16 is the only outlier +WITH a AS ( + SELECT + ARRAY[5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5]::numeric[] As vals, + ARRAY[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]::int[] As ids +), b As ( + SELECT + (CDB_StdDevOutlier(vals, 1.0, ids)).* + FROM a + ORDER BY ids) +SELECT * +FROM b +WHERE outlier IS TRUE; + -- With a ratio threshold of 2.0 threshold (100% above or below the mean) -- which is greater than ~21, which are values WITH a AS ( From 99856ce95608101553ee11402ea6e81822c61e53 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 29 Sep 2016 11:37:22 -0400 Subject: [PATCH 08/13] flip inequality --- src/pg/sql/18_outliers.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index 454e383..b726bf6 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -30,7 +30,7 @@ BEGIN END IF; SELECT array_agg( - outlier_fraction > i / avg_val) INTO out_vals + outlier_fraction < i / avg_val) INTO out_vals FROM unnest(column_values) As x(i); RETURN QUERY From 23b2ad57c538b0eba0eaa8dfdf121993464d5c7c Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 29 Sep 2016 11:37:42 -0400 Subject: [PATCH 09/13] test updates --- src/pg/test/expected/18_outliers_test.out | 1 + src/pg/test/sql/18_outliers_test.sql | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pg/test/expected/18_outliers_test.out b/src/pg/test/expected/18_outliers_test.out index a329e07..417933b 100644 --- a/src/pg/test/expected/18_outliers_test.out +++ b/src/pg/test/expected/18_outliers_test.out @@ -9,6 +9,7 @@ outlier|rowid t|16 t|17 (2 rows) +ERROR: Standard deviation of input data is zero outlier|rowid t|8 t|11 diff --git a/src/pg/test/sql/18_outliers_test.sql b/src/pg/test/sql/18_outliers_test.sql index 6f18bf1..3b56125 100644 --- a/src/pg/test/sql/18_outliers_test.sql +++ b/src/pg/test/sql/18_outliers_test.sql @@ -48,7 +48,7 @@ WITH a AS ( ARRAY[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]::int[] As ids ), b As ( SELECT - (CDB_StdDevOutlier(vals, 1.0, ids)).* + (cdb_crankshaft.CDB_StdDevOutlier(vals, 1.0, ids)).* FROM a ORDER BY ids) SELECT * From 6846014a4fb1ccb1d738cbde48fbc17d33617394 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 29 Sep 2016 11:42:11 -0400 Subject: [PATCH 10/13] adding docs --- doc/18_outliers.md | 163 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 doc/18_outliers.md diff --git a/doc/18_outliers.md b/doc/18_outliers.md new file mode 100644 index 0000000..f1aa862 --- /dev/null +++ b/doc/18_outliers.md @@ -0,0 +1,163 @@ +## Outlier Detection + +This set of functions detects the presence of outliers. There are three functions for finding outliers from non-spatial data: + +1. Static Outliers +1. Percentage Outliers +1. Standard Deviation Outliers + +### CDB_StaticOutlier(column_value numeric, threshold numeric) + +#### Arguments + +| Name | Type | Description | +|------|------|-------------| +| column_value | numeric | The column of values on which to apply the threshold | +| threshold | numeric | The static threshold which is used to indicate whether a `column_value` is an outlier or not | + +### Returns + +Returns a boolean (true/false) depending on whether a value is above or below (or equal to) the threshold + +| Name | Type | Description | +|------|------|-------------| +| outlier | boolean | classification of whether a row is an outlier or not | + +#### Example Usage + +With a table `website_visits`: + +``` +| id | visits_10k | +|----|------------| +| 1 | 1 | +| 2 | 3 | +| 3 | 5 | +| 4 | 1 | +| 5 | 32 | +| 6 | 3 | +| 7 | 57 | +| 8 | 2 | +``` + +```sql +SELECT + id, + CDB_StaticOutlier(visits_10k, 11.0) As outlier, + visits_10k +FROM website_visits +``` + +``` +| id | outlier | visits_10k | +|----|---------|------------| +| 1 | f | 1 | +| 2 | f | 3 | +| 3 | f | 5 | +| 4 | f | 1 | +| 5 | t | 32 | +| 6 | f | 3 | +| 7 | t | 57 | +| 8 | f | 2 | +``` + +### CDB_PercentOutlier(column_values numeric[], ratio_threshold numeric, ids int[]) + +`CDB_PercentOutlier` calculates whether or not a value falls above a given threshold based on a percentage above the mean value of the input values. + +#### Arguments + +| Name | Type | Description | +|------|------|-------------| +| column_values | numeric[] | An array of the values to calculate the outlier classification on | +| outlier_fraction | numeric | The threshold above which a column value divided by the mean of all values is considered an outlier | +| ids | int[] | An array of the unique row ids of the input data (usually `cartodb_id`) | + +### Returns + +Returns a table of the outlier classification with the following columns + +| Name | Type | Description | +|------|------|-------------| +| outlier | boolean | classification of whether a row is an outlier or not | +| rowid | int | original row id (e.g., input `cartodb_id`) of the row which has the outlier classification | + +#### Example Usage + +This example find outliers which are more than 100% larger than the average (that is, more than 2.0 times larger). + +```sql +WITH cte As ( + SELECT + unnest(Array[1,2,3,4,5,6,7,8]) As id, + unnest(Array[1,3,5,1,32,3,57,2]) As visits_10k + ) +SELECT + (CDB_PercentOutlier(array_agg(visits_10k), 2.0, array_agg(id))).* +FROM cte; +``` + +Output +``` +| outlier | rowid | +|---------+-------| +| f | 1 | +| f | 2 | +| f | 3 | +| f | 4 | +| t | 5 | +| f | 6 | +| t | 7 | +| f | 8 | +``` + +### CDB_StdDevOutlier(column_values numeric[], ratio_threshold numeric, ids int[], is_symmetric boolean DEFAULT true) + +`CDB_StdDevOutlier` calculates whether or not a value falls above or below a given threshold based on the number of standard deviations from the mean. + +#### Arguments + +| Name | Type | Description | +|------|------|-------------| +| column_values | numeric[] | An array of the values to calculate the outlier classification on | +| num_deviations | numeric | The threshold in units of standard deviation | +| ids | int[] | An array of the unique row ids of the input data (usually `cartodb_id`) | +| is_symmetric (optional) | boolean | Consider outliers that are symmetric about the mean (default: true) | + +### Returns + +Returns a table of the outlier classification with the following columns + +| Name | Type | Description | +|------|------|-------------| +| outlier | boolean | classification of whether a row is an outlier or not | +| rowid | int | original row id (e.g., input `cartodb_id`) of the row which has the outlier classification | + +#### Example Usage + +This example find outliers which are more than 100% larger than the average (that is, more than 2.0 times larger). + +```sql +WITH cte As ( + SELECT + unnest(Array[1,2,3,4,5,6,7,8]) As id, + unnest(Array[1,3,5,1,32,3,57,2]) As visits_10k + ) +SELECT + (CDB_StdDevOutlier(array_agg(visits_10k), 2.0, array_agg(id))).* +FROM cte; +``` + +Output +``` +| outlier | rowid | +|---------+-------| +| f | 1 | +| f | 2 | +| f | 3 | +| f | 4 | +| f | 5 | +| f | 6 | +| t | 7 | +| f | 8 | +``` From c7f5c2451071e813ede30eff5f5f9f3f005cc7f0 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 6 Oct 2016 09:53:22 -0400 Subject: [PATCH 11/13] update signature names --- doc/18_outliers.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/18_outliers.md b/doc/18_outliers.md index f1aa862..f557529 100644 --- a/doc/18_outliers.md +++ b/doc/18_outliers.md @@ -25,7 +25,7 @@ Returns a boolean (true/false) depending on whether a value is above or below (o #### Example Usage -With a table `website_visits`: +With a table `website_visits` and a column of the number of website visits in units of 10,000 visits: ``` | id | visits_10k | @@ -61,7 +61,7 @@ FROM website_visits | 8 | f | 2 | ``` -### CDB_PercentOutlier(column_values numeric[], ratio_threshold numeric, ids int[]) +### CDB_PercentOutlier(column_values numeric[], outlier_fraction numeric, ids int[]) `CDB_PercentOutlier` calculates whether or not a value falls above a given threshold based on a percentage above the mean value of the input values. @@ -79,7 +79,7 @@ Returns a table of the outlier classification with the following columns | Name | Type | Description | |------|------|-------------| -| outlier | boolean | classification of whether a row is an outlier or not | +| is_outlier | boolean | classification of whether a row is an outlier or not | | rowid | int | original row id (e.g., input `cartodb_id`) of the row which has the outlier classification | #### Example Usage @@ -111,7 +111,7 @@ Output | f | 8 | ``` -### CDB_StdDevOutlier(column_values numeric[], ratio_threshold numeric, ids int[], is_symmetric boolean DEFAULT true) +### CDB_StdDevOutlier(column_values numeric[], num_deviations numeric, ids int[], is_symmetric boolean DEFAULT true) `CDB_StdDevOutlier` calculates whether or not a value falls above or below a given threshold based on the number of standard deviations from the mean. @@ -130,7 +130,7 @@ Returns a table of the outlier classification with the following columns | Name | Type | Description | |------|------|-------------| -| outlier | boolean | classification of whether a row is an outlier or not | +| is_outlier | boolean | classification of whether a row is an outlier or not | | rowid | int | original row id (e.g., input `cartodb_id`) of the row which has the outlier classification | #### Example Usage From da1449331cec83b8e3598c63630f63df2510e944 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 6 Oct 2016 09:53:38 -0400 Subject: [PATCH 12/13] update signature variable names --- src/pg/sql/18_outliers.sql | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pg/sql/18_outliers.sql b/src/pg/sql/18_outliers.sql index b726bf6..5b23c77 100644 --- a/src/pg/sql/18_outliers.sql +++ b/src/pg/sql/18_outliers.sql @@ -1,12 +1,12 @@ -- Find outliers using a static threshold -- -CREATE OR REPLACE FUNCTION CDB_StaticOutlier(attr numeric, threshold numeric) +CREATE OR REPLACE FUNCTION CDB_StaticOutlier(column_value numeric, threshold numeric) RETURNS boolean AS $$ BEGIN - RETURN attr > threshold; + RETURN column_value > threshold; END; $$ LANGUAGE plpgsql; @@ -15,7 +15,7 @@ $$ LANGUAGE plpgsql; -- TODO: add symmetric option? `is_symmetric boolean DEFAULT false` CREATE OR REPLACE FUNCTION CDB_PercentOutlier(column_values numeric[], outlier_fraction numeric, ids int[]) -RETURNS TABLE(outlier boolean, rowid int) +RETURNS TABLE(is_outlier boolean, rowid int) AS $$ DECLARE avg_val numeric; @@ -34,7 +34,7 @@ BEGIN FROM unnest(column_values) As x(i); RETURN QUERY - SELECT unnest(out_vals) As outlier, + SELECT unnest(out_vals) As is_outlier, unnest(ids) As rowid; END; @@ -42,8 +42,8 @@ $$ LANGUAGE plpgsql; -- Find outliers above a given number of standard deviations from the mean -CREATE OR REPLACE FUNCTION CDB_StdDevOutlier(attrs numeric[], num_deviations numeric, ids int[], is_symmetric boolean DEFAULT true) -RETURNS TABLE(outlier boolean, rowid int) +CREATE OR REPLACE FUNCTION CDB_StdDevOutlier(column_values numeric[], num_deviations numeric, ids int[], is_symmetric boolean DEFAULT true) +RETURNS TABLE(is_outlier boolean, rowid int) AS $$ DECLARE stddev_val numeric; @@ -52,7 +52,7 @@ DECLARE BEGIN SELECT stddev(i), avg(i) INTO stddev_val, avg_val - FROM unnest(attrs) As x(i); + FROM unnest(column_values) As x(i); IF stddev_val = 0 THEN RAISE EXCEPTION 'Standard deviation of input data is zero'; @@ -61,15 +61,15 @@ BEGIN IF is_symmetric THEN SELECT array_agg( abs(i - avg_val) / stddev_val > num_deviations) INTO out_vals - FROM unnest(attrs) As x(i); + FROM unnest(column_values) As x(i); ELSE SELECT array_agg( (i - avg_val) / stddev_val > num_deviations) INTO out_vals - FROM unnest(attrs) As x(i); + FROM unnest(column_values) As x(i); END IF; RETURN QUERY - SELECT unnest(out_vals) As outlier, + SELECT unnest(out_vals) As is_outlier, unnest(ids) As rowid; END; $$ LANGUAGE plpgsql; From c7e690980f6165c303de91edc7a463c1497971b6 Mon Sep 17 00:00:00 2001 From: Andy Eschbacher Date: Thu, 6 Oct 2016 10:29:52 -0400 Subject: [PATCH 13/13] update column names in tests --- src/pg/test/expected/18_outliers_test.out | 8 ++++---- src/pg/test/sql/18_outliers_test.sql | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pg/test/expected/18_outliers_test.out b/src/pg/test/expected/18_outliers_test.out index 417933b..0798e0c 100644 --- a/src/pg/test/expected/18_outliers_test.out +++ b/src/pg/test/expected/18_outliers_test.out @@ -1,21 +1,21 @@ SET client_min_messages TO WARNING; \set ECHO none -outlier|rowid +is_outlier|rowid t|11 t|16 t|17 (3 rows) -outlier|rowid +is_outlier|rowid t|16 t|17 (2 rows) ERROR: Standard deviation of input data is zero -outlier|rowid +is_outlier|rowid t|8 t|11 t|16 (3 rows) -outlier|rowid +is_outlier|rowid t|8 t|9 t|11 diff --git a/src/pg/test/sql/18_outliers_test.sql b/src/pg/test/sql/18_outliers_test.sql index 3b56125..9a6b87d 100644 --- a/src/pg/test/sql/18_outliers_test.sql +++ b/src/pg/test/sql/18_outliers_test.sql @@ -24,7 +24,7 @@ WITH a AS ( ORDER BY ids) SELECT * FROM b -WHERE outlier IS TRUE; +WHERE is_outlier IS TRUE; -- With a threshold of 2.0 standard deviations, id 16 is the only outlier WITH a AS ( @@ -38,7 +38,7 @@ WITH a AS ( ORDER BY ids) SELECT * FROM b -WHERE outlier IS TRUE; +WHERE is_outlier IS TRUE; -- With a Stddev of zero, should throw back error -- With a threshold of 2.0 standard deviations, id 16 is the only outlier @@ -53,7 +53,7 @@ WITH a AS ( ORDER BY ids) SELECT * FROM b -WHERE outlier IS TRUE; +WHERE is_outlier IS TRUE; -- With a ratio threshold of 2.0 threshold (100% above or below the mean) -- which is greater than ~21, which are values @@ -68,7 +68,7 @@ WITH a AS ( ORDER BY ids) SELECT * FROM b - WHERE outlier IS TRUE; + WHERE is_outlier IS TRUE; -- With a static threshold of 11, what are the outliers WITH a AS ( @@ -79,8 +79,7 @@ WITH a AS ( SELECT unnest(vals) As v, unnest(ids) as i FROM a ) -SELECT cdb_crankshaft.CDB_StaticOutlier(v, 11.0) As outlier, i As rowid +SELECT cdb_crankshaft.CDB_StaticOutlier(v, 11.0) As is_outlier, i As rowid FROM b WHERE cdb_crankshaft.CDB_StaticOutlier(v, 11.0) is True ORDER BY i; -