Функція оцінки Theil-Sen в T-SQL


Відповіді:


9

Я брехав, коли сказав, що я не в змозі перекодувати його в SQL. Я був просто лінивий. Ось код з прикладом використання.

Код заснований на бібліотеці Perl TheiSen , використовуючи QuickMedian . Давайте визначимо новий тип таблиці, щоб легко передати наші дані в процедуру.

CREATE TYPE dbo.TheilSenInputDataTableType AS TABLE 
(
    ID INT IDENTITY(1,1),
    x REAL, 
    y REAL
)

Зверніть увагу на стовпчик ідентифікатора, який тут важливий, оскільки наше рішення використовує оператор CROSS APPLY, щоб досягти правильної інтерпретації внутрішнього циклу, знайденого в TheilSen.pm.

my ($x1,$x2,$y1,$y2);
foreach my $i(0 .. $n-2){
    $y1 = $y->[$i];
    $x1 = $x->[$i];
    foreach my $j($i+1 .. $n-1){
        $y2 = $y->[$j];
        $x2 = $x->[$j];

Також нам знадобиться новий тип даних для зберігання масиву реальних значень типу.

CREATE TYPE [dbo].[RealArray] AS TABLE(
    [val] [real] NULL
)

Ось функція f_QuickMedian , що повертає медіану для заданого масиву. Заслуга в цьому - Іцік Бен-Ган .

CREATE FUNCTION [dbo].[f_QuickMedian](@RealArray RealArray READONLY)
RETURNS REAL
AS
BEGIN
    DECLARE @Median REAL;
    DECLARE @QMedian REAL;

    SELECT @Median = AVG(1.0 * val)
    FROM
    (
        SELECT o.val, rn = ROW_NUMBER() OVER (ORDER BY o.val), c.c
        FROM @RealArray AS o
        CROSS JOIN (SELECT c = COUNT(*) FROM @RealArray) AS c
    ) AS x
    WHERE rn IN ((c + 1)/2, (c + 2)/2);

    SELECT TOP 1 @QMedian = val FROM @RealArray
    ORDER BY ABS(val - @Median) ASC, val DESC

    RETURN @QMedian
END

І Оцінювач p_TheilSen :

CREATE PROCEDURE [dbo].[p_TheilSen](
      @TheilSenInput TheilSenInputDataTableType READONLY
    , @m Real OUTPUT
    , @c Real OUTPUT
)
AS
BEGIN
    DECLARE 
        @m_arr RealArray
      , @c_arr RealArray;       

    INSERT INTO @m_arr
        SELECT m
        FROM 
        (
            SELECT  
                t1.x as x1
                , t1.y as y1
                , t2o.x as x2
                , t2o.y as y2
                , t2o.y-t1.y as [y2 - y1]
                , t2o.x-t1.x as [x2 - x1]
                , CASE WHEN (t2o.x <> t1.x) THEN  CAST((t2o.y-t1.y) AS Real)/(t2o.x-t1.x) ELSE NULL END AS [($y2-$y1)/($x2-$x1)]
                , CASE WHEN t1.y = t2o.y THEN 0
                  ELSE
                    CASE WHEN t1.x = t2o.x THEN NULL
                        ELSE 
                        -- push @M, ($y2-$y1)/($x2-$x1);
                        CAST((t2o.y-t1.y) AS Real)/(t2o.x-t1.x)
                    END
                  END as m
            FROM @TheilSenInput t1
            CROSS APPLY
                    (
                    SELECT  t2.x, t2.y
                    FROM    @TheilSenInput t2
                    WHERE   t2.ID > t1.ID
                     ) t2o
        ) t
        WHERE m IS NOT NULL 

    SELECT @m = dbo.f_QuickMedian(@m_arr)

    INSERT INTO @c_arr
        SELECT y - (@m * x)
            FROM @TheilSenInput

    SELECT @c = dbo.f_QuickMedian(@c_arr)

END

Приклад:

DECLARE 
      @in TheilSenInputDataTableType
    , @m Real
    , @c Real

INSERT INTO @in(x,y) VALUES (10.79,118.99)
INSERT INTO @in(x,y) VALUES (10.8,120.76)
INSERT INTO @in(x,y) VALUES (10.86,122.71)
INSERT INTO @in(x,y) VALUES (10.93,125.48)
INSERT INTO @in(x,y) VALUES (10.99,127.31)
INSERT INTO @in(x,y) VALUES (10.96,130.06)
INSERT INTO @in(x,y) VALUES (10.98,132.41)
INSERT INTO @in(x,y) VALUES (11.03,135.89)
INSERT INTO @in(x,y) VALUES (11.08,139.02)
INSERT INTO @in(x,y) VALUES (11.1,140.25)
INSERT INTO @in(x,y) VALUES (11.19,145.61)
INSERT INTO @in(x,y) VALUES (11.25,153.45)
INSERT INTO @in(x,y) VALUES (11.4,158.03)
INSERT INTO @in(x,y) VALUES (11.61,162.72)
INSERT INTO @in(x,y) VALUES (11.69,167.67)
INSERT INTO @in(x,y) VALUES (11.91,172.86)
INSERT INTO @in(x,y) VALUES (12.07,177.52)
INSERT INTO @in(x,y) VALUES (12.32,182.09)


EXEC p_TheilSen @in, @m = @m OUTPUT, @c = @c OUTPUT

SELECT @m
SELECT @c

Повернення:

m = 52.7079
c = -448.4853

Просто для порівняння, версія perl повертає наступні значення для того ж набору даних:

m = 52.7078651685394
c = -448.484943820225

Я використовую оцінювач TheilSen для обчислення метрики DaysToFill для файлових систем. Насолоджуйтесь!


1

Це, швидше за все, було б добре підходити для того, щоб робити щось у SQLCLR, подібно до наступного питання / відповіді (також тут, на DBA.SE):

Чи існує реалізація на SQL Server найдовшої загальної проблеми підрядки?

Коли я встигну пізніше, я побачу, наскільки це можливо.


1

Я перевірив також T-SQL, Oracle і сервери взагалі (занадто складний, щоб писати в чистому SQL).

Тим НЕ менше, ви можете бути зацікавлені в цьому (науковий / статистичний пакет для Python). Алгоритм реалізований там і в Python. Python - це мова, яку люди мають хоч якісь шанси зрозуміти, на відміну від Perl.

Ваше запитання мене заінтригувало, і я розкопався. Є бібліотеки C і C ++, які містять цей алгоритм - і він також доступний у кількох пакетах R. А публікація @srutzky також виглядає цікаво.

+1 за цікаве запитання BTW - і ласкаво просимо на форум :-)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.