Расчет процентильного ранга в MySQL - PullRequest
18 голосов
/ 29 июня 2009

У меня очень большая таблица данных измерений в MySQL, и мне нужно вычислить процентильный ранг для каждого из этих значений. Похоже, что в Oracle есть функция процент_rank, но я не могу найти ничего похожего для MySQL. Конечно, я мог бы просто перебрать его в Python, который я в любом случае использую для заполнения таблицы, но я подозреваю, что это будет довольно неэффективно, потому что один образец может иметь 200 000 наблюдений.

Ответы [ 9 ]

19 голосов
/ 25 октября 2011

Вот другой подход, который не требует объединения. В моем случае (таблица с 15 000+) строк, он запускается примерно за 3 секунды. (Метод JOIN занимает на порядок больше).

В этом примере предположим, что measure - это столбец, для которого вы рассчитываете процентный ранг, а id - просто идентификатор строки (не требуется):

SELECT
    id,
    @prev := @curr as prev,
    @curr := measure as curr,
    @rank := IF(@prev > @curr, @rank+@ties, @rank) AS rank,
    @ties := IF(@prev = @curr, @ties+1, 1) AS ties,
    (1-@rank/@total) as percentrank
FROM
    mytable,
    (SELECT
        @curr := null,
        @prev := null,
        @rank := 0,
        @ties := 1,
        @total := count(*) from mytable where measure is not null
    ) b
WHERE
    measure is not null
ORDER BY
    measure DESC

Кредит за этот метод достается Шломи Ноаху. Об этом он подробно пишет здесь:

http://code.openark.org/blog/mysql/sql-ranking-without-self-join

Я проверил это в MySQL, и оно прекрасно работает; понятия не имею об Oracle, SQLServer и т. д.

4 голосов
/ 20 апреля 2015
SELECT 
    c.id, c.score, ROUND(((@rank - rank) / @rank) * 100, 2) AS percentile_rank
FROM
    (SELECT 
    *,
        @prev:=@curr,
        @curr:=a.score,
        @rank:=IF(@prev = @curr, @rank, @rank + 1) AS rank
    FROM
        (SELECT id, score FROM mytable) AS a,
        (SELECT @curr:= null, @prev:= null, @rank:= 0) AS b
ORDER BY score DESC) AS c;
4 голосов
/ 29 июня 2009

нет простого способа сделать это. см http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html

2 голосов
/ 25 марта 2011

Если вы комбинируете свой SQL с процедурным языком, таким как PHP, вы можете сделать следующее. Этот пример разбивает лишние времена блокирования полета на аэропорт, на их процентили. Использует предложение LIMIT x, y в MySQL в сочетании с ORDER BY. Не очень красиво, но делает работу (извините, боролся с форматированием):

$startDt = "2011-01-01";
$endDt = "2011-02-28";
$arrPort= 'JFK';

$strSQL = "SELECT COUNT(*) as TotFlights FROM FIDS where depdt >= '$startDt' And depdt <= '$endDt' and ArrPort='$arrPort'";
if (!($queryResult = mysql_query($strSQL, $con)) ) {
    echo $strSQL . " FAILED\n"; echo mysql_error();
    exit(0);
}
$totFlights=0;
while($fltRow=mysql_fetch_array($queryResult)) {
    echo "Total Flights into " . $arrPort . " = " . $fltRow['TotFlights'];
    $totFlights = $fltRow['TotFlights'];

    /* 1906 flights. Percentile 90 = int(0.9 * 1906). */
    for ($x = 1; $x<=10; $x++) {
        $pctlPosn = $totFlights - intval( ($x/10) * $totFlights);
        echo "PCTL POSN for " . $x * 10 . " IS " . $pctlPosn . "\t";
        $pctlSQL = "SELECT  (ablk-sblk) as ExcessBlk from FIDS where ArrPort='" . $arrPort . "' order by ExcessBlk DESC limit " . $pctlPosn . ",1;";
        if (!($query2Result = mysql_query($pctlSQL, $con)) ) {
            echo $pctlSQL  . " FAILED\n";
            echo mysql_error();
            exit(0);
        }
        while ($pctlRow = mysql_fetch_array($query2Result)) {
            echo "Excess Block is :" . $pctlRow['ExcessBlk'] . "\n";
        }
    }
}
2 голосов
/ 31 августа 2009

Это довольно уродливый ответ, и я чувствую себя виноватым, говоря это. Тем не менее, это может помочь вам в вашей проблеме.

Одним из способов определения процентной доли будет подсчет всех строк и подсчет количества строк, превышающих указанное вами число. Вы можете рассчитать больше или меньше чем и принять обратное значение при необходимости.

Создайте индекс по вашему номеру. итого = выберите количество (); less_equal = выберите количество (), где значение> indexed_number;

Процент будет примерно таким: less_equal / total или (total - less_equal) / total

Убедитесь, что они оба используют индекс, который вы создали. Если они не, настройте их, пока они не будут. Запрос объяснения должен иметь «использующий индекс» в правом столбце. В случае счетчика выбора (*) следует использовать индекс для InnoDB и что-то вроде const для MyISAM. MyISAM будет знать это значение в любое время без необходимости его расчета.

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

Помогает ли это?

Jacob

1 голос
/ 28 января 2019

В MySQL 8 наконец-то появились оконные функции, и среди них - функция PERCENT_RANK(), которую вы искали. Итак, просто напишите:

SELECT col, percent_rank() OVER (ORDER BY col)
FROM t
ORDER BY col

Ваш вопрос упоминает "процентили", которые немного отличаются. Для полноты картины в стандарте SQL и некоторых RBDMS (Oracle, PostgreSQL, SQL Server, Teradata), но не в MySQL, есть функции обратного распределения PERCENTILE_DISC и PERCENTILE_CONT. С MySQL 8 и оконными функциями вы можете эмулировать PERCENTILE_DISC, однако, снова используя оконные функции PERCENT_RANK и FIRST_VALUE .

0 голосов
/ 15 ноября 2018

Предположим, у нас есть таблица продаж, например:

user_id, шт

тогда следующий запрос даст процентиль каждого пользователя:

select a.user_id,a.units,
(sum(case when a.units >= b.units then 1 else 0 end )*100)/count(1) percentile
from sales a join sales b ;

Обратите внимание, что это пойдет для перекрестного соединения, что приведет к сложности O (n2), поэтому может рассматриваться как неоптимизированное решение, но кажется простым, учитывая, что в версии mysql у нас нет никакой функции.

0 голосов
/ 09 июля 2018

Не уверен, что операция подразумевает под «процентилем ранга», но чтобы получить данный процентиль для набора значений, см. http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html Расчет sql можно легко изменить, чтобы получить другой или несколько процентилей.

Одно примечание: мне пришлось немного изменить расчет, например, 90-й процентиль - «90/100 * COUNT (*) + 0,5» вместо «90/100 * COUNT (*) + 1». Иногда он пропускал два значения после точки процентиля в упорядоченном списке вместо выбора следующего более высокого значения для процентиля. Может быть, способ целочисленного округления работает в MySQL.

есть:

.... SUBSTRING_INDEX (SUBSTRING_INDEX (GROUP_CONCAT (fieldValue ORDER BY fieldValue SEPARATOR ','), ',', 90/100 * COUNT (*) + 0.5 ), ',', - - 1) как 90-й процентиль ....

0 голосов
/ 21 августа 2009

Чтобы получить ранг, я бы сказал, что вам нужно (слева) внешне присоединиться к столу на себе что-то вроде:

select t1.name, t1.value, count(distinct isnull(t2.value,0))  
from table t1  
left join table t2  
on t1.value>t2.value  
group by t1.name, t1.value 

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

Обратите внимание, что я более знаком с sqlserver, поэтому синтаксис может быть неправильным. Также отличное может не иметь правильного поведения для того, чего вы хотите достичь. Но это общая идея.
Затем, чтобы получить реальный процентильный ранг, вам нужно сначала получить количество значений в переменной (или различных значений в зависимости от соглашения, которое вы хотите принять) и вычислить процентильный ранг, используя реальный ранг, приведенный выше.

...