有没有更好的方法来计算中位数(不是平均值)

Posted

技术标签:

【中文标题】有没有更好的方法来计算中位数(不是平均值)【英文标题】:Is there a better way to calculate the median (not average) 【发布时间】:2011-04-13 17:03:13 【问题描述】:

假设我有以下表定义:

CREATE TABLE x (i serial primary key, value integer not null);

我想计算 value 的 MEDIAN(不是 AVG)。中位数是将集合划分为两个包含相同数量元素的子集的值。如果元素个数是偶数,则中位数是最低段的最大值和最大段的最小值的平均值。 (有关详细信息,请参阅***。)

这是我设法计算 MEDIAN 的方法,但我想一定有更好的方法:

SELECT AVG(values_around_median) AS median
  FROM (
    SELECT
       DISTINCT(CASE WHEN FIRST_VALUE(above) OVER w2 THEN MIN(value) OVER w3 ELSE MAX(value) OVER w2 END)
        AS values_around_median
      FROM (
        SELECT LAST_VALUE(value) OVER w AS value,
               SUM(COUNT(*)) OVER w > (SELECT count(*)/2 FROM x) AS above
          FROM x
          GROUP BY value
          WINDOW w AS (ORDER BY value)
          ORDER BY value
        ) AS find_if_values_are_above_or_below_median
      WINDOW w2 AS (PARTITION BY above ORDER BY value DESC),
             w3 AS (PARTITION BY above ORDER BY value ASC)
    ) AS find_values_around_median

有什么想法吗?

【问题讨论】:

平均值和平均值是同义词。您要问的是中位数:en.wikipedia.org/wiki/Median 平均值 en.wikipedia.org/wiki/Arithmetic_mean 只是数字的总和除以计数。 确实如此。有了这些信息,也许谷歌会证明更有收益=) Simple way to calculate median with mysql的可能重复 @ChrisF - 不是骗子。不同的关系型数据库。 postgressql 可能比 mysql 有更好的方法,因为它支持分析函数和用户定义的聚合wiki.postgresql.org/wiki/Aggregate_Median 【参考方案1】:

是的,在 PostgreSQL 9.4 中,您可以使用新引入的逆分布函数 PERCENTILE_CONT(),它也是 SQL 标准中指定的有序集聚合函数。

WITH t(value) AS (
  SELECT 1   UNION ALL
  SELECT 2   UNION ALL
  SELECT 100 
)
SELECT
  percentile_cont(0.5) WITHIN GROUP (ORDER BY value)
FROM
  t;

This emulation of MEDIAN() via PERCENTILE_CONT() is also documented here.

【讨论】:

【参考方案2】:

确实有更简单的方法。在 Postgres 中,您可以定义自己的聚合函数。不久前,我在 PostgreSQL sn-ps 库中发布了用于计算中值以及模式和范围的函数。

http://wiki.postgresql.org/wiki/Aggregate_Median

【讨论】:

【参考方案3】:

一个更简单的查询:

WITH y AS (
   SELECT value, row_number() OVER (ORDER BY value) AS rn
   FROM   x
   WHERE  value IS NOT NULL
   )
, c AS (SELECT count(*) AS ct FROM y) 
SELECT CASE WHEN c.ct%2 = 0 THEN
          round((SELECT avg(value) FROM y WHERE y.rn IN (c.ct/2, c.ct/2+1)), 3)
       ELSE
                (SELECT     value  FROM y WHERE y.rn = (c.ct+1)/2)
       END AS median
FROM   c;

要点

忽略 NULL 值。 核心功能是 row_number() window function,自 8.4 版起就存在 最终的 SELECT 获得一行用于奇数,avg() 获得两行用于偶数。结果是数字,四舍五入到小数点后 3 位。

测试表明,新版本比问题中的查询快 4 倍(并且产生正确的结果,不同):

CREATE TEMP TABLE x (value int);
INSERT INTO x SELECT generate_series(1,10000);
INSERT INTO x VALUES (NULL),(NULL),(NULL),(3);

【讨论】:

【参考方案4】:

对于谷歌用户:还有http://pgxn.org/dist/quantile 安装此扩展后,可以在一行中计算中位数。

【讨论】:

【参考方案5】:

仅带有本机 postgres 函数的简单 sql:

select 
    case count(*)%2
        when 1 then (array_agg(num order by num))[count(*)/2+1]
        else ((array_agg(num order by num))[count(*)/2]::double precision + (array_agg(num order by num))[count(*)/2+1])/2
    end as median
from unnest(array[5,17,83,27,28]) num;

如果你想处理空值,当然可以添加 coalesce() 或其他东西。

【讨论】:

【参考方案6】:
CREATE TABLE array_table (id integer, values integer[]) ;

INSERT INTO array_table VALUES ( 1,'1,2,3');
INSERT INTO array_table VALUES ( 2,'4,5,6,7');

select id, values, cardinality(values) as array_length,
(case when cardinality(values)%2=0 and cardinality(values)>1 then (values[(cardinality(values)/2)]+ values[((cardinality(values)/2)+1)])/2::float 
 else values[(cardinality(values)+1)/2]::float end) as median  
 from array_table

或者您可以创建一个函数并在您进一步查询的任何地方使用它。

CREATE OR REPLACE FUNCTION median (a integer[]) 
RETURNS float AS    $median$ 
Declare     
    abc float; 
BEGIN    
    SELECT (case when cardinality(a)%2=0 and cardinality(a)>1 then 
           (a[(cardinality(a)/2)] + a[((cardinality(a)/2)+1)])/2::float   
           else a[(cardinality(a)+1)/2]::float end) into abc;    
    RETURN abc; 
END;    
$median$ 
LANGUAGE plpgsql;

select id,values,median(values) from array_table

【讨论】:

【参考方案7】:

使用以下函数查找第 n 个百分位数

CREATE or REPLACE FUNCTION nth_percentil(anyarray, int)
    RETURNS 
        anyelement as 
    $$
        SELECT $1[$2/100.0 * array_upper($1,1) + 1] ;
    $$ 
LANGUAGE SQL IMMUTABLE STRICT;

在您的情况下,它是第 50 个百分位数。

使用以下查询获取中位数

SELECT nth_percentil(ARRAY (SELECT Field_name FROM table_name ORDER BY 1),50)

这将为您提供第 50 个百分位数,基本上是中位数。

希望这有帮助。

【讨论】:

以上是关于有没有更好的方法来计算中位数(不是平均值)的主要内容,如果未能解决你的问题,请参考以下文章

Perl实例---寻找两个有序数组的中位数:进阶解法

用 Mysql 计算中位数

超级集计算第90个百分位数响应时间

R语言vtreat包自动处理dataframe的缺失值使用分组的中位数来标准化数据列中每个数据的值(和中位数表连接并基于中位数进行数据标化)计算数据列的中位数或者均值并进行数据标准化

中位数怎么求

请计算数据组"10,7,10,8,10,10,7,6"的均值,中位数,众数,标准差和极差