Простой способ вычислить медиану с помощью MySQL

StackOverflow https://stackoverflow.com/questions/1291152

  •  18-09-2019
  •  | 
  •  

Вопрос

Какой самый простой (и, надеюсь, не слишком медленный) способ вычислить медиану с помощью MySQL?Я использовал AVG(x) для нахождения среднего значения, но мне трудно найти простой способ вычисления медианы.На данный момент я возвращаю все строки в PHP, выполняю сортировку, а затем выбираю среднюю строку, но наверняка должен быть какой-то простой способ сделать это в одном запросе MySQL.

Примерные данные:

id | val
--------
 1    4
 2    7
 3    2
 4    2
 5    9
 6    8
 7    3

Сортировка по val дает 2 2 3 4 7 8 9, таким образом , медиана должна быть 4, по сравнению SELECT AVG(val) который == 5.

Это было полезно?

Решение

В MariaDB / MySQL:

SELECT AVG(dd.val) as median_val
FROM (
SELECT d.val, @rownum:=@rownum+1 as `row_number`, @total_rows:=@rownum
  FROM data d, (SELECT @rownum:=0) r
  WHERE d.val is NOT NULL
  -- put some where clause here
  ORDER BY d.val
) as dd
WHERE dd.row_number IN ( FLOOR((@total_rows+1)/2), FLOOR((@total_rows+2)/2) );

Стив Коэн указывает, что после первого прохода @rownum будет содержать общее количество строк.Это может быть использовано для определения медианы, поэтому второго прохода или соединения не требуется.

Также AVG(dd.val) и dd.row_number IN(...) используется для корректного получения медианы при четном количестве записей.Рассуждения:

SELECT FLOOR((3+1)/2),FLOOR((3+2)/2); -- when total_rows is 3, avg rows 2 and 2
SELECT FLOOR((4+1)/2),FLOOR((4+2)/2); -- when total_rows is 4, avg rows 2 and 3

Наконец - то, MariaDB 10.3.3+ содержит МЕДИАННУЮ функцию

Другие советы

Я просто нашел другой ответ в Интернете в комментариях:

Для медиан практически в любом SQL:

SELECT x.val from data x, data y
GROUP BY x.val
HAVING SUM(SIGN(1-SIGN(y.val-x.val))) = (COUNT(*)+1)/2

Убедитесь, что ваши столбцы хорошо проиндексированы и индекс используется для фильтрации и сортировки.Сверьтесь с планами объяснения.

select count(*) from table --find the number of rows

Вычислите "средний" номер строки.Может быть, использовать: median_row = floor(count / 2).

Затем выберите его из списка:

select val from table order by val asc limit median_row,1

Это должно вернуть вам одну строку только с тем значением, которое вы хотите.

Джейкоб

Я обнаружил, что принятое решение не сработало при моей установке MySQL, вернув пустой набор, но этот запрос работал у меня во всех ситуациях, в которых я его тестировал:

SELECT x.val from data x, data y
GROUP BY x.val
HAVING SUM(SIGN(1-SIGN(y.val-x.val)))/COUNT(*) > .5
LIMIT 1

К сожалению, ни ответы Якобтейлора, ни velcro не возвращают точных результатов для текущих версий MySQL.

Ответ Velcro выше близок, но он неправильно вычисляется для результирующих наборов с четным количеством строк.Медианы определяются либо как 1) среднее число в наборах с нечетными номерами, либо как 2) среднее из двух средних чисел в наборах с четными номерами.

Итак, вот решение velcro, исправленное для обработки как четных, так и нечетных наборов чисел:

SELECT AVG(middle_values) AS 'median' FROM (
  SELECT t1.median_column AS 'middle_values' FROM
    (
      SELECT @row:=@row+1 as `row`, x.median_column
      FROM median_table AS x, (SELECT @row:=0) AS r
      WHERE 1
      -- put some where clause here
      ORDER BY x.median_column
    ) AS t1,
    (
      SELECT COUNT(*) as 'count'
      FROM median_table x
      WHERE 1
      -- put same where clause here
    ) AS t2
    -- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
    WHERE t1.row >= t2.count/2 and t1.row <= ((t2.count/2) +1)) AS t3;

Чтобы использовать это, выполните следующие 3 простых шага:

  1. Замените "median_table" (2 вхождения) в приведенном выше коде на имя вашей таблицы
  2. Замените "median_column" (3 вхождения) именем столбца, для которого вы хотели бы найти медиану
  3. Если у вас есть условие WHERE, замените "WHERE 1" (2 вхождения) на ваше условие where

