Есть ли способ рассчитать корреляцию в TSQL, используя предложения OVER вместо CTE? - PullRequest
7 голосов
/ 04 августа 2011

Допустим, у вас есть таблица со столбцами, Date, GroupID, X и Y.

CREATE TABLE #sample
  (
     [Date]  DATETIME,
     GroupID INT,
     X       FLOAT,
     Y       FLOAT
  )

DECLARE @date DATETIME = getdate()

INSERT INTO #sample VALUES(@date, 1, 1,3)
INSERT INTO #sample VALUES(DATEADD(d, 1, @date), 1, 1,1)
INSERT INTO #sample VALUES(DATEADD(d, 2, @date), 1, 4,2)
INSERT INTO #sample VALUES(DATEADD(d, 3, @date), 1, 3,3)
INSERT INTO #sample VALUES(DATEADD(d, 4, @date), 1, 6,4)
INSERT INTO #sample VALUES(DATEADD(d, 5, @date), 1, 7,5)
INSERT INTO #sample VALUES(DATEADD(d, 6, @date), 1, 1,6)

и вы хотите рассчитать соотношение X и Y для каждой группы. В настоящее время я использую CTE, которые немного запутались:

;WITH DataAvgStd
     AS (SELECT GroupID,
                AVG(X)   AS XAvg,
                AVG(Y)   AS YAvg,
                STDEV(X) AS XStdev,
                STDEV(Y) AS YSTDev,
                COUNT(*) AS SampleSize
         FROM   #sample
         GROUP  BY GroupID),
     ExpectedVal
     AS (SELECT s.GroupID,
                SUM(( X - XAvg ) * ( Y - YAvg )) AS ExpectedValue
         FROM   #sample s
                JOIN DataAvgStd das
                  ON s.GroupID = das.GroupID
         GROUP  BY s.GroupID)
SELECT das.GroupID,
       ev.ExpectedValue / ( das.SampleSize - 1 ) / ( das.XStdev * das.YSTDev )
       AS
       Correlation
FROM   DataAvgStd das
       JOIN ExpectedVal ev
         ON das.GroupID = ev.GroupID

DROP TABLE #sample  

Кажется, должен быть способ использовать OVER и PARTITION, чтобы сделать это одним махом без каких-либо подзапросов. В идеале TSQL должен иметь функцию, чтобы вы могли написать:

SELECT GroupID, CORR(X, Y) OVER(PARTITION BY GroupID)
FROM #sample
GROUP BY GroupID

Ответы [ 3 ]

9 голосов
/ 03 февраля 2016

Используя эту формулу корреляции, вы не сможете избежать всех вложенных запросов, даже если вы используете over().Дело в том, что вы не можете использовать обе группы в одном и том же запросе, а также вы не можете иметь вложенные функции агрегирования, например, sum(x - avg(x)).Таким образом, вы в лучшем случае, согласно вашим данным, вам нужно будет сохранить хотя бы with.

Ваш код будет выглядеть примерно так

;WITH DataAvgStd
     AS (SELECT GroupID,
                STDEV(X) over(partition by GroupID) AS XStdev,
                STDEV(Y) over(partition by GroupID) AS YSTDev,
                COUNT(*) over(partition by GroupID) AS SampleSize,
                ( X - AVG(X) over(partition by GroupID)) * ( Y - AVG(Y) over(partition by GroupID)) AS ExpectedValue
         FROM   #sample s)         
SELECT distinct GroupID,
       SUM(ExpectedValue) over(partition by GroupID) / (SampleSize - 1 ) / ( XStdev * YSTDev ) AS Correlation
FROM DataAvgStd 

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

можно записать как

SELECT GroupID,
       Correlation=(COUNT(*) * SUM(X * Y) - SUM(X) * SUM(Y)) / 
                   (SQRT(COUNT(*) * SUM(X * X) - SUM(X) * SUM(x))
                    * SQRT(COUNT(*) * SUM(Y* Y) - SUM(Y) * SUM(Y)))
FROM #sample s
GROUP BY GroupID;
2 голосов
/ 03 мая 2017

Однопроходное решение для обоих калибров:

Существует два варианта коэффициента корреляции Пирсона: один для выборки и один для всего населения.Это простые, однопроходные и, я полагаю, правильные формулы для обоих:

-- Methods for calculating the two Pearson correlation coefficients
SELECT  
        -- For Population
        (avg(x * y) - avg(x) * avg(y)) / 
        (sqrt(avg(x * x) - avg(x) * avg(x)) * sqrt(avg(y * y) - avg(y) * avg(y))) 
        AS correlation_coefficient_population,
        -- For Sample
        (count(*) * sum(x * y) - sum(x) * sum(y)) / 
        (sqrt(count(*) * sum(x * x) - sum(x) * sum(x)) * sqrt(count(*) * sum(y * y) - sum(y) * sum(y))) 
        AS correlation_coefficient_sample
    FROM (
        -- The following generates a table of sample data containing two columns with a luke-warm and tweakable correlation 
        -- y = x for 0 thru 99, y = x - 100 for 100 thru 199, etc.  Execute it as a stand-alone to see for yourself
        -- x and y are CAST as DECIMAL to avoid integer math, you should definitely do the same
        -- Try TOP 100 or less for full correlation (y = x for all cases), TOP 200 for a PCC of 0.5, TOP 300 for one near 0.33, etc.
        -- The superfluous "+ 0" is where you could apply various offsets to see that they have no effect on the results
        SELECT TOP 200
                CAST(ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 + 0 AS DECIMAL) AS x, 
                CAST((ROW_NUMBER() OVER (ORDER BY [object_id]) - 1) % 100 AS DECIMAL) AS y 
            FROM sys.all_objects
    ) AS a

Как я отмечал в комментариях, вы можете попробовать пример с TOP 100 или менее для полной корреляции (y = xдля всех случаев);ТОП 200 дает корреляции очень близко к 0,5;ТОП 300, около 0,33;и т.д. Есть место ("+ 0") для добавления смещения, если хотите;Оповещение спойлера, это не имеет никакого эффекта.Удостоверьтесь, что вы ЗАЧИЩАЕТЕ свои значения как DECIMAL - целочисленная математика может существенно повлиять на эти вычисления.

1 голос
/ 04 августа 2011

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

Если это должно быть реализовано на сервере БД, и вы ищете что-то более читаемое, чем CTE, ваш единственный вариант - свернуть свой агрегат с помощью CLR.

Здесь есть хорошее руководство http://www.sqlservercentral.com/articles/SQLCLR/71942/ по созданию аналогичного агрегата CLR.

...