Files
lazarus-ccr/applications/lazstats/source/forms/analysis/nonparametric/chisqrunit.pas

671 lines
20 KiB
ObjectPascal
Raw Normal View History

unit ChiSqrUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
ExtCtrls, StdCtrls, Buttons,
MainUnit, FunctionsLib, GraphLib, Globals, MatrixLib, DataProcs,
DictionaryUnit, BasicStatsReportFormUnit;
type
{ TChiSqrFrm }
TChiSqrFrm = class(TBasicStatsReportForm)
ObsChk: TCheckBox;
ExpChk: TCheckBox;
PropsChk: TCheckBox;
CellChiChk: TCheckBox;
SaveFChk: TCheckBox;
OptionsGroup: TGroupBox;
YatesChk: TCheckBox;
RowIn: TBitBtn;
RowOut: TBitBtn;
ColIn: TBitBtn;
ColOut: TBitBtn;
DepIn: TBitBtn;
DepOut: TBitBtn;
NCasesEdit: TEdit;
NCasesLabel: TLabel;
RowEdit: TEdit;
ColEdit: TEdit;
DepEdit: TEdit;
InputGrp: TRadioGroup;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
AnalyzeLabel: 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; User: boolean);
private
protected
procedure AdjustConstraints; override;
procedure Compute; override;
procedure UpdateBtnStates; override;
function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override;
public
constructor Create(AOwner: TComponent); override;
procedure Reset; override;
end;
var
ChiSqrFrm: TChiSqrFrm;
implementation
{$R *.lfm}
uses
Math;
{ TChiSqrFrm }
constructor TChiSqrFrm.Create(AOwner: TComponent);
begin
inherited;
if DictionaryFrm = nil then
Application.CreateForm(TDictionaryFrm, DictionaryFrm);
end;
procedure TChiSqrFrm.AdjustConstraints;
begin
inherited;
ParamsPanel.Constraints.MinWidth := MaxValue([
4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left,
OptionsGroup.Width,
InputGrp.Width
]);
ParamsPanel.Constraints.MinHeight := NCasesEdit.Top + NCasesEdit.Height +
OptionsGroup.BorderSpacing.Top + OptionsGroup.Height +
ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height;
end;
procedure TChiSqrFrm.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 TChiSqrFrm.ColOutClick(Sender: TObject);
begin
if ColEdit.Text <> '' then
begin
VarList.Items.Add(ColEdit.Text);
ColEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TChiSqrFrm.Compute;
var
ColNoSelected: IntDyneVec = nil;
Freq: IntDyneMat = nil;
Prop: DblDyneMat = nil;
Expected: DblDyneMat = nil;
CellChi: DblDyneMat = nil;
i, j, RowNo, ColNo, DepNo, MinRow, MaxRow, MinCol, MaxCol : integer;
Row, Col, NoSelected, Ncases, Nrows, Ncols, FObs, df : integer;
RowLabels, ColLabels : StrDyneVec;
cellstring: string;
PObs, ChiSquare, ProbChi, phi, SumX, SumY, VarX, VarY, liklihood : double;
yates : boolean;
title : string;
Adjchisqr, probliklihood, G, pearsonr, MantelHaenszel, MHprob : double;
Adjprobchi, CoefCont, CramerV : double;
lReport: TStrings;
begin
SetLength(ColNoSelected, NoVariables);
yates := false;
RowNo := 0;
ColNo := 0;
DepNo := 0;
for i := 1 to NoVariables do
begin
cellstring := OS3MainFrm.DataGrid.Cells[i,0];
if cellstring = RowEdit.Text then RowNo := i;
if cellstring = ColEdit.Text then ColNo := i;
if cellstring = DepEdit.Text then DepNo := i;
end;
ColNoSelected[0] := RowNo;
ColNoSelected[1] := ColNo;
NoSelected := 2;
if InputGrp.ItemIndex > 0 then // for reading proportions or frequencies
begin
NoSelected := 3;
ColNoSelected[2] := DepNo;
end;
// get min and max of row and col numbers
MinRow := 1000;
MaxRow := 0;
MinCol := 1000;
MaxCol := 0;
for i := 1 to NoCases do
begin
if not GoodRecord(i,NoSelected,ColNoSelected) then continue;
Row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[RowNo,i])));
Col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNo,i])));
if Row > MaxRow then MaxRow := Row;
if Row < MinRow then MinRow := Row;
if Col > MaxCol then MaxCol := Col;
if Col < MinCol then MinCol := Col;
end;
Nrows := MaxRow - MinRow + 1;
Ncols := MaxCol - MinCol + 1;
// allocate and initialize
SetLength(Freq,Nrows+1,Ncols+1);
SetLength(Prop,Nrows+1,Ncols+1);
SetLength(Expected,Nrows,Ncols);
SetLength(CellChi,Nrows,Ncols);
SetLength(RowLabels,Nrows+1);
SetLength(ColLabels,Ncols+1);
for i := 1 to Nrows + 1 do
for j := 1 to Ncols + 1 do Freq[i-1,j-1] := 0;
// get cell data
NCases := 0;
case InputGrp.ItemIndex of
0 : begin // count number of cases in each row and column combination
for i := 1 to NoCases do
begin
if not GoodRecord(i,NoSelected,ColNoSelected) then continue;
NCases := NCases + 1;
Row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[RowNo,i])));
Col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNo,i])));
Row := Row - MinRow + 1;
Col := Col - MinCol + 1;
Freq[Row-1,Col-1] := Freq[Row-1,Col-1] + 1;
end;
end;
1 : begin // read frequencies data from grid
for i := 1 to NoCases do
begin
if not GoodRecord(i,NoSelected,ColNoSelected) then continue;
Row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[RowNo,i])));
Col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNo,i])));
Row := Row - MinRow + 1;
Col := Col - MinCol + 1;
FObs := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[DepNo,i])));
Freq[Row-1,Col-1] := Freq[Row-1,Col-1] + FObs;
NCases := NCases + FObs;
end;
end;
2 : begin // get no. of cases and proportions for each cell
NCases := StrToInt(NCasesEdit.Text);
for i := 1 to NoCases do
begin
if not GoodRecord(i,NoSelected,ColNoSelected) then continue;
Row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[RowNo,i])));
Col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNo,i])));
Row := Row - MinRow + 1;
Col := Col - MinCol + 1;
PObs := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[DepNo,i]));
Freq[Row-1,Col-1] := Freq[Row-1,Col-1] + round(PObs * NCases);
end;
end;
end; // end case
Freq[Nrows,Ncols] := NCases;
// Now, calculate expected values
// Get row totals first
for i := 1 to Nrows do
for j := 1 to Ncols do
Freq[i-1,Ncols] := Freq[i-1,Ncols] + Freq[i-1,j-1];
// Get col totals next
for j := 1 to Ncols do
for i := 1 to Nrows do
Freq[Nrows,j-1] := Freq[Nrows,j-1] + Freq[i-1,j-1];
// Then get expected values and cell chi-squares
ChiSquare := 0.0;
AdjChisqr := 0.0;
if (YatesChk.Checked) and (Nrows = 2) and (Ncols = 2) then yates := true;
for i := 1 to Nrows do
begin
for j := 1 to Ncols do
begin
Expected[i-1,j-1] := Freq[Nrows,j-1] * Freq[i-1,Ncols] / NCases;
if Expected[i-1,j-1] > 0.0 then
CellChi[i-1,j-1] := sqr(Freq[i-1,j-1] - Expected[i-1,j-1]) / Expected[i-1,j-1]
else begin
MessageDlg('Zero expected value found.', mtError, [mbOK], 0);
CellChi[i-1,j-1] := 0.0;
end;
ChiSquare := ChiSquare + CellChi[i-1,j-1];
end;
end;
df := (Nrows - 1) * (Ncols - 1);
if yates = true then // 2 x 2 corrected chi-square
begin
Adjchisqr := abs((Freq[0,0] * Freq[1,1]) - (Freq[0,1] * Freq[1,0]));
Adjchisqr := sqr(Adjchisqr - NCases / 2.0) * NCases; // numerator
Adjchisqr := Adjchisqr / (Freq[0,2] * Freq[1,2] * Freq[2,0] * Freq[2,1]);
Adjprobchi := 1.0 - chisquaredprob(Adjchisqr,df);
end;
ProbChi := 1.0 - chisquaredprob(ChiSquare,df); // prob. larger chi
//Print results to output form
lReport := TStringList.Create;
try
lReport.Add('CHI-SQUARE ANALYSIS RESULTS');
// print tables requested by use
for i := 1 to Nrows do RowLabels[i-1] := Format('Row %d', [i]);
RowLabels[Nrows] := 'Total';
for j := 1 to Ncols do ColLabels[j-1] := Format('COL.%d', [j]);
ColLabels[Ncols] := 'Total';
if ObsChk.Checked then
begin
IntArrayPrint(Freq, Nrows+1, Ncols+1,'Rows',
RowLabels, ColLabels,'OBSERVED FREQUENCIES', lReport);
lReport.Add('------------------------------------------------------------------------------');
lReport.Add('');
end;
if ExpChk.Checked then
begin
title := 'EXPECTED FREQUENCIES';
MatPrint(Expected,Nrows,Ncols,title,RowLabels,ColLabels,NCases, lReport);
lReport.Add('------------------------------------------------------------------------------');
lReport.Add('');
end;
if PropsChk.Checked then
begin
title := 'ROW PROPORTIONS';
for i := 1 to Nrows + 1 do
begin
for j := 1 to Ncols do
begin
if Freq[i-1,Ncols] > 0.0 then
Prop[i-1,j-1] := Freq[i-1,j-1] / Freq[i-1,Ncols]
else Prop[i-1,j-1] := 0.0;
end;
if Freq[i-1,Ncols] > 0.0 then Prop[i-1,Ncols] := 1.0
else Prop[i-1,Ncols] := 0.0;
end;
MatPrint(Prop,Nrows+1,Ncols+1,title,RowLabels,ColLabels,NCases, lReport);
lReport.Add('------------------------------------------------------------------------------');
lReport.Add('');
title := 'COLUMN PROPORTIONS';
for j := 1 to Ncols + 1 do
begin
for i := 1 to Nrows do
begin
if Freq[Nrows,j-1] > 0.0 then
Prop[i-1,j-1] := Freq[i-1,j-1] / Freq[Nrows,j-1]
else Prop[i-1,j-1] := 0.0;
end;
if Freq[Nrows,j-1] > 0.0 then Prop[NRows,j-1] := 1.0
else Prop[NRows,j-1] := 0.0;
end;
MatPrint(Prop,Nrows+1,Ncols+1,title,RowLabels,ColLabels,NCases, lReport);
lReport.Add('------------------------------------------------------------------------------');
lReport.Add('');
Title := 'PROPORTIONS OF TOTAL N';
for i := 1 to Nrows + 1 do
for j := 1 to Ncols + 1 do Prop[i-1,j-1] := Freq[i-1,j-1] / NCases;
Prop[Nrows,Ncols] := 1.0;
MatPrint(Prop,Nrows+1,Ncols+1,title,RowLabels,ColLabels,NCases, lReport);
lReport.Add('------------------------------------------------------------------------------');
lReport.Add('');
end;
if CellChiChk.Checked then
begin
title := 'CHI-SQUARED VALUE FOR CELLS';
MatPrint(CellChi,Nrows,Ncols,title,RowLabels,ColLabels,NCases, lReport);
lReport.Add('------------------------------------------------------------------------------');
lReport.Add('');
end;
lReport.Add('Chi-square: %.3f with D.F. = %d. Prob. > value %.3f', [ChiSquare, df, ProbChi]);
lReport.Add('');
if yates then
lReport.Add('Chi-square using Yates correction: %.3f and Prob > value: %.3f', [Adjchisqr, Adjprobchi]);
liklihood := 0.0;
for i := 0 to Nrows-1 do
for j := 0 to Ncols-1 do
if (Freq[i,j] > 0.0) then
liklihood := Liklihood + (Freq[i,j] * (ln(Expected[i,j] / Freq[i,j])));
liklihood := -2.0 * liklihood;
probliklihood := 1.0 - chisquaredprob(liklihood,df);
lReport.Add('Likelihood Ratio: %.3f with prob. > value %.4f', [liklihood, probliklihood]);
G := 0.0;
for i := 0 to Nrows-1 do
for j := 0 to Ncols-1 do
if (Expected[i,j] > 0) then
G := G + Freq[i,j] * (ln(Freq[i,j] / Expected[i,j]));
G := 2.0 * G;
probliklihood := 1.0 - chisquaredprob(G,df);
lReport.Add('G statistic: %.3f with prob. > value %.4f', [G, probliklihood]);
if ((Nrows > 1) and (Ncols > 1)) then
begin
phi := sqrt(ChiSquare / Ncases);
lReport.Add('phi correlation: %.4f', [phi]);
lReport.Add('');
pearsonr := 0.0;
SumX := 0.0;
SumY := 0.0;
VarX := 0.0;
VarY := 0.0;
for i := 0 to Nrows-1 do SumX := SumX + ( (i+1) * Freq[i,Ncols] );
for j := 0 to Ncols-1 do SumY := SumY + ( (j+1) * Freq[Nrows,j] );
for i := 0 to Nrows-1 do VarX := VarX + ( ((i+1)*(i+1)) * Freq[i,Ncols] );
for j := 0 to Ncols-1 do VarY := VarY + ( ((j+1)*(j+1)) * Freq[Nrows,j] );
VarX := VarX - ((SumX * SumX) / Ncases);
VarY := VarY - ((SumY * SumY) / Ncases);
for i := 0 to Nrows-1 do
for j := 0 to Ncols-1 do
pearsonr := pearsonr + ((i+1)*(j+1) * Freq[i,j]);
pearsonr := pearsonr - (SumX * SumY / Ncases);
pearsonr := pearsonr / sqrt(VarX * VarY);
lReport.Add('Pearson Correlation r: %.4f', [pearsonr]);
lReport.Add('');
MantelHaenszel := (Ncases-1) * (pearsonr * pearsonr);
MHprob := 1.0 - chisquaredprob(MantelHaenszel,1);
lReport.Add('Mantel-Haenszel Test of Linear Association: %.3f with probability > value %.4f', [MantelHaenszel, MHprob]);
lReport.Add('');
CoefCont := sqrt(ChiSquare / (ChiSquare + Ncases));
lReport.Add('The coefficient of contingency: %.3f', [CoefCont]);
lReport.Add('');
if (Nrows < Ncols) then
CramerV := sqrt(ChiSquare / (Ncases * ((Nrows-1))))
else
CramerV := sqrt(ChiSquare / (Ncases * ((Ncols-1))));
lReport.Add('Cramers V: %.3f', [CramerV]);
lReport.Add('');
end;
FReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
// save frequency data file if elected
if SaveFChk.Checked then
begin
OS3MainFrm.mnuFileCloseClick(self);
OS3MainFrm.FileNameEdit.Text := '';
for i := 1 to DictionaryFrm.DictGrid.RowCount - 1 do
for j := 0 to 7 do DictionaryFrm.DictGrid.Cells[j,i] := '';
DictionaryFrm.DictGrid.RowCount := 1;
// DictionaryFrm.FileNameEdit.Text := '';
// get labels for new file
ColLabels[0] := 'ROW';
ColLabels[1] := 'COL';
ColLabels[2] := 'FREQ';
// create new variables
Row := 0;
OS3MainFrm.DataGrid.ColCount := 4;
DictionaryFrm.DictGrid.ColCount := 8;
NoVariables := 0;
for i := 1 to 3 do
begin
col := NoVariables + 1;
DictionaryFrm.NewVar(col);
DictionaryFrm.DictGrid.Cells[1,col] := ColLabels[i-1];
OS3MainFrm.DataGrid.Cells[col,0] := ColLabels[i-1];
NoVariables := NoVariables + 1;
end;
OS3MainFrm.DataGrid.RowCount := (Nrows * NCols) + 1;
for i := 1 to Nrows do
begin
for j := 1 to Ncols do
begin
Row := Row + 1;
OS3MainFrm.DataGrid.Cells[0,Row] := Format('Case:%d',[Row]);
OS3MainFrm.DataGrid.Cells[1,Row] := IntToStr(i);
OS3MainFrm.DataGrid.Cells[2,Row] := IntToStr(j);
OS3MainFrm.DataGrid.Cells[3,Row] := IntToStr(Freq[i-1,j-1]);
end;
end;
NoCases := Row;
OS3MainFrm.FileNameEdit.Text := 'ChiSqrFreq.LAZ';
OS3MainFrm.NoCasesEdit.Text := IntToStr(NoCases);
OS3MainFrm.NoVarsEdit.Text := IntToStr(NoVariables);
// OS3MainFrm.SaveFileBtnClick(self);
end;
//clean up
ColLabels := nil;
RowLabels := nil;
CellChi := nil;
Expected := nil;
Prop := nil;
Freq := nil;
ColNoSelected := nil;
end;
procedure TChiSqrFrm.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 TChiSqrFrm.DepOutClick(Sender: TObject);
begin
if DepEdit.Text <> '' then
begin
VarList.Items.Add(DepEdit.Text);
DepEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TChiSqrFrm.InputGrpClick(Sender: TObject);
begin
case InputGrp.ItemIndex of
0: begin // have to count cases in each row and col. combination
NCasesLabel.Enabled := false;
NCasesEdit.Enabled := false;
DepEdit.Enabled := false;
end;
1: begin // frequencies available for each row and column combo
NCasesLabel.Enabled := false;
NCasesEdit.Enabled := false;
AnalyzeLabel.Enabled := true;
end;
2: begin // only proportions available - get N size
NCasesLabel.Enabled := true;
AnalyzeLabel.Enabled := true;
NCasesEdit.Enabled := true;
NCasesEdit.SetFocus;
DepEdit.Enabled := true;
end;
end;
UpdateBtnStates;
end;
procedure TChiSqrFrm.Reset;
var
i: integer;
begin
inherited;
InputGrp.ItemIndex := 0;
VarList.Clear;
for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
RowEdit.Clear;
ColEdit.Clear;
DepEdit.Clear;
AnalyzeLabel.Enabled := false;
NCasesLabel.Enabled := false;
NCasesEdit.Text := '';
NCasesEdit.Enabled := false;
ObsChk.Checked := false;
ExpChk.Checked := false;
PropsChk.Checked := false;
CellChiChk.Checked := false;
SaveFChk.Checked := false;
UpdateBtnStates;
end;
procedure TChiSqrFrm.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 TChiSqrFrm.RowOutClick(Sender: TObject);
begin
if RowEdit.Text <> '' then
begin
VarList.Items.Add(RowEdit.Text);
RowEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TChiSqrFrm.UpdateBtnStates;
begin
inherited;
RowIn.Enabled := (VarList.Items.Count > 0) and (RowEdit.Text = '');
ColIn.Enabled := (VarList.Items.Count > 0) and (ColEdit.Text = '');
DepIn.Enabled := (VarList.Items.Count > 0) and (DepEdit.Text = '') and (InputGrp.ItemIndex > 0);
RowOut.Enabled := (RowEdit.Text <> '');
ColOut.Enabled := (ColEdit.Text <> '');
DepOut.Enabled := (DepEdit.Text <> '') and (InputGrp.ItemIndex > 0);
end;
function TChiSqrFrm.Validate(out AMsg: String; out AControl: TWinControl): Boolean;
var
n: Integer;
begin
Result := false;
if RowEdit.Text = '' then
begin
AMsg := 'Row variable not selected.';
AControl := RowEdit;
exit;
end;
if ColEdit.Text = '' then
begin
AMsg := 'Column variable not selected.';
AControl := ColEdit;
exit;
end;
if (DepEdit.Text = '') and (InputGrp.ItemIndex > 0) then
begin
AMsg := 'Variable to analyze is not selected';
AControl := DepEdit;
exit;
end;
if InputGrp.ItemIndex = 2 then
begin
if NCasesEdit.Text = '' then
begin
AControl := NCasesEdit;
AMsg := 'Total number of cases not selected.';
exit;
end;
if not TryStrToInt(NCasesEdit.Text, n) then
begin
AControl := NCasesEdit;
AMsg := 'Numberical input expected for total number of cases.';
exit;
end;
end;
Result := True;
end;
procedure TChiSqrFrm.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 > 0) then
DepEdit.Text := s;
VarList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TChiSqrFrm.VarListSelectionChange(Sender: TObject; User: boolean);
begin
UpdateBtnStates;
end;
end.