2010-05-29 81 views
1

说我有一个具有以下值的PostgreSQL表:如何获得忽略异常值的平均值?

id | value 
---------- 
1 | 4 
2 | 8 
3 | 100 
4 | 5 
5 | 7 

如果我使用PostgreSQL计算平均值,它给了我24.8的平均,因为100的高值,对计算影响很大。事实上,我想找到一个平均在6左右的平均值,并消除极端(s)。

我正在寻找一种方法来消除极端情况,并希望做到“统计正确”。极端的不能修复。我不能说;如果一个值超过X,它必须被消除。

我一直在对postgresql聚合函数弯曲头,但不能把我的手指放在适合我的东西上。有什么建议么?

+1

你正在寻找一个[截断平均(http://en.wikipedia.org/wiki/Truncated_mean) – 2011-07-06 11:48:13

+0

会位数有什么用处? – 2012-01-01 12:50:51

回答

4

我不能说;如果一个值超过X,它必须被消除。

嗯,你可以使用具有与子查询,以消除异常值,是这样的:

HAVING value < (
SELECT 2 * avg(value) 
FROM mytable 
GROUP BY ... 
) 

(或者,对于这个问题,使用更复杂的版本,以消除上述2点或3个标准差,如果什么你想要的东西在消除只有异常值方面会更好。)

另一种选择是查看生成中值,这是一种统计上合理的异常值计算方法;幸好有三个合理的例子:one from the Postgresql Wiki,一个built as an Oracle compatability layer,另一个来自PostgreSQL Journal。请注意围绕他们实施中位数的准确度/准确度的警告。

+1

杰出的答案,尤其是聚合中位数的wiki页面!然而,正如Peter Tillemans所说,我将把它与stddev结合起来。但由于你的答案包含最多的提示,我会评价它作为正确的答案。 – milovanderlinden 2010-05-30 13:05:09

9

Postgresql也可以计算标准偏差。

您可以仅取平均值()+/- 2 * stddev()中的数据点,它们大致对应于最接近平均值的90%数据点。

当然,2也可以是3(95%)或6(99.995%),但不要挂在数字上,因为存在集合异常值时,您不再处理正态分布。

要非常小心并验证它是否按预期工作。

+0

这听起来不错!我不知道stddev会导致该集合的百分比,尽管它听起来完全合法。我知道如果我把你的答案和Rodger的答案结合起来,我必须走在正确的轨道上! – milovanderlinden 2010-05-30 13:04:27

+0

看来你认为这是一个正态分布(这很难从问题中的例子中说出,事实上,从这样的5个数据点看来,它看起来不是这样)。如果是这样,你的百分比也不太对。 – Bruno 2014-07-04 18:41:38

2

这是一个聚合函数,它将计算一组值的修剪平均值,但不包括距平均值的N个标准偏差以外的值。

实施例:

DROP TABLE IF EXISTS foo; 
CREATE TEMPORARY TABLE foo (x FLOAT); 
INSERT INTO foo VALUES (1); 
INSERT INTO foo VALUES (2); 
INSERT INTO foo VALUES (3); 
INSERT INTO foo VALUES (4); 
INSERT INTO foo VALUES (100); 

SELECT avg(x), tmean(x, 2), tmean(x, 1.5) FROM foo; 

-- avg | tmean | tmean 
-- -----+-------+------- 
-- 22 | 22 | 2.5 

代码:

 
DROP TYPE IF EXISTS tmean_stype CASCADE; 

CREATE TYPE tmean_stype AS (
    deviations FLOAT, 
    count INT, 
    acc FLOAT, 
    acc2 FLOAT, 
    vals FLOAT[] 
); 

CREATE OR REPLACE FUNCTION tmean_sfunc(tmean_stype, float, float) 
RETURNS tmean_stype AS $$ 
    SELECT $3, $1.count + 1, $1.acc + $2, $1.acc2 + ($2 * $2), array_append($1.vals, $2); 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION tmean_finalfunc(tmean_stype) 
RETURNS float AS $$ 
DECLARE 
    fcount INT; 
    facc FLOAT; 
    mean FLOAT; 
    stddev FLOAT; 
    lbound FLOAT; 
    ubound FLOAT; 
    val FLOAT; 
BEGIN 
    mean := $1.acc/$1.count; 
    stddev := sqrt(($1.acc2/$1.count) - (mean * mean)); 
    lbound := mean - stddev * $1.deviations; 
    ubound := mean + stddev * $1.deviations; 
    -- RAISE NOTICE 'mean: % stddev: % lbound: % ubound: %', mean, stddev, lbound, ubound; 

    fcount := 0; 
    facc := 0; 
    FOR i IN array_lower($1.vals, 1) .. array_upper($1.vals, 1) LOOP 
     val := $1.vals[i]; 
     IF val >= lbound AND val <= ubound THEN 
      fcount := fcount + 1; 
      facc := facc + val; 
     END IF; 
    END LOOP; 

    IF fcount = 0 THEN 
     return NULL; 
    END IF; 
    RETURN facc/fcount; 
END; 
$$ LANGUAGE plpgsql; 

CREATE AGGREGATE tmean(float, float) 
(
    SFUNC = tmean_sfunc, 
    STYPE = tmean_stype, 
    FINALFUNC = tmean_finalfunc, 
    INITCOND = '(-1, 0, 0, 0, {})' 
); 

要点(这应该是相同的):使用ntile窗函数https://gist.github.com/4458294

0

精神。它使您可以轻松地从结果集中分离出极端值。

假设你想从结果集的两边减少10%。然后将值10传递给ntile并查找2到9之间的值将会给你想要的结果。请记住,如果您的记录少于10条,则可能意外地减少了20%以上,因此请务必检查记录总数。

WITH yyy AS (
    SELECT 
    id, 
    value, 
    NTILE(10) OVER (ORDER BY value) AS ntiled, 
    COUNT(*) OVER() AS counted 
    FROM 
    xxx) 
SELECT 
    * 
FROM 
    yyy 
WHERE 
    counted < 10 OR ntiled BETWEEN 2 AND 9;