unit ExactUnit; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, Buttons, MainUnit, FunctionsLib, Globals, BasicStatsReportFormUnit; type { TFisherForm } TFisherForm = class(TBasicStatsReportForm) AlphaEdit: TEdit; Label2: TLabel; Label5: TLabel; Notebook: TNotebook; GridDataPage: TPage; TableDataPage: TPage; 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; procedure ColInClick(Sender: TObject); procedure ColOutClick(Sender: TObject); procedure DepInClick(Sender: TObject); procedure DepOutClick(Sender: TObject); procedure InputGrpClick(Sender: TObject); procedure RowInClick(Sender: TObject); procedure RowOutClick(Sender: TObject); procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); private procedure PrintFisherTable(AList: TStrings; A, B, C, D: integer; P, SumP: double); protected procedure AdjustConstraints; override; procedure Compute; override; procedure UpdateBtnStates; override; function Validate(out AMsg: String; out AControl: TWinControl): boolean; override; public procedure Reset; override; end; var FisherForm: TFisherForm; implementation {$R *.lfm} uses Utils, GridProcs; { TFisherForm } procedure TFisherForm.AdjustConstraints; begin inherited; ParamsPanel.Constraints.MinWidth := InputGrp.Width; ParamsPanel.Constraints.MinHeight := InputGrp.Top + InputGrp.Height + NCasesEdit.Top + NCasesEdit.Height + Notebook.BorderSpacing.Bottom + AlphaEdit.Height + ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height; end; procedure TFisherForm.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 TFisherForm.ColOutClick(Sender: TObject); begin if ColEdit.Text <> '' then begin VarList.Items.Add(ColEdit.Text); ColEdit.Text := ''; end; UpdateBtnStates; end; procedure TFisherForm.Compute; 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 = nil; done: boolean; lReport: TStrings; begin Randomize; // initialize random number generator // get column no.s of row and col variables if InputGrp.ItemIndex <> 3 then begin row := GetVariableIndex(OS3MainFrm.DataGrid, RowEdit.Text); col := GetVariableIndex(OS3MainFrm.DataGrid, ColEdit.Text); if InputGrp.ItemIndex = 2 then dep := GetVariableIndex(OS3MainFrm.DataGrid, DepEdit.Text); 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; SetLength(ColNoSelected, noSelected); // Initialize observed matrix for i := 1 to 2 do for j := 1 to 2 do obs[i, j] := 0; // Count no. in row/col combinations if InputGrp.ItemIndex = 0 then begin for j := 1 to NoCases do begin if (not GoodRecord(OS3MainFrm.DataGrid, j, 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 ErrorMsg('Row < 1 or > 2 found. Case ignored.'); continue; end; if (caseCol > 2) or (caseCol < 1) then begin ErrorMsg('Column < 1 or > 2 found. Case ignored.'); continue; end; obs[caseRow, caseCol] := obs[caseRow, caseCol] + 1; end; end; // Grid has frequencies for row/col if (InputGrp.ItemIndex = 1) or (InputGrp.ItemIndex = 2) then begin for j := 1 to NoCases do begin if (not GoodRecord(OS3MainFrm.DataGrid, j, 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 ErrorMsg('Row < 1 or > 2 found. Case ignored.'); continue; end; if (caseCol > 2) or (caseCol < 1) then begin ErrorMsg('Column < 1 or > 2 found. Case ignored.'); 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; // Get freq. from form if InputGrp.ItemIndex = 3 then begin obs[1, 1] := StrToInt(RC11Edit.Text); obs[1, 2] := StrToInt(RC12Edit.Text); obs[2, 1] := StrToInt(RC21Edit.Text); obs[2, 2] := StrToInt(RC22Edit.Text); 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 alpha := StrToFloat(AlphaEdit.Text); // Extreme values > alpha - accept null hypothesis if ((sumProb - FirstP) > alpha) then 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 // wp: why not simply X := random() 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(''); 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(''); 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 FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TFisherForm.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 TFisherForm.DepOutClick(Sender: TObject); begin if DepEdit.Text <> '' then begin VarList.Items.Add(DepEdit.Text); DepEdit.Text := ''; end; UpdateBtnStates; end; procedure TFisherForm.InputGrpClick(Sender: TObject); begin if InputGrp.ItemIndex = 3 then begin Notebook.PageIndex := 1; // Panel2.Visible := true; // Panel1.Visible := false; //RC11Edit.SetFocus; // -1) and (RowEdit.Text = '') then begin RowEdit.Text := VarList.Items[index]; VarList.Items.Delete(index); end; UpdateBtnStates; end; procedure TFisherForm.RowOutClick(Sender: TObject); begin if RowEdit.Text <> '' then begin VarList.Items.Add(RowEdit.Text); RowEdit.Text := ''; end; UpdateBtnStates; end; function TFisherForm.Validate(out AMsg: String; out AControl: TWinControl): boolean; var x: Double; n: Integer; begin Result := false; if InputGrp.ItemIndex = 3 then begin if (RC11Edit.Text = '') or not TryStrToInt(RC11Edit.Text, n) then begin AControl := RC11Edit; AMsg := 'No valid input.'; exit; end; if (RC12Edit.Text = '') or not TryStrToInt(RC12Edit.Text, n) then begin AControl := RC12Edit; AMsg := 'No valid input'; exit; end; if (RC21Edit.Text = '') or not TryStrToInt(RC21Edit.Text, n) then begin AControl := RC21Edit; AMsg := 'No valid input.'; exit; end; if (RC22Edit.Text = '') or not TryStrToInt(RC22Edit.Text, n) then begin AControl := RC22Edit; AMsg := 'No valid input'; exit; end; end; if AlphaEdit.Text = '' then begin AMsg := 'Input required.'; AControl := AlphaEdit; exit; end; if not TryStrToFloat(AlphaEdit.Text, x) or (x < 0) or (x > 1) then begin AMsg := 'Numerical value between 0 and 1 required.'; AControl := AlphaEdit; exit; end; Result := true; end; procedure TFisherForm.VarListDblClick(Sender: TObject); var index: Integer; s: String; begin index := VarList.ItemIndex; if index > -1 then begin s := VarList.Items[index]; if RowEdit.Text = '' then RowEdit.Text := s else if ColEdit.Text = '' then ColEdit.Text := s else if (DepEdit.Text = '') and (InputGrp.ItemIndex in [1, 2]) then DepEdit.Text := s; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TFisherForm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; procedure TFisherForm.UpdateBtnStates; begin inherited; 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; end.