如何获得忽略异常值的平均值?

时间:2010-05-29 08:41:56

标签: postgresql statistics aggregate

说我有一个带有以下值的postgresql表:

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

如果我使用postgresql来计算平均值,它会给我平均24.8,因为100的高值对计算有很大影响。事实上,我希望在6左右找到一个平均值,并消除极端情况。

我正在寻找消除极端的方法,并希望这样做“统计上正确”。极端无法修复。我不能说;如果值超过X,则必须将其消除。

我一直在关注postgresql聚合函数,但无法指出我最适合使用的东西。有什么建议吗?

5 个答案:

答案 0 :(得分:10)

Postgresql也可以计算标准偏差。

您只能获取平均值()+/- 2 * stddev()中的数据点,这些数据点大致对应于最接近平均值的90%数据点。

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

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

答案 1 :(得分:6)

我不能说;如果值超过X,则必须将其删除。

好吧,你可以使用having和subselect来消除异常值,例如:

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。请注意它们如何准确/准确地实现中位数的注意事项。

答案 2 :(得分: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, {})'
);

要点(应该相同):https://gist.github.com/4458294

答案 3 :(得分:0)

注意使用ntile窗口功能。它允许您轻松地从结果集中隔离极值。

假设你想从结果集的两边削减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;

答案 4 :(得分:0)

您可以使用IQR to filter outliers。 PL / pgSQL代码:

select percentile_cont(0.25) WITHIN GROUP (ORDER BY value)
  into q1
  from table;
select percentile_cont(0.75) WITHIN GROUP (ORDER BY value)
  into q3
  from table;

iqr := q3 - q1;
min := q1 - 1.5 * iqr;
max := q3 + 1.5 * iqr;

select value
  into result
  from table
  where value >= min and value <= max;
return result;