Расчет процентиля в MySQL

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

7 ответов


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

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

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

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

убедитесь, что оба они используют созданный вами индекс. Если это не так, подправьте их, пока они не будут. Запрос explain должен иметь "using index" в правом столбце. В случае select count (*) он должен использовать index для InnoDB и что-то вроде const для MyISAM. MyISAM будет знать это значение в любое время без необходимости расчета он.

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

Это поможет?

Яков


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

в примере, Предположим, что мера - это столбец, на котором вы вычисляете процентный ранг, и 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 и т. д.


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


Если вы объединяете свой 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";
        }
    }
}

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;

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

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, поэтому синтаксис может быть неправильным. Кроме того, отдельный может не иметь правильного поведения для того, чего вы хотите достичь. Но это общая идея.
Затем, чтобы получить реальный рейтинг процентиля, вам нужно будет сначала получите количество значений в переменной (или различные значения в зависимости от соглашения, которое вы хотите принять) и вычислите ранг процентиля, используя реальный ранг, указанный выше.


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

одно примечание: мне пришлось немного изменить расчет, например 90 - й процентиль - "90/100 * COUNT(*) + 0.5" вместо "90/100 * COUNT(*) + 1". Иногда он пропускал два значения мимо точки процентиля в упорядоченном списке, вместо того, чтобы выбрать следующее более высокое значение для процентиля. Возможно, как целочисленное округление работает в mysql.

ie:

.... SUBSTRING_INDEX(SUBSTRING_INDEX (GROUP_CONCAT (FIELDVALUE ORDER BY FIELDVALUE SEPARATOR ','), ',', 90/100 * COUNT (*)+0.5), ',', -1) как 90thPercentile ....