Files
lazarus-ccr/applications/lazstats/source/forms/analysis/nonparametric/exactunit.pas
wp_xxyyzz 2f33dc9f7b LazStats: initial commit.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7345 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2020-03-30 18:01:44 +00:00

543 lines
14 KiB
ObjectPascal

unit ExactUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
ExtCtrls, StdCtrls, Buttons, MainUnit, OutPutUnit, FunctionsLib,
Globals, DataProcs, Math;
type
{ TFisherFrm }
TFisherFrm = class(TForm)
Bevel1: TBevel;
Label5: TLabel;
ResetBtn: TButton;
ComputeBtn: TButton;
CloseBtn: TButton;
RC11Edit: TEdit;
RC12Edit: TEdit;
RC21Edit: TEdit;
RC22Edit: TEdit;
Label6: TLabel;
Label7: TLabel;
Label8: TLabel;
Label9: TLabel;
NCasesEdit: TEdit;
NCasesLabel: TLabel;
Panel2: TPanel;
RowIn: TBitBtn;
RowOut: TBitBtn;
ColIn: TBitBtn;
ColOut: TBitBtn;
DepIn: TBitBtn;
DepOut: TBitBtn;
ColEdit: TEdit;
DepEdit: TEdit;
RowEdit: TEdit;
InputGrp: TRadioGroup;
Label1: TLabel;
RowLabel: TLabel;
ColLabel: TLabel;
DepLabel: TLabel;
VarList: TListBox;
Panel1: TPanel;
procedure ColInClick(Sender: TObject);
procedure ColOutClick(Sender: TObject);
procedure ComputeBtnClick(Sender: TObject);
procedure DepInClick(Sender: TObject);
procedure DepOutClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure InputGrpClick(Sender: TObject);
procedure RC11EditKeyPress(Sender: TObject; var Key: char);
procedure RC12EditKeyPress(Sender: TObject; var Key: char);
procedure RC21EditKeyPress(Sender: TObject; var Key: char);
procedure RC22EditKeyPress(Sender: TObject; var Key: char);
procedure ResetBtnClick(Sender: TObject);
procedure RowInClick(Sender: TObject);
procedure RowOutClick(Sender: TObject);
private
{ private declarations }
FAutoSized: Boolean;
procedure PrintFisherTable(AList: TStrings; A, B, C, D: integer; P, SumP: double);
procedure UpdateBtnStates;
public
{ public declarations }
end;
var
FisherFrm: TFisherFrm;
implementation
{ TFisherFrm }
procedure TFisherFrm.ResetBtnClick(Sender: TObject);
var
i: integer;
begin
VarList.Clear;
RowEdit.Text := '';
ColEdit.Text := '';
DepEdit.Text := '';
DepEdit.Visible := false;
DepIn.Visible := false;
DepOut.Visible := false;
NCasesLabel.Visible := false;
DepLabel.Visible := false;
NCasesEdit.Text := '';
NCasesEdit.Visible := false;
for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
Panel1.Visible := false;
Panel2.Visible := false;
RC11Edit.Text := '';
RC12Edit.Text := '';
RC21Edit.Text := '';
RC22Edit.Text := '';
UpdateBtnStates;
end;
procedure TFisherFrm.RowInClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (RowEdit.Text = '') then
begin
RowEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
end;
UpdateBtnStates;
end;
procedure TFisherFrm.RowOutClick(Sender: TObject);
begin
if RowEdit.Text <> '' then
begin
VarList.Items.Add(RowEdit.Text);
RowEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TFisherFrm.FormActivate(Sender: TObject);
var
w: Integer;
begin
if FAutoSized then
exit;
w := MaxValue([ResetBtn.Width, ComputeBtn.Width, CloseBtn.Width]);
ResetBtn.Constraints.MinWidth := w;
ComputeBtn.Constraints.MinWidth := w;
CloseBtn.Constraints.MinWidth := w;
Constraints.MinWidth := Width;
Constraints.MinHeight := Height;
FAutoSized := true;
end;
procedure TFisherFrm.FormCreate(Sender: TObject);
begin
Assert(OS3MainFrm <> nil);
if OutputFrm = nil then
Application.CreateForm(TOutputFrm, OutputFrm);
end;
procedure TFisherFrm.FormShow(Sender: TObject);
begin
ResetBtnClick(self);
end;
procedure TFisherFrm.ColInClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (ColEdit.Text = '') then
begin
ColEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
end;
UpdateBtnStates;
end;
procedure TFisherFrm.ColOutClick(Sender: TObject);
begin
if ColEdit.Text <> '' then
begin
VarList.Items.Add(ColEdit.Text);
ColEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TFisherFrm.ComputeBtnClick(Sender: TObject);
var
i, j, row, col, caserow, casecol, A, B, C, D, Largest: integer;
N, APlusB, APlusC, BPlusD, CPlusD, NoSelected, dep: integer;
FirstP, p, SumProb, Tocher, Alpha, X: double;
obs: array[1..2, 1..2] of integer;
ColNoSelected: IntDyneVec;
done, ok: boolean;
response: string;
lReport: TStrings;
begin
Randomize; // initialize random number generator
row := 0;
col := 0;
dep := 0;
// get column no.s of row and col variables
if InputGrp.ItemIndex <> 3 then
begin
for i := 1 to NoVariables do
begin
if RowEdit.Text = OS3MainFrm.DataGrid.Cells[i,0] then row := i;
if ColEdit.Text = OS3MainFrm.DataGrid.Cells[i,0] then col := i;
if InputGrp.ItemIndex = 2 then
begin
if DepEdit.Text = OS3MainFrm.DataGrid.Cells[i,0] then dep := i;
end;
end;
end;
SetLength(ColNoSelected,3);
ColNoSelected[0] := row;
ColNoSelected[1] := col;
if InputGrp.ItemIndex = 2 then
begin
ColNoSelected[2] := dep;
NoSelected := 3;
end else
NoSelected := 2;
//initialize observed matrix
for i := 1 to 2 do
for j := 1 to 2 do obs[i,j] := 0;
if InputGrp.ItemIndex = 3 then // get freq. from form
begin
if (RC11Edit.Text = '') or not TryStrToInt(RC11Edit.Text, obs[1, 1]) then
begin
RC11Edit.SetFocus;
MessageDlg('No valid input.', mtError, [mbOK], 0);
exit;
end;
if (RC12Edit.Text = '') or not TryStrToInt(RC12Edit.Text, obs[1, 2]) then
begin
RC12Edit.SetFocus;
MessageDlg('No valid input', mtError, [mbOK], 0);
exit;
end;
if (RC21Edit.Text = '') or not TryStrToInt(RC21Edit.Text, obs[2, 1]) then
begin
RC21Edit.SetFocus;
MessageDlg('No valid input.', mtError, [mbOK], 0);
exit;
end;
if (RC22Edit.Text = '') or not TryStrToInt(RC22Edit.Text, obs[2, 2]) then
begin
RC22Edit.SetFocus;
MessageDlg('No valid input', mtError, [mbOK], 0);
exit;
end;
end;
if InputGrp.ItemIndex = 0 then // count no. in row/col combinations
begin
for j := 1 to NoCases do
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
caserow := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[row,j])));
casecol := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[col,j])));
if (caserow > 2) or (caserow < 1) then
begin
MessageDlg('Row < 1 or > 2 found. Case ignored.', mtInformation, [mbOK], 0);
continue;
end;
if (casecol > 2) or (casecol < 1) then
begin
MessageDlg('Column < 1 or > 2 found. Case ignored.', mtInformation, [mbOK], 0);
continue;
end;
obs[caserow, casecol] := obs[caserow, casecol] + 1;
end;
end;
if (InputGrp.ItemIndex = 1) or (InputGrp.ItemIndex = 2) then // Grid has frequencies for row/col
begin
for j := 1 to NoCases do
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
caserow := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[row,j])));
casecol := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[col,j])));
if (caserow > 2) or (caserow < 1) then
begin
MessageDlg('Row < 1 or > 2 found. Case ignored.', mtInformation, [mbOk], 0);
continue;
end;
if (casecol > 2) or (casecol < 1) then
begin
MessageDlg('Column < 1 or > 2 found. Case ignored.', mtError, [mbOK], 0);
continue;
end;
obs[caserow, casecol] := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[dep,j])));
if InputGrp.ItemIndex = 2 then
obs[caserow,casecol] := obs[caserow,casecol] * StrToInt(NCasesEdit.Text);
end;
end;
//Find smallest value
A := obs[1, 1];
B := obs[1, 2];
C := obs[2, 1];
D := obs[2, 2];
APlusB := A + B;
CPlusD := C + D;
BPlusD := B + D;
APlusC := A + C;
N := A + B + C + D;
Largest := 1;
if (B > A) then largest := 2;
if ((B > A) and (B > C) and (B > D)) then Largest := 2;
if ((C > A) and (C > B) and (C > D)) then Largest := 3;
if ((D > A) and (D > B) and (D > C)) then Largest := 4;
// Ready for output
lReport := TStringList.Create;
try
lReport.Add('FISHER EXACT PROBABILITY TEST');
lReport.Add('');
//Get first probability
FirstP := combos(A, APlusC) * combos(B, BPlusD) / combos(APlusB, N);
SumProb := FirstP;
PrintFisherTable(lReport, A, B, C, D, FirstP, SumProb);
lReport.Add('');
//Get more extreme probabilities
done := false;
while (not done) do
begin
case Largest of
1: begin// top row, first col
if (A = APlusB) then
done := true
else
begin
inc(A);
dec(B);
dec(C);
inc(D);
end;
end;
2: begin// top row, second column
if (B = APlusB) then
done := true
else
begin
dec(A);
inc(B);
inc(C);
dec(D);
end;
end;
3: begin // second row, first column
if (C = CPlusD) then
done := true
else
begin
dec(A);
inc(B);
inc(C);
dec(D);
end;
end;
4: begin // second row, second column
if (D = CPlusD) then
done := true
else
begin
inc(A);
dec(B);
dec(C);
inc(D);
end;
end;
end; // end case
if (not done) then
begin
p := combos(A, APlusC) * combos(b, BPlusD) / combos(APlusB, N);
SumProb := SumProb + p;
PrintFisherTable(lReport, A, B, C, D, p, SumProb);
lReport.Add('');
end;
end;
//Tocher's modification
repeat
response := FormatFloat('0.00', DEFAULT_ALPHA_LEVEL);
ok := InputQuery('Alpha', 'Enter your Alpha level (Type I Error rate): ', response);
if not ok then
exit;
if TryStrToFloat(response, Alpha) then
break
else
MessageDlg('Not a valid number.', mtError, [mbOk], 0);
until false;
if ((SumProb - FirstP) > Alpha) then //Extreme values > alpha - accept null hypothesis
lReport.Add('Null hypothesis accepted.')
else
begin//Extreme values significant - is total probability significant?
if (SumProb >= Alpha) then //No, so apply Tocher's rule
begin
Tocher := ( Alpha - (SumProb - FirstP)) / FirstP;
X := random(1000) / 1000.0; //Select a random value between 0 and num - 1
lReport.Add('Tocher ratio computed: %5.3f', [Tocher]);
if (X < Tocher) then //Call it significant
begin
lReport.Add('A random value of %5.3f selected was less than the Tocher value.', [X]);
lReport.Add('Conclusion: Reject the null Hypothesis');
end else
begin //Call it non-significant
lReport.Add('A random value of %5.3f selected was greater than the Tocher value.', [X]);
lReport.Add('Conclusion: Accept the null Hypothesis');
end;
end else
begin //Total probability < alpha - reject null
lReport.Add('Probability less than alpha - reject null hypothesis.');
end; // end if-else
end; // end if-else
OutputFrm.Clear;
OutputFrm.AddLines(lReport);
OutputFrm.ShowModal;
finally
lReport.Free;
end;
end;
procedure TFisherFrm.DepInClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (DepEdit.Text = '') then
begin
DepEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
end;
UpdateBtnStates;
end;
procedure TFisherFrm.DepOutClick(Sender: TObject);
begin
if DepEdit.Text <> '' then
begin
VarList.Items.Add(DepEdit.Text);
DepEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TFisherFrm.InputGrpClick(Sender: TObject);
begin
if InputGrp.ItemIndex = 3 then
begin
Panel2.Visible := true;
Panel1.Visible := false;
RC11Edit.SetFocus;
end else
begin
Panel1.Visible := true;
Panel2.Visible := false;
ColIn.Enabled := true;
ColOut.Enabled := false;
if InputGrp.ItemIndex = 2 then
begin
NCasesLabel.Visible := true;
NCasesEdit.Visible := true;
end
else begin
NCasesLabel.Visible := false;
NCasesEdit.Visible := false;
end;
if InputGrp.ItemIndex = 0 then
begin
DepLabel.Visible := false;
DepEdit.Visible := false;
DepIn.Visible := false;
DepOut.Visible := false;
end
else begin // InputGrp = 1
DepLabel.Visible := true;
DepEdit.Visible := true;
DepIn.Visible := true;
DepOut.Visible := true;
DepIn.Enabled := true;
DepOut.Enabled := false;
end;
end;
end;
procedure TFisherFrm.RC11EditKeyPress(Sender: TObject; var Key: char);
begin
if Key = #13 then RC12Edit.SetFocus;
end;
procedure TFisherFrm.RC12EditKeyPress(Sender: TObject; var Key: char);
begin
if Key = #13 then RC21Edit.SetFocus;
end;
procedure TFisherFrm.RC21EditKeyPress(Sender: TObject; var Key: char);
begin
if Key = #13 then RC22Edit.SetFocus;
end;
procedure TFisherFrm.RC22EditKeyPress(Sender: TObject; var Key: char);
begin
if Key = #13 then ComputeBtn.SetFocus;
end;
procedure TFisherFrm.PrintFisherTable(AList: TStrings;
A, B, C, D: integer; P, SumP: double);
begin
AList.Add('Contingency Table for Fisher Exact Test');
AList.Add(' Column');
AList.Add('Row 1 2');
AList.Add(' 1 %10d %10d', [A, B]);
AList.Add(' 2 %10d %10d', [C, D]);
AList.Add('');
AList.Add('Probability = %6.4f', [P]);
AList.Add('Cumulative Probability = %6.4f', [SumP]);
AList.Add('');
end;
procedure TFisherFrm.UpdateBtnStates;
begin
RowIn.Enabled := VarList.ItemIndex > -1;
ColIn.Enabled := VarList.ItemIndex > -1;
DepIn.Enabled := VarList.ItemIndex > -1;
RowOut.Enabled := RowEdit.Text <> '';
ColOut.Enabled := ColEdit.Text <> '';
DepOut.Enabled := DepEdit.Text <> '';
end;
initialization
{$I exactunit.lrs}
end.