2010-05-29 145 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;