【问题标题】: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

有什么想法吗?

【问题讨论】:

标签: sql postgresql aggregate-functions


【解决方案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 个百分位数,基本上是中位数。

              希望这有帮助。

              【讨论】:

                猜你喜欢
                • 2013-12-03
                • 2017-11-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多