Classe TLinearRegression

Este artigo fornece o código fonte para a implementação de uma agradável e limpa regressão linear pelo algoritmo de erro linear, menos-quadrado.

(Este artigo apareceu originalmente em 17 de março de 2000 na edição 
do The Unofficial Newsletter of
Delphi Users)

Classe TLinearRegression

Este artigo fornece o código fonte para a implementação de uma agradável e limpa regressão linear pelo algoritmo de erro linear, menos-quadrado.

A classe fornece propriedades retornando a média para ambas as variáveis independentes e dependentes, bem como a variância, covariância e correlação. Uma função método é fornecida para interpolar ao longo da linha definida pela inclinação do computador e interceptar.

O código é bem documentado, dando o esboço da base teórica e algumas notas sobre o que os diversos métodos estão fazendo.

Um projeto exemplo também está incluído.

(************************************************* *****************)
()
(Classe execução de regressão linear por quadrados-menos-lineares)
()
(Copyright © 2000, Berend M. Tober.. Todos os direitos reservados. )
(E-mail do Autor - mailto: btober@computer.org)
Outros componentes em () 
(Http://www.connix.com/ ~ btober / delphi.htm)
()
(************************************************* *****************)
linregr unidade;
Classe) (Regressão linear
(

Nota sobre a declaração de problema
------------------------------------

Esta classe é utilizada para encontrar o melhor encaixar-slope (m) e o interceptor-y (b), como na equação de uma linha reta

y: = m * x + b

no caso de você ter mais de dois (x, y) pontos pares de dados, ou seja, você tem (Xi, Yi) para i: = 1,2 ,..., n, com n> 2. Nesta situação, você pode escrever n equações: 

Y1: = m * x1 + b + E1
Y2: = m * X2 + b + E2
.
.
.
Yn: = m * Xn + b + En

dando um sistema de equações lineares n que você precisa para resolver duas incógnitas, a saber, m e b, onde os Ei são termos de erro. Um algoritmo quadrado-menos-linear de erro empregado aqui encontra o melhor m e b tal que a soma dos termos de erro quadrados é minimizado. 

A álgebra linear por trás da solução é escrever uma matriz A n-por-2 que tenha linhas parecendo com [1, Xi] para i = 1, .., n e um vetor u 2-por-1 de incógnitas com a interceptação e inclinação, ou seja, u = [b, m] ', onde ( ') indica" assumir a transposição do ". Há um monte de outros detalhes técnicos como "encontrar uma base ortogonal para um sub-espaço", e "garantir soluções únicas", etc, que eu vagamente me recordo do meu curso de vetores reais, mas ele resume-se a escrita.

e'e = (y - Au) '(y - Au) = | y - Au | ^ 2

para mostrar a melhor solução encontrada, resolvendo o u na equação

Au y =

onde, para resumir:

y = [Y1, Y2 ,..., Yn] '
u = [b, m] "
A = [[1, X1] ', [1, X2 ]',...[ 1, Xn]'] '

No processo de resolver esta, você eventualmente chega à solução explícita

u = inv (A'A) (A'y)

onde inv () significa "pegar o inverso de". Esta é a equação implementada nesta classe. Ela calcula as estatísticas "on the fly", por isso funciona para qualquer número de dois ou mais pontos de dados.

Isso tudo é bem conhecido, coisas tiradas diretamente de um livro didático. Eu não reclamo qualquer crédito para a matemática ... eu não estou inventando nada de novo aqui, basta fazer uma ardilosa implementação.


Nota: No que diz respeito às exceções
-------------------------------

Esta classe não levanta exceções: o seu aplicativo de controle não deve cair na armadilha dos erros de 'divisão por zero' que podem ocorrer em

function GetCorrelation

        function GetIntercept

        function GetSlope

Eu não construí esta exceção, porque implicaria uma cláusula USES. Tal como está, esta unidade não tem dependências explícitas sobre quaisquer outras unidades, e eu não podia suportar destruir essa pureza!

A circunstância 'divisão por zero' pode ocorrer quando aceder a qualquer das propriedades derivadas, relacionadas à covariância se:


•    você tiver acumulado menos de dois pontos de dados, ou

•    você "degenerou" dados, ou seja, todos os x são os mesmos (isto corresponde à inclinação do infinito), caso em que você vai achar que CovarianceXX será zero (ou dentro de alguns pequenos intervalos errados de zero).



Nota relativa à normalização de dados
-------------------------------

É recomendado que você normalize os dados, antes de aplicar estes algoritmos quadrados-menos-lineares, se você puder.

Para pura escala multiplicativa, aplique o seguinted:

Se você escala como dados de entrada (x ', y'): = (Ax, By), então você deve
1) ajustar o interceptador no computador b: = b '/ B, e
2) ajustar a inclinação no computador m: = m '* A / B
)

interface
type
  TRegressionRec=Record
     N :Integer;
{ Soma do número de pontos de dados acumulados }
     AvgX :Double;
{ Média de variáveis independentes }
     AvgY :Double;
{ Média de variáveis dependentes }
     AvgXX :Double;
{ Média de variáveis independentes quadradas }
     AvgYY :Double;
{ Média de variáveis dependentes quadradas }
     AvgXY :Double;
{ Média de produtos cruzados }
  end;


TLinearRegression=class(TObject)
    private     
FRegressionRec:TRegressionRec;
      function GetCorrelation:Double;
      function GetCovarianceXX:Double;
      function GetCovarianceYY:Double;
      function GetCovarianceXY:Double;
      function GetIntercept:Double;
      function GetSlope:Double;
    public
      constructor Create;
      procedure Clear;
      function Add(X,Y:Double):Integer;
      function Interpolate(x:Double):Double;

  property AverageX:Double Read FRegressionRec.AvgX;
      property AverageY:Double read FRegressionRec.AvgY;
      property Count:Integer read FRegressionRec.N;
      property Correlation:Double Read GetCorrelation;
      property CovarianceXX:Double Read GetCovarianceXX;
      property CovarianceYY:Double Read GetCovarianceYY;
      property CovarianceXY:Double Read GetCovarianceXY;
      property Intercept:Double Read GetIntercept;
      property Slope:Double Read GetSlope;
  end;

execução

devem acumular funções
(
N: Integer; (Número de seqüência deste novo ponto de dados)
a, (A média existente (dos pontos de dados N-1))
x: Double (Os novos valores de dados a serem incluídos na médio executada)): Double;
Retorna a média executada () 

begin
  Result:=(N-1)/N;
  Result:=a*Result + x/N;
end;

constructor TLinearRegression.Create;
begin
  inherited Create;
  Clear;
end;

function TLinearRegression.Add(X,Y:Double):Integer;
  {Essa função 'acumula' outro ponto de dados (x,y) }
begin
  with FRegressionRec DO

  begin
    Inc(N);

 AvgX := Accumulate(N,AvgX,X);
    AvgY := Accumulate(N,AvgY,Y);

    AvgXX := Accumulate(N,AvgXX,X*X);
    AvgYY := Accumulate(N,AvgYY,Y*Y);
    AvgXY := Accumulate(N,AvgXY,X*Y);

    Result:=N;
  end;
end;

procedure TLinearRegression.Clear;

begin

  with FRegressionRec do

  begin
    N := 0;
    AvgX := 0;
    AvgY := 0;
   AvgXX:= 0;
    AvgYY:= 0;
    AvgXY:= 0;
    end;
end;

function TLinearRegression.GetCovarianceXX:Double;
begin
  with FRegressionRec do
    Result := AvgXX-AvgX*AvgX;
end;

function TLinearRegression.GetCovarianceYY:Double;
begin
  with FRegressionRec do
    Result := AvgYY-AvgY*AvgY;
end;

function TLinearRegression.GetCovarianceXY:Double;
  begin
    with FRegressionRec do
    Result := AvgXY-AvgX*AvgY;
  end;

function TLinearRegression.GetCorrelation:Double;
  var CovXX,CovYY:Double;
  begin
    with FRegressionRec do
        begin
        CovXX:=GetCovarianceXX;
        CovYY:=GetCovarianceYY;
        Result:=0;
        if CovXX = 0.0 then
          Result := 1.0
{ caso degenerado com inclinação infinita}
        else if CovYY = 0.0 then
          Result := 1.0
{Dados têm inclinação zero }
        else
          Result := GetCovarianceXY/Sqrt(CovXX*CovYY);
        end;
  end;

function TLinearRegression.GetIntercept:Double;
  begin
  {Note que, se CovXX for zero, então nenhum interceptor y único existe}
  with FRegressionRec do
    Result:=(AvgY*AvgXX-AvgX*AvgXY)/GetCovarianceXX;
  end;

function TLinearRegression.GetSlope:Double;
  begin
  {Note que se CovXX for zero, então a inclinação é infinita}
  with FRegressionRec do
    Result := GetCovarianceXY/GetCovarianceXX;
  end;

function TLinearRegression.Interpolate(x:Double):Double;
begin
  with FRegressionRec do
    Result:=x*Slope + Intercept;
end;

end.

Exemplo de arquivo de projeto segue:

Exemplo de programa;

uses
  WinCRT,linregr;

type TDataArray=Array[1..10,1..2]
of Real;
const

  Data1:TDataArray=
  (
    (75,81),
    (78,73),
    (88,85),
    (92,85),
    (95,89),
    (67,73),
    (55,66),
    (73,81),
    (74,81),
    (80,81)
  );
{
  Estes são os resultados "corretos" para os dados definidos acima:
r=0.891, m=0.513, b=39.658

}

Data2: TDataArray =
( (Inclinação zero)
(75,1),
(78,1),
(88,1),
(92,1),
(95,1),
(67,1),
(55,1),
(73,1),
(74,1),
(80,1)
);

Data3: TDataArray =
( (Inclinação Infinita)
(2,81),
(2,73),
(2,85),
(2,85),
(2,89),
(2,73),
(2,66),
(2,81),
(2,81),
(2,81)
);


procedure TestRegression(Data:TDataArray);
  var i:Word;
  begin
  with TLinearRegression.Create do
    begin
    for i:= 1 to 10 do
      Add(Data[i,1],Data[i,2]);
    try
      write(Correlation:12:3);
      write(Slope:12:3);
      writeln(Intercept:12:3);
      writeln('Average of X''s',AverageX:12:3);
      writeln('Average of Y''s',AverageY:12:3);
      writeln('Std Dev of X''s',sqrt(CovarianceXX):12:3);

      writeln('Std Dev of Y''s',sqrt(CovarianceYY):12:3);
    except
    end;
    writeln;
    Free;
    end;
  end;

begin
    writeln(Esses são o que os valores de correlação, inclinação e intercepção deveriam ser:');
    writeln(0.891:12:3,0.513:12:3,39.658:12:3,#13);

    writeln( Resultados do exemplo:');
    TestRegression(Data1);

    writeln(#13'Dados com inclinação zero');
    TestRegression(Data2);

    writeln(#13'Dados degenerados com inclinação infinita');
    TestRegression(Data3);
end.