sqlpostgresqlstatisticsaggregate

CORR en SQL: el coeficiente de correlacion de Pearson en una consulta

Como CORR(y, x) calcula la correlacion de Pearson, que significan su signo y magnitud, como se tratan los NULL y como anadir una recta de tendencia con REGR_*.

3 min de lecturaReferencesql · postgresql · statistics · aggregate · analytics

CORR(y, x) devuelve el coeficiente de correlacion de Pearson entre dos columnas numericas como un unico agregado. El resultado siempre cae entre -1 y 1 e indica con que fuerza ambos valores se mueven juntos de forma lineal.

Que calcula CORR y como leerlo

La funcion mide una relacion lineal: como de bien se ajusta la nube de puntos (x, y) a una recta. El valor se interpreta por su signo y su magnitud:

  • +1 es una relacion directa perfecta: mas x, mas y.
  • -1 es una inversa perfecta: mas x, menos y.
  • 0 significa que no hay vinculo lineal (puede existir uno no lineal).
  • Un |r| de unos 0.7 o mas suele llamarse fuerte, y 0.3-0.7 moderado.

El orden de los argumentos no cambia el coeficiente: CORR(a, b) = CORR(b, a). El orden empezara a importar mas adelante, con las funciones de regresion.

-- Correlation between order amount and the user's account age in days
SELECT CORR(
         o.amount,
         EXTRACT(EPOCH FROM (o.created_at - u.created_at)) / 86400
       ) AS amount_vs_age
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid';

Correlacion no es causalidad

La gran trampa. Un r alto solo dice que los numeros se mueven juntos, no que uno cause el otro. Fuentes clasicas de correlacion espuria:

  • Un factor comun oculto (la estacionalidad o una campana empujan a la vez pedidos y trafico).
  • Efectos de escala: los clientes grandes hacen mas pedidos y tienen un ticket medio mayor.
  • Puro ruido en una muestra pequena: r = 0.9 sobre 5 filas no significa casi nada.
-- Per-country correlation, but only where the sample is large enough to trust
SELECT u.country,
       COUNT(*)                  AS n,
       CORR(o.amount, u.id)      AS r
FROM orders o
JOIN users u ON u.id = o.user_id
GROUP BY u.country
HAVING COUNT(*) >= 30
ORDER BY r DESC;

Muestra siempre el COUNT(*) al lado: un coeficiente sin tamano de muestra es facil de malinterpretar.

Tratamiento de NULL y casos limite

CORR solo usa pares en los que ambos valores no son NULL. Si x o y es NULL, toda la fila se descarta en silencio del calculo. Es el comportamiento por defecto, no un error.

  • Si hay menos de dos pares validos, el resultado es NULL.
  • Si x o y es constante (varianza cero), la division por cero da NULL en lugar de lanzar una excepcion.
-- Salary vs manager presence: rows with NULL manager_id are dropped automatically
SELECT dept,
       COUNT(*)                       AS rows_total,
       COUNT(salary)                  AS rows_with_salary,
       CORR(salary, manager_id)       AS r
FROM employees
GROUP BY dept;

Gotcha: COUNT(*) y el numero de pares que CORR usa de verdad pueden diferir. Para conocer el tamano real de muestra, cuenta los pares de forma explicita, por ejemplo con COUNT(*) FILTER (WHERE salary IS NOT NULL AND manager_id IS NOT NULL).

Combinacion con REGR_* para una recta de tendencia

CORR responde a "que tan fuerte es el vinculo" pero no da la ecuacion de la recta. La familia REGR_* si lo hace, y es barato calcularla en la misma pasada:

  • REGR_SLOPE(y, x) es la pendiente de la recta de tendencia.
  • REGR_INTERCEPT(y, x) es donde cruza el eje y.
  • REGR_R2(y, x) es el coeficiente de determinacion, igual a r al cuadrado.
-- Trend line for amount as a function of account age, plus strength of fit
SELECT REGR_SLOPE(o.amount, age_days)     AS slope,
       REGR_INTERCEPT(o.amount, age_days) AS intercept,
       CORR(o.amount, age_days)           AS r,
       REGR_R2(o.amount, age_days)        AS r_squared
FROM (
  SELECT o.amount,
         EXTRACT(EPOCH FROM (o.created_at - u.created_at)) / 86400 AS age_days
  FROM orders o
  JOIN users u ON u.id = o.user_id
) o;

Pronostico: predicted_amount = intercept + slope * age_days.

Diferencias entre bases de datos

  • PostgreSQL: soporte completo de CORR, COVAR_POP, COVAR_SAMP y todo el conjunto REGR_*.
  • ClickHouse: tiene corr(x, y) pero no REGR_*; la pendiente se deriva a mano desde covarPop y varPop.
  • MySQL: no existe CORR. Se arma la formula con AVG, STDDEV_POP y el producto medio, o se calcula en la aplicacion.
-- Portable fallback that works even without a built-in CORR
SELECT (AVG(x * y) - AVG(x) * AVG(y))
       / (STDDEV_POP(x) * STDDEV_POP(y)) AS r
FROM (SELECT amount AS x, user_id AS y FROM orders) t;

En resumen: CORR es una forma barata y de una sola pasada de estimar la fuerza de un vinculo lineal, y un REGR_* al lado convierte esa estimacion en una ecuacion de tendencia lista para usar.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador