unit UnitClsStats;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils; 

type
    TAofPop= array of array of integer;

{ TClsStats }

TClsStats= class(TObject)
private
       fExpOld: integer;//To store last call
public
      constructor Create;
      destructor Destroy; override;
      procedure InitConfusion(NCls: integer);
      function ConfusionS: TStringList;
      function Recall(i: integer): double;
      function Precision(i: integer): double;
      function Fmeasure(i: integer): double;
      function MCC(i: integer): double;
      function Accuracy: double;
      function BalancedAccuracy: double;
      property ExpOld: integer read fExpOld write fExpOld;
      Confusion: TAofPop;//fConfusion(Exp,Pred)
end;

implementation

{ TClsStats }

constructor TClsStats.Create;
begin
     fExpOld:=-1;
end;

destructor TClsStats.Destroy;
var
   i: integer;
begin
     for i:=Low(Confusion) to High(Confusion) do begin
         SetLength(Confusion[i],0);
     end;
     SetLength(Confusion,0);
     inherited Destroy;
end;

procedure TClsStats.InitConfusion(NCls: integer);
var
   i,j: integer;
begin
     SetLength(Confusion,NCls);
     for i:=Low(Confusion) to High(Confusion) do
         SetLength(Confusion[i],NCls);
     //
     for i:=Low(Confusion) to High(Confusion) do
         for j:=Low(Confusion[i]) to High(Confusion[i]) do
             Confusion[i,j]:=0;
end;

function TClsStats.ConfusionS: TStringList;
var
   i,j: integer;
   OneLine: string;
begin
     Result:=TStringList.Create;
     Result.Add('------');
     Result.Add('      CONFUSION MATRIX');
     OneLine:='       |';
     for i:=Low(Confusion) to High(Confusion) do OneLine := OneLine+Format('%7d', [i]);
     Result.Add(OneLine);
     OneLine:='-------+';
     for i:=Low(Confusion) to High(Confusion) do OneLine := OneLine+'-------';
     Result.Add(OneLine);
     for i:=Low(Confusion) to High(Confusion) do begin
         OneLine := '';
         OneLine := OneLine+Format('%7d', [i])+'|';
         for j:=Low(Confusion[i]) to High(Confusion[i]) do
             OneLine := OneLine+Format('%7d', [Confusion[j,i]]);
         Result.Add(OneLine);
     end;
end;

function TClsStats.Recall(i: integer): double;
var
   j: integer;
   Success, All: integer;
begin
     Result:=0;
     All:=0;
     Success:=Confusion[i,i];
     for j:=Low(Confusion[i]) to High(Confusion[i]) do begin
         All:=All+Confusion[i,j];
     end;
     if (All>0) then
        Result:=Success/All;
end;

function TClsStats.Precision(i: integer): double;
var
   j: integer;
   Success, All: integer;
begin
     Result:=0;
     All:=0;
     Success:=Confusion[i,i];
     for j:=Low(Confusion[i]) to High(Confusion[i]) do begin
         All:=All+Confusion[j,i];
     end;
     if (All>0) then
        Result:=Success/All;
end;

function TClsStats.Fmeasure(i: integer): double;
var
   R,P: double;
begin
     Result:=0;
     R:=Recall(i);
     P:=Precision(i);
     if ((R+P)>0) then
        Result:=2*R*P/(R+P);
end;

function TClsStats.MCC(i: integer): double;
var
   j: integer;
   TP, FP, TN, FN: integer;
begin
     Result:=0;
     TP:=0; FP:=0; TN:=0; FN:=0;
     TP:=Confusion[i,i];
     for j:=Low(Confusion[i]) to High(Confusion[i]) do
         if (j<>i) then TN:=TN+Confusion[j,j];
     for j:=Low(Confusion[i]) to High(Confusion[i]) do
         if (j<>i) then FP:=FP+Confusion[j,i];
     for j:=Low(Confusion[i]) to High(Confusion[i]) do
         if (j<>i) then FN:=FN+Confusion[i,j];
     if (((TP+FP)>0) and ((TP+FN)>0) and ((TN+FP)>0) and ((TN+FN)>0)) then Result:=(TP*TN-FP*FN)/sqrt((TP+FP)*(TP+FN)*(TN+FP)*(TN+FN));
end;

function TClsStats.Accuracy: double;
var
   i,j: integer;
   Success, All: integer;
begin
     Result:=0;
     Success:=0;
     All:=0;
     for i:=Low(Confusion) to High(Confusion) do begin
         Success:=Success+Confusion[i,i];
         for j:=Low(Confusion[i]) to High(Confusion[i]) do
             All:=All+Confusion[i,j];
     end;
     if (All>0) then
        Result:=Success/All;
end;

function TClsStats.BalancedAccuracy: double;
var
   i,N: integer;
   R: double;
begin
     Result:=0;
     R:=0;
     N:=0;
     for i:=Low(Confusion) to High(Confusion) do begin
         R:=R+Recall(i);
         Inc(N);
     end;
     if (N>0) then
        Result:=R/N;
end;

end.