Я предлагаю более быстрый способ.

Получить количество строк:

SELECT CEIL(COUNT(*)/2) FROM data;

Затем возьмите среднее значение в отсортированном подзапросе:

SELECT max(val) FROM (SELECT val FROM data ORDER BY val limit @middlevalue) x;

Я протестировал это с набором данных случайных чисел 5x10e6, и он найдет медиану менее чем за 10 секунд.

Комментарий к эта страница в документации MySQL имеет следующее предложение:

-- (mostly) High Performance scaling MEDIAN function per group
-- Median defined in http://en.wikipedia.org/wiki/Median
--
-- by Peter Hlavac
-- 06.11.2008
--
-- Example Table:

DROP table if exists table_median;
CREATE TABLE table_median (id INTEGER(11),val INTEGER(11));
COMMIT;


INSERT INTO table_median (id, val) VALUES
(1, 7), (1, 4), (1, 5), (1, 1), (1, 8), (1, 3), (1, 6),
(2, 4),
(3, 5), (3, 2),
(4, 5), (4, 12), (4, 1), (4, 7);



-- Calculating the MEDIAN
SELECT @a := 0;
SELECT
id,
AVG(val) AS MEDIAN
FROM (
SELECT
id,
val
FROM (
SELECT
-- Create an index n for every id
@a := (@a + 1) mod o.c AS shifted_n,
IF(@a mod o.c=0, o.c, @a) AS n,
o.id,
o.val,
-- the number of elements for every id
o.c
FROM (
SELECT
t_o.id,
val,
c
FROM
table_median t_o INNER JOIN
(SELECT
id,
COUNT(1) AS c
FROM
table_median
GROUP BY
id
) t2
ON (t2.id = t_o.id)
ORDER BY
t_o.id,val
) o
) a
WHERE
IF(
-- if there is an even number of elements
-- take the lower and the upper median
-- and use AVG(lower,upper)
c MOD 2 = 0,
n = c DIV 2 OR n = (c DIV 2)+1,

-- if its an odd number of elements
-- take the first if its only one element
-- or take the one in the middle
IF(
c = 1,
n = 1,
n = c DIV 2 + 1
)
)
) a
GROUP BY
id;

-- Explanation:
-- The Statement creates a helper table like
--
-- n id val count
-- ----------------
-- 1, 1, 1, 7
-- 2, 1, 3, 7
-- 3, 1, 4, 7
-- 4, 1, 5, 7
-- 5, 1, 6, 7
-- 6, 1, 7, 7
-- 7, 1, 8, 7
--
-- 1, 2, 4, 1

-- 1, 3, 2, 2
-- 2, 3, 5, 2
--
-- 1, 4, 1, 4
-- 2, 4, 5, 4
-- 3, 4, 7, 4
-- 4, 4, 12, 4


-- from there we can select the n-th element on the position: count div 2 + 1 

Большинство приведенных выше решений работают только для одного поля таблицы, возможно, вам потребуется получить медиану (50-й процентиль) для многих полей запроса.

Я использую это:

SELECT CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(
 GROUP_CONCAT(field_name ORDER BY field_name SEPARATOR ','),
  ',', 50/100 * COUNT(*) + 1), ',', -1) AS DECIMAL) AS `Median`
FROM table_name;

Вы можете заменить "50" в примере выше на любой процентиль, это очень эффективно.

Просто убедитесь, что у вас достаточно памяти для GROUP_CONCAT, вы можете изменить ее с помощью:

SET group_concat_max_len = 10485760; #10MB max length

Более подробная информация: http://web.performancerasta.com/metrics-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query/

Основываясь на ответе velcro, для тех из вас, кому приходится делать медиану от чего-то, что сгруппировано по другому параметру:

SELECT grp_field, t1.val FROM (
   SELECT grp_field, @rownum:=IF(@s = grp_field, @rownum + 1, 0) AS row_number,
   @s:=IF(@s = grp_field, @s, grp_field) AS sec, d.val
  FROM data d,  (SELECT @rownum:=0, @s:=0) r
  ORDER BY grp_field, d.val
) as t1 JOIN (
  SELECT grp_field, count(*) as total_rows
  FROM data d
  GROUP BY grp_field
) as t2
ON t1.grp_field = t2.grp_field
WHERE t1.row_number=floor(total_rows/2)+1;

У меня есть приведенный ниже код, который я нашел на HackerRank, и он довольно прост и работает в каждом конкретном случае.

SELECT M.MEDIAN_COL FROM MEDIAN_TABLE M WHERE  
  (SELECT COUNT(MEDIAN_COL) FROM MEDIAN_TABLE WHERE MEDIAN_COL < M.MEDIAN_COL ) = 
  (SELECT COUNT(MEDIAN_COL) FROM MEDIAN_TABLE WHERE MEDIAN_COL > M.MEDIAN_COL );

Вы могли бы использовать определенную пользователем функцию, которая найдена здесь.

Заботится о количестве нечетных значений - в этом случае дает среднее значение двух значений посередине.

SELECT AVG(val) FROM
  ( SELECT x.id, x.val from data x, data y
      GROUP BY x.id, x.val
      HAVING SUM(SIGN(1-SIGN(IF(y.val-x.val=0 AND x.id != y.id, SIGN(x.id-y.id), y.val-x.val)))) IN (ROUND((COUNT(*))/2), ROUND((COUNT(*)+1)/2))
  ) sq

Мой код, эффективный без таблиц или дополнительных переменных:

SELECT
((SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(val order by val), ',', floor(1+((count(val)-1) / 2))), ',', -1))
+
(SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(val order by val), ',', ceiling(1+((count(val)-1) / 2))), ',', -1)))/2
as median
FROM table;

При желании вы также можете сделать это в хранимой процедуре:

DROP PROCEDURE IF EXISTS median;
DELIMITER //
CREATE PROCEDURE median (table_name VARCHAR(255), column_name VARCHAR(255), where_clause VARCHAR(255))
BEGIN
  -- Set default parameters
  IF where_clause IS NULL OR where_clause = '' THEN
    SET where_clause = 1;
  END IF;

  -- Prepare statement
  SET @sql = CONCAT(
    "SELECT AVG(middle_values) AS 'median' FROM (
      SELECT t1.", column_name, " AS 'middle_values' FROM
        (
          SELECT @row:=@row+1 as `row`, x.", column_name, "
          FROM ", table_name," AS x, (SELECT @row:=0) AS r
          WHERE ", where_clause, " ORDER BY x.", column_name, "
        ) AS t1,
        (
          SELECT COUNT(*) as 'count'
          FROM ", table_name, " x
          WHERE ", where_clause, "
        ) AS t2
        -- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
        WHERE t1.row >= t2.count/2
          AND t1.row <= ((t2.count/2)+1)) AS t3
    ");

  -- Execute statement
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
END//
DELIMITER ;


-- Sample usage:
-- median(table_name, column_name, where_condition);
CALL median('products', 'price', NULL);

Мое решение, представленное ниже, работает всего в одном запросе без создания таблицы, переменной или даже подзапроса.Кроме того, это позволяет вам получать медиану для каждой группы в запросах по группам (это то, что мне было нужно!):

SELECT `columnA`, 
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(`columnB` ORDER BY `columnB`), ',', CEILING((COUNT(`columnB`)/2))), ',', -1) medianOfColumnB
FROM `tableC`
-- some where clause if you want
GROUP BY `columnA`;

Это работает благодаря разумному использованию group_concat и substring_index .

Но, чтобы разрешить большой group_concat, вы должны установить group_concat_max_len на более высокое значение (1024 символа по умолчанию).Вы можете установить это следующим образом (для текущего сеанса sql) :

SET SESSION group_concat_max_len = 10000; 
-- up to 4294967295 in 32-bits platform.

Дополнительная информация для group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_group_concat_max_len

Еще один рифф к ответу Велкроу, но использует единственную промежуточную таблицу и использует переменную, используемую для нумерации строк, чтобы получить количество, вместо того, чтобы выполнять дополнительный запрос для его вычисления.Также начинается подсчет таким образом, чтобы первая строка была строкой 0, что позволяет просто использовать Floor и Ceil для выбора средней строки (ов).

SELECT Avg(tmp.val) as median_val
    FROM (SELECT inTab.val, @rows := @rows + 1 as rowNum
              FROM data as inTab,  (SELECT @rows := -1) as init
              -- Replace with better where clause or delete
              WHERE 2 > 1
              ORDER BY inTab.val) as tmp
    WHERE tmp.rowNum in (Floor(@rows / 2), Ceil(@rows / 2));

Установите и используйте эти статистические функции mysql: http://www.xarg.org/2012/07/statistical-functions-in-mysql/

После этого вычислить медиану несложно:

ВЫБЕРИТЕ медиану ( x ) ИЗ t1

SELECT 
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(
            GROUP_CONCAT(field ORDER BY field),
            ',',
            ((
                ROUND(
                    LENGTH(GROUP_CONCAT(field)) - 
                    LENGTH(
                        REPLACE(
                            GROUP_CONCAT(field),
                            ',',
                            ''
                        )
                    )
                ) / 2) + 1
            )),
            ',',
            -1
        )
FROM
    table

Вышесказанное, кажется, работает для меня.

Я использовал подход с двумя запросами:

  • первый, кто получит count, min, max и avg
  • второй (подготовленный оператор) с предложениями "LIMIT @count / 2, 1" и "ORDER BY .." для получения среднего значения

Они заключены в функцию defn, поэтому все значения могут быть возвращены из одного вызова.

Если ваши диапазоны статичны и ваши данные меняются не часто, возможно, было бы эффективнее предварительно вычислить / сохранить эти значения и использовать сохраненные значения вместо того, чтобы каждый раз запрашивать с нуля.

поскольку мне просто нужно было медианное И процентильное решение, я создал простую и довольно гибкую функцию, основанную на результатах, приведенных в этой теме.Я знаю, что сам буду счастлив, если найду "готовые" функции, которые легко включить в мои проекты, поэтому я решил быстро поделиться:

function mysql_percentile($table, $column, $where, $percentile = 0.5) {

    $sql = "
            SELECT `t1`.`".$column."` as `percentile` FROM (
            SELECT @rownum:=@rownum+1 as `row_number`, `d`.`".$column."`
              FROM `".$table."` `d`,  (SELECT @rownum:=0) `r`
              ".$where."
              ORDER BY `d`.`".$column."`
            ) as `t1`, 
            (
              SELECT count(*) as `total_rows`
              FROM `".$table."` `d`
              ".$where."
            ) as `t2`
            WHERE 1
            AND `t1`.`row_number`=floor(`total_rows` * ".$percentile.")+1;
        ";

    $result = sql($sql, 1);

    if (!empty($result)) {
        return $result['percentile'];       
    } else {
        return 0;
    }

}

Использование очень простое, пример из моего текущего проекта:

...
$table = DBPRE."zip_".$slug;
$column = 'seconds';
$where = "WHERE `reached` = '1' AND `time` >= '".$start_time."'";

    $reaching['median'] = mysql_percentile($table, $column, $where, 0.5);
    $reaching['percentile25'] = mysql_percentile($table, $column, $where, 0.25);
    $reaching['percentile75'] = mysql_percentile($table, $column, $where, 0.75);
...

Вот мой путь .Конечно, вы могли бы включить это в процедуру :-)

SET @median_counter = (SELECT FLOOR(COUNT(*)/2) - 1 AS `median_counter` FROM `data`);

SET @median = CONCAT('SELECT `val` FROM `data` ORDER BY `val` LIMIT ', @median_counter, ', 1');

PREPARE median FROM @median;

EXECUTE median;

Вы могли бы избежать переменной @median_counter, если вы замените его:

SET @median = CONCAT( 'SELECT `val` FROM `data` ORDER BY `val` LIMIT ',
                      (SELECT FLOOR(COUNT(*)/2) - 1 AS `median_counter` FROM `data`),
                      ', 1'
                    );

PREPARE median FROM @median;

EXECUTE median;

Этот способ, по-видимому, включает как четное, так и нечетное количество без подзапроса.

SELECT AVG(t1.x)
FROM table t1, table t2
GROUP BY t1.x
HAVING SUM(SIGN(t1.x - t2.x)) = 0

Основываясь на ответе @bob's, это обобщает запрос, чтобы иметь возможность возвращать несколько медиан, сгруппированных по некоторым критериям.

Подумайте, например, о средней цене продажи подержанных автомобилей на автомобильной стоянке, сгруппированной по годам и месяцам.

SELECT 
    period, 
    AVG(middle_values) AS 'median' 
FROM (
    SELECT t1.sale_price AS 'middle_values', t1.row_num, t1.period, t2.count
    FROM (
        SELECT 
            @last_period:=@period AS 'last_period',
            @period:=DATE_FORMAT(sale_date, '%Y-%m') AS 'period',
            IF (@period<>@last_period, @row:=1, @row:=@row+1) as `row_num`, 
            x.sale_price
          FROM listings AS x, (SELECT @row:=0) AS r
          WHERE 1
            -- where criteria goes here
          ORDER BY DATE_FORMAT(sale_date, '%Y%m'), x.sale_price
        ) AS t1
    LEFT JOIN (  
          SELECT COUNT(*) as 'count', DATE_FORMAT(sale_date, '%Y-%m') AS 'period'
          FROM listings x
          WHERE 1
            -- same where criteria goes here
          GROUP BY DATE_FORMAT(sale_date, '%Y%m')
        ) AS t2
        ON t1.period = t2.period
    ) AS t3
WHERE 
    row_num >= (count/2) 
    AND row_num <= ((count/2) + 1)
GROUP BY t3.period
ORDER BY t3.period;

Часто нам может потребоваться вычислить медиану не только для всей таблицы, но и для агрегированных значений по отношению к нашему идентификатору.Другими словами, вычислите медиану для каждого идентификатора в нашей таблице, где каждый идентификатор содержит много записей.(хорошая производительность и работает во многих SQL + исправляет проблему четности и коэффициентов, подробнее о производительности различных медианных методов https://sqlperformance.com/2012/08/t-sql-queries/median )

SELECT our_id, AVG(1.0 * our_val) as Median
FROM
( SELECT our_id, our_val, 
  COUNT(*) OVER (PARTITION BY our_id) AS cnt,
  ROW_NUMBER() OVER (PARTITION BY our_id ORDER BY our_val) AS rn
  FROM our_table
) AS x
WHERE rn IN ((cnt + 1)/2, (cnt + 2)/2) GROUP BY our_id;

Надеюсь, это поможет

Если MySQL имеет ROW_NUMBER, то МЕДИАНА равна (вдохновляйтесь этим запросом SQL Server):

WITH Numbered AS 
(
SELECT *, COUNT(*) OVER () AS Cnt,
    ROW_NUMBER() OVER (ORDER BY val) AS RowNum
FROM yourtable
)
SELECT id, val
FROM Numbered
WHERE RowNum IN ((Cnt+1)/2, (Cnt+2)/2)
;

Значение IN используется в том случае, если у вас четное количество записей.

Если вы хотите найти медиану для каждой группы, то просто РАЗДЕЛИТЕ ПО группам в своих предложениях OVER.

Роб

После прочтения всех предыдущих они не соответствовали моему фактическому требованию, поэтому я внедрил свое собственное, которое не требует какой-либо процедуры или усложняющих инструкций, просто я GROUP_CONCAT все значения из столбца, которые я хотел получить, являются МЕДИАННЫМИ, и, применяя значение COUNT DIV НА 2, я извлекаю значение из середины списка, как это делает следующий запрос :

(POS - это название столбца, я хочу получить его медиану)

(query) SELECT
SUBSTRING_INDEX ( 
   SUBSTRING_INDEX ( 
       GROUP_CONCAT(pos ORDER BY CAST(pos AS SIGNED INTEGER) desc SEPARATOR ';') 
    , ';', COUNT(*)/2 ) 
, ';', -1 ) AS `pos_med`
FROM table_name
GROUP BY any_criterial

Я надеюсь, что это могло бы быть кому-то полезно так же, как многие другие комментарии были для меня с этого сайта.

Зная точное количество строк, вы можете использовать этот запрос:

SELECT <value> AS VAL FROM <table> ORDER BY VAL LIMIT 1 OFFSET <half>

Где <half> = ceiling(<size> / 2.0) - 1

У меня есть база данных, содержащая около 1 миллиарда строк, которые нам требуются для определения среднего возраста в наборе.Отсортировать миллиард строк сложно, но если вы объедините различные значения, которые можно найти (возраст варьируется от 0 до 100), вы можете отсортировать ЭТОТ список и использовать некоторую арифметическую магию, чтобы найти любой нужный вам процентиль следующим образом:

with rawData(count_value) as
(
    select p.YEAR_OF_BIRTH
        from dbo.PERSON p
),
overallStats (avg_value, stdev_value, min_value, max_value, total) as
(
  select avg(1.0 * count_value) as avg_value,
    stdev(count_value) as stdev_value,
    min(count_value) as min_value,
    max(count_value) as max_value,
    count(*) as total
  from rawData
),
aggData (count_value, total, accumulated) as
(
  select count_value, 
    count(*) as total, 
        SUM(count(*)) OVER (ORDER BY count_value ROWS UNBOUNDED PRECEDING) as accumulated
  FROM rawData
  group by count_value
)
select o.total as count_value,
  o.min_value,
    o.max_value,
    o.avg_value,
    o.stdev_value,
    MIN(case when d.accumulated >= .50 * o.total then count_value else o.max_value end) as median_value,
    MIN(case when d.accumulated >= .10 * o.total then count_value else o.max_value end) as p10_value,
    MIN(case when d.accumulated >= .25 * o.total then count_value else o.max_value end) as p25_value,
    MIN(case when d.accumulated >= .75 * o.total then count_value else o.max_value end) as p75_value,
    MIN(case when d.accumulated >= .90 * o.total then count_value else o.max_value end) as p90_value
from aggData d
cross apply overallStats o
GROUP BY o.total, o.min_value, o.max_value, o.avg_value, o.stdev_value
;

Этот запрос зависит от вашей базы данных, поддерживающей оконные функции (включая строки, НЕОГРАНИЧЕННЫЕ ПРЕДЫДУЩИМИ), но если у вас их нет, то несложно объединить aggData CTE с самим собой и объединить все предыдущие итоги в столбец "накопленный", который используется для определения, какое значение содержит указанный прецентиль.Приведенная выше выборка вычисляет p10, p25, p50 (медиана), p75 и p90.

-Крис

Взято из:http://mdb-blog.blogspot.com/2015/06/mysql-find-median-nth-element-without.html

Я бы предложил другой способ, без объединения, но работая с струны

я не проверял это с таблицами с большими данными, но с маленькими / средними таблицами это работает просто отлично.

Хорошая вещь здесь в том, что это также работает путем ГРУППИРОВКИ таким образом, он может возвращать медиану для нескольких элементов.

вот тестовый код для тестовой таблицы:

DROP TABLE test.test_median
CREATE TABLE test.test_median AS
SELECT 'book' AS grp, 4 AS val UNION ALL
SELECT 'book', 7 UNION ALL
SELECT 'book', 2 UNION ALL
SELECT 'book', 2 UNION ALL
SELECT 'book', 9 UNION ALL
SELECT 'book', 8 UNION ALL
SELECT 'book', 3 UNION ALL

SELECT 'note', 11 UNION ALL

SELECT 'bike', 22 UNION ALL
SELECT 'bike', 26 

и код для нахождения медианы для каждой группы:

SELECT grp,
         SUBSTRING_INDEX( SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val), ',', COUNT(*)/2 ), ',', -1) as the_median,
         GROUP_CONCAT(val ORDER BY val) as all_vals_for_debug
FROM test.test_median
GROUP BY grp

Выходной сигнал:

grp | the_median| all_vals_for_debug
bike| 22        | 22,26
book| 4         | 2,2,3,4,7,8,9
note| 11        | 11

В некоторых случаях медиана вычисляется следующим образом :

"Медиана" - это "среднее" значение в списке чисел, когда они упорядочены по значению.Для наборов четного количества, медиана - это среднее из двух средних значений.Я создал для этого простой код :

$midValue = 0;
$rowCount = "SELECT count(*) as count {$from} {$where}";

$even = FALSE;
$offset = 1;
$medianRow = floor($rowCount / 2);
if ($rowCount % 2 == 0 && !empty($medianRow)) {
  $even = TRUE;
  $offset++;
  $medianRow--;
}

$medianValue = "SELECT column as median 
               {$fromClause} {$whereClause} 
               ORDER BY median 
               LIMIT {$medianRow},{$offset}";

$medianValDAO = db_query($medianValue);
while ($medianValDAO->fetch()) {
  if ($even) {
    $midValue = $midValue + $medianValDAO->median;
  }
  else {
    $median = $medianValDAO->median;
  }
}
if ($even) {
  $median = $midValue / 2;
}
return $median;

Возвращенный $ median был бы требуемым результатом :-)

Медианы , сгруппированные по размеру:

SELECT your_dimension, avg(t1.val) as median_val FROM (
SELECT @rownum:=@rownum+1 AS `row_number`,
   IF(@dim <> d.your_dimension, @rownum := 0, NULL),
   @dim := d.your_dimension AS your_dimension,
   d.val
   FROM data d,  (SELECT @rownum:=0) r, (SELECT @dim := 'something_unreal') d
  WHERE 1
  -- put some where clause here
  ORDER BY d.your_dimension, d.val
) as t1
INNER JOIN  
(
  SELECT d.your_dimension,
    count(*) as total_rows
  FROM data d
  WHERE 1
  -- put same where clause here
  GROUP BY d.your_dimension
) as t2 USING(your_dimension)
WHERE 1
AND t1.row_number in ( floor((total_rows+1)/2), floor((total_rows+2)/2) )

GROUP BY your_dimension;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top