Pregunta

Yo tengo un gran tabla de medición de datos en MySQL y necesito calcular el percentil para cada uno de estos valores.Oracle parece tener una función llamada percent_rank pero no puedo encontrar nada similar para MySQL.Estoy seguro de que podría sólo fuerza bruta en Python que puedo usar de todos modos para rellenar la tabla, pero sospecho que sería muy ineficiente debido a que una muestra puede tener 200.000 observaciones.

¿Fue útil?

Solución

Esta es una respuesta relativamente fea, y me siento culpable al decirlo. Dicho esto, podría ayudarte con tu problema.

Una forma de determinar el porcentaje sería contar todas las filas y contar la cantidad de filas que es mayor que la cantidad que proporcionó. Puede calcular mayor o menor que y tomar el inverso según sea necesario.

Cree un índice en su número. total = seleccionar recuento (); less_equal = seleccionar cuenta () donde valor > indexed_number;

El porcentaje sería algo así como: less_equal / total o (total - less_equal) / total

Asegúrese de que ambos estén usando el índice que creó. Si no lo están, modifícalos hasta que lo estén. La consulta de explicación debe tener & Quot; utilizando index & Quot; en la columna de la derecha. En el caso del recuento selectivo (*) debería estar usando index para InnoDB y algo así como const para MyISAM. MyISAM conocerá este valor en cualquier momento sin tener que calcularlo.

Si necesita tener el porcentaje almacenado en la base de datos, puede usar la configuración desde arriba para el rendimiento y luego calcular el valor de cada fila utilizando la segunda consulta como una selección interna. El valor de la primera consulta se puede establecer como una constante.

¿Ayuda esto?

Jacob

Otros consejos

Aquí hay un enfoque diferente que no requiere una unión. En mi caso (una tabla con más de 15,000 filas), se ejecuta en aproximadamente 3 segundos. (El método JOIN toma un orden de magnitud más largo).

En la muestra, suponga que medida es la columna en la que está calculando el rango porcentual, y id es solo un identificador de fila (no obligatorio):

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

El crédito para este método va a Shlomi Noach. Él escribe sobre ello en detalle aquí:

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

He probado esto en MySQL y funciona muy bien; ninguna idea sobre Oracle, SQLServer, etc.

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;

Si está combinando su SQL con un lenguaje de procedimiento como PHP, puede hacer lo siguiente. Este ejemplo desglosa el exceso de tiempo de bloqueo de vuelo en un aeropuerto, en sus percentiles. Utiliza la cláusula LIMIT x, y en MySQL en combinación con ORDER BY. No es muy bonito, pero hace el trabajo (lo siento, luché con el formato):

$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";
        }
    }
}

MySQL 8 finalmente introdujo las funciones de la ventana, y entre ellos, el PERCENT_RANK() la función que usted estaba buscando.Así, sólo tiene que escribir:

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

Su pregunta menciona "percentiles", que son un poco diferente de la cosa.La integridad de' amor, hay PERCENTILE_DISC y PERCENTILE_CONT inversa de la distribución de funciones en el estándar SQL y en algunos RBDMS (Oracle, PostgreSQL, SQL Server, Teradata), pero no en MySQL.Con MySQL 8 y las funciones de la ventana, se puede emular PERCENTILE_DISC, sin embargo, de nuevo con la PERCENT_RANK y FIRST_VALUE las funciones de la ventana.

Para obtener el rango, yo diría que necesitas (izquierda) unirte a la mesa en sí mismo de forma similar a:

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 

Para cada fila, contará cuántas filas (si las hay) de la misma tabla tienen un valor inferior.

Tenga en cuenta que estoy más familiarizado con sqlserver, por lo que la sintaxis podría no ser correcta. Además, lo distinto puede no tener el comportamiento correcto para lo que desea lograr. Pero esa es la idea general.
Luego, para obtener el rango de percentil real, primero deberá obtener el número de valores en una variable (o valores distintos según la convención que desee tomar) y calcular el rango de percentil usando el rango real indicado anteriormente.

Supongamos que tenemos una tabla de ventas como:

user_id, unidades

la siguiente consulta dará el percentil de cada usuario:

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 ;

Tenga en cuenta que esto se aplicará a la unión cruzada, por lo que resultará en una complejidad O (n2), por lo que puede considerarse como una solución no optimizada, pero parece simple, dado que no tenemos ninguna función en la versión de MySQL.

No estoy seguro de qué significaba la operación por 'rango de percentil', pero para obtener un percentil dado para un conjunto de valores, consulte http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html El cálculo de sql podría modificarse fácilmente para producir otro o varios percentiles.

Una nota: tuve que cambiar ligeramente el cálculo, por ejemplo, el percentil 90 - " 90/100 * COUNT (*) + 0.5 " en lugar de " 90/100 * COUNT (*) + 1 " ;. A veces se saltaba dos valores más allá del punto de percentil en la lista ordenada, en lugar de elegir el siguiente valor más alto para el percentil. Tal vez la forma en que el redondeo entero funciona en mysql.

es decir:

.... SUBSTRING_INDEX (SUBSTRING_INDEX (GROUP_CONCAT (fieldValue ORDER BY fieldValue SEPARATOR ','), ',', 90/100 * COUNT (*) + 0.5 ), ',', -1) como percentil 90 ....

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top