Saltar al contenido

¿Existe alguna función de regresión lineal en SQL Server?

Solución:

Hasta donde yo sé, no hay ninguno. Sin embargo, escribir uno es bastante sencillo. Lo siguiente le da el alfa constante y la pendiente beta para y = Alfa + Beta * x + épsilon:

-- test data (GroupIDs 1, 2 normal regressions, 3, 4 = no variance)
WITH some_table(GroupID, x, y) AS
(       SELECT 1,  1,  1    UNION SELECT 1,  2,  2    UNION SELECT 1,  3,  1.3  
  UNION SELECT 1,  4,  3.75 UNION SELECT 1,  5,  2.25 UNION SELECT 2, 95, 85    
  UNION SELECT 2, 85, 95    UNION SELECT 2, 80, 70    UNION SELECT 2, 70, 65    
  UNION SELECT 2, 60, 70    UNION SELECT 3,  1,  2    UNION SELECT 3,  1, 3
  UNION SELECT 4,  1,  2    UNION SELECT 4,  2,  2),
 -- linear regression query
/*WITH*/ mean_estimates AS
(   SELECT GroupID
          ,AVG(x * 1.)                                             AS xmean
          ,AVG(y * 1.)                                             AS ymean
    FROM some_table
    GROUP BY GroupID
),
stdev_estimates AS
(   SELECT pd.GroupID
          -- T-SQL STDEV() implementation is not numerically stable
          ,CASE      SUM(SQUARE(x - xmean)) WHEN 0 THEN 1 
           ELSE SQRT(SUM(SQUARE(x - xmean)) / (COUNT(*) - 1)) END AS xstdev
          ,     SQRT(SUM(SQUARE(y - ymean)) / (COUNT(*) - 1))     AS ystdev
    FROM some_table pd
    INNER JOIN mean_estimates  pm ON pm.GroupID = pd.GroupID
    GROUP BY pd.GroupID, pm.xmean, pm.ymean
),
standardized_data AS                   -- increases numerical stability
(   SELECT pd.GroupID
          ,(x - xmean) / xstdev                                    AS xstd
          ,CASE ystdev WHEN 0 THEN 0 ELSE (y - ymean) / ystdev END AS ystd
    FROM some_table pd
    INNER JOIN stdev_estimates ps ON ps.GroupID = pd.GroupID
    INNER JOIN mean_estimates  pm ON pm.GroupID = pd.GroupID
),
standardized_beta_estimates AS
(   SELECT GroupID
          ,CASE WHEN SUM(xstd * xstd) = 0 THEN 0
                ELSE SUM(xstd * ystd) / (COUNT(*) - 1) END         AS betastd
    FROM standardized_data pd
    GROUP BY GroupID
)
SELECT pb.GroupID
      ,ymean - xmean * betastd * ystdev / xstdev                   AS Alpha
      ,betastd * ystdev / xstdev                                   AS Beta
FROM standardized_beta_estimates pb
INNER JOIN stdev_estimates ps ON ps.GroupID = pb.GroupID
INNER JOIN mean_estimates  pm ON pm.GroupID = pb.GroupID

Aquí GroupID se utiliza para mostrar cómo agrupar por algún valor en su tabla de datos de origen. Si solo desea las estadísticas de todos los datos de la tabla (no subgrupos específicos), puede eliminarlo y las uniones. He usado el WITH declaración en aras de la claridad. Como alternativa, puede utilizar subconsultas en su lugar. Tenga en cuenta la precisión del tipo de datos utilizado en sus tablas, ya que la estabilidad numérica puede deteriorarse rápidamente si la precisión no es lo suficientemente alta en relación con sus datos.

EDITAR: (en respuesta a la pregunta de Peter sobre estadísticas adicionales como R2 en los comentarios)

Puede calcular fácilmente estadísticas adicionales utilizando la misma técnica. Aquí hay una versión con R2, correlación y covarianza de muestra:

-- test data (GroupIDs 1, 2 normal regressions, 3, 4 = no variance)
WITH some_table(GroupID, x, y) AS
(       SELECT 1,  1,  1    UNION SELECT 1,  2,  2    UNION SELECT 1,  3,  1.3  
  UNION SELECT 1,  4,  3.75 UNION SELECT 1,  5,  2.25 UNION SELECT 2, 95, 85    
  UNION SELECT 2, 85, 95    UNION SELECT 2, 80, 70    UNION SELECT 2, 70, 65    
  UNION SELECT 2, 60, 70    UNION SELECT 3,  1,  2    UNION SELECT 3,  1, 3
  UNION SELECT 4,  1,  2    UNION SELECT 4,  2,  2),
 -- linear regression query
/*WITH*/ mean_estimates AS
(   SELECT GroupID
          ,AVG(x * 1.)                                             AS xmean
          ,AVG(y * 1.)                                             AS ymean
    FROM some_table pd
    GROUP BY GroupID
),
stdev_estimates AS
(   SELECT pd.GroupID
          -- T-SQL STDEV() implementation is not numerically stable
          ,CASE      SUM(SQUARE(x - xmean)) WHEN 0 THEN 1 
           ELSE SQRT(SUM(SQUARE(x - xmean)) / (COUNT(*) - 1)) END AS xstdev
          ,     SQRT(SUM(SQUARE(y - ymean)) / (COUNT(*) - 1))     AS ystdev
    FROM some_table pd
    INNER JOIN mean_estimates  pm ON pm.GroupID = pd.GroupID
    GROUP BY pd.GroupID, pm.xmean, pm.ymean
),
standardized_data AS                   -- increases numerical stability
(   SELECT pd.GroupID
          ,(x - xmean) / xstdev                                    AS xstd
          ,CASE ystdev WHEN 0 THEN 0 ELSE (y - ymean) / ystdev END AS ystd
    FROM some_table pd
    INNER JOIN stdev_estimates ps ON ps.GroupID = pd.GroupID
    INNER JOIN mean_estimates  pm ON pm.GroupID = pd.GroupID
),
standardized_beta_estimates AS
(   SELECT GroupID
          ,CASE WHEN SUM(xstd * xstd) = 0 THEN 0
                ELSE SUM(xstd * ystd) / (COUNT(*) - 1) END         AS betastd
    FROM standardized_data
    GROUP BY GroupID
)
SELECT pb.GroupID
      ,ymean - xmean * betastd * ystdev / xstdev                   AS Alpha
      ,betastd * ystdev / xstdev                                   AS Beta
      ,CASE ystdev WHEN 0 THEN 1 ELSE betastd * betastd END        AS R2
      ,betastd                                                     AS Correl
      ,betastd * xstdev * ystdev                                   AS Covar
FROM standardized_beta_estimates pb
INNER JOIN stdev_estimates ps ON ps.GroupID = pb.GroupID
INNER JOIN mean_estimates  pm ON pm.GroupID = pb.GroupID

EDITAR 2 mejora la estabilidad numérica al estandarizar los datos (en lugar de solo centrarlos) y al reemplazar STDEV debido a problemas de estabilidad numérica. Para mí, la implementación actual parece ser la mejor compensación entre estabilidad y complejidad. Podría mejorar la estabilidad reemplazando mi desviación estándar con un algoritmo en línea numéricamente estable, pero esto complicaría sustancialmente la implementación (y la ralentizaría). De manera similar, las implementaciones que utilizan, por ejemplo, compensaciones de Kahan (-Babuška-Neumaier) para SUM y AVG parecen funcionar modestamente mejor en pruebas limitadas, pero hacen que la consulta sea mucho más compleja. Y mientras no sepa cómo implementa T-SQL SUM y AVG (por ejemplo, es posible que ya esté usando la suma por pares), no puedo garantizar que tales modificaciones siempre mejoren la precisión.

Este es un método alternativo, basado en una publicación de blog sobre Regresión lineal en T-SQL, que usa las siguientes ecuaciones:

ingrese la descripción de la imagen aquí

Sin embargo, la sugerencia de SQL en el blog usa cursores. Aquí hay una versión mejorada de una respuesta del foro que usé:

table
-----
X (numeric)
Y (numeric)

/**
 * m = (nSxy - SxSy) / (nSxx - SxSx)
 * b = Ay - (Ax * m)
 * N.B. S = Sum, A = Mean
 */
DECLARE @n INT
SELECT @n = COUNT(*) FROM table
SELECT (@n * SUM(X*Y) - SUM(X) * SUM(Y)) / (@n * SUM(X*X) - SUM(X) * SUM(X)) AS M,
       AVG(Y) - AVG(X) *
       (@n * SUM(X*Y) - SUM(X) * SUM(Y)) / (@n * SUM(X*X) - SUM(X) * SUM(X)) AS B
FROM table

De hecho, escribí una rutina SQL usando la ortoorganización Gram-Schmidt. Este, así como otras rutinas de pronóstico y aprendizaje automático, está disponible en sqldatamine.blogspot.com

Por sugerencia de Brad Larson, agregué el código aquí en lugar de solo dirigir a los usuarios a mi blog. Esto produce los mismos resultados que la función linest en Excel. Mi fuente principal es Elements of Statistical Learning (2008) de Hastie, Tibshirni y Friedman.

--Create a table of data
create table #rawdata (id int,area float, rooms float, odd float,  price float)

insert into #rawdata select 1, 2201,3,1,400
insert into #rawdata select 2, 1600,3,0,330
insert into #rawdata select 3, 2400,3,1,369
insert into #rawdata select 4, 1416,2,1,232
insert into #rawdata select 5, 3000,4,0,540

--Insert the data into x & y vectors
select id xid, 0 xn,1 xv into #x from #rawdata
union all
select id, 1,rooms  from #rawdata
union all
select id, 2,area  from #rawdata
union all
select id, 3,odd  from #rawdata

select id yid, 0 yn, price yv  into #y from #rawdata

--create a residuals table and insert the intercept (1)
create table #z (zid int, zn int, zv float)
insert into #z select id , 0 zn,1 zv from #rawdata

--create a table for the orthoganal (#c) & regression(#b) parameters
create table #c(cxn int, czn int, cv float) 
create table #b(bn int, bv float) 


[email protected]
is the number of independent variables including the intercept (@p = 0) declare @p int set @p = 1 --Loop through each independent variable and estimate the orthagonal parameter (#c) -- then estimate the residuals and insert into the residuals table (#z) while @p <= (select max(xn) from #x) begin insert into #c select xn cxn, zn czn, sum(xv*zv)/sum(zv*zv) cv from #x join #z on xid = zid where zn = @p-1 and xn>zn group by xn, zn insert into #z select zid, xn,xv- sum(cv*zv) from #x join #z on xid = zid join #c on czn = zn and cxn = xn where xn = @p and zn=0 begin insert into #b select zn, sum(yv*zv)/ sum(zv*zv) from #z join (select yid, yv-isnull(sum(bv*xv),0) yv from #x join #y on xid = yid left join #b on xn=bn group by yid, yv) y on zid = yid where zn = @p group by zn set @p = @p-1 end --The regression parameters select * from #b --Actual vs. fit with error select yid, yv, fit, yv-fit err from #y join (select xid, sum(xv*bv) fit from #x join #b on xn = bn group by xid) f on yid = xid --R Squared select 1-sum(power(err,2))/sum(power(yv,2)) from (select yid, yv, fit, yv-fit err from #y join (select xid, sum(xv*bv) fit from #x join #b on xn = bn group by xid) f on yid = xid) d

Tienes la opción de añadir valor a nuestro contenido informacional participando con tu veteranía en las notas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *