unit ChiSqrUnit; {$mode objfpc}{$H+} {$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, Buttons, ComCtrls, MainUnit, FunctionsLib, GraphLib, Globals, MatrixLib, DictionaryUnit, ReportFrameUnit, BasicStatsReportFormUnit; type { TChiSqrForm } TChiSqrForm = class(TBasicStatsReportForm) ObsChk: TCheckBox; ExpChk: TCheckBox; PageControl: TPageControl; PropsChk: TCheckBox; CellChiChk: TCheckBox; SaveFChk: TCheckBox; OptionsGroup: TGroupBox; ResultsPage: TTabSheet; FrequenciesPage: TTabSheet; RowColPage: TTabSheet; CellChiSqrPage: TTabSheet; 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; 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 FFrequenciesReportFrame: TReportFrame; FRowColPropsReportFrame: TReportFrame; FCellChiSqrReportFrame: TReportFrame; function CalcGStatistic(const AFrequencies: IntDyneMat; const AExpected: DblDyneMat): Double; function CalcLikelihoodRatio(const AFrequencies: IntDyneMat; const AExpected: DblDyneMat): Double; function CalcPearsonR(const AFrequencies: IntDyneMat): Double; procedure FrequenciesToGrid(const AFrequencies: IntDyneMat); procedure GetExpectedAndCellChiSqr(const AFrequencies: IntDyneMat; out AExpected, ACellChiSqr: DblDyneMat; out AChiSqr: Double); procedure GetFrequencies(const AColNoSelected: IntDyneVec; ARowIndex, AColIndex, ADepIndex: Integer; out ANumRows, ANumCols, ANumCases: Integer; out AFrequencies: IntDyneMat); procedure GetColProportions(const AFrequencies: IntDyneMat; out AProportions: DblDyneMat); procedure GetRowProportions(const AFrequencies: IntDyneMat; out AProportions: DblDyneMat); procedure GetTotalProportions(const AFrequencies: IntDyneMat; out AProportions: DblDyneMat); procedure GetYatesCorrection(const AFrequencies: IntDyneMat; out AdjChiSqr: Double); procedure ProcessAndReportCellChiSqr(const ACellChiSqr: DblDyneMat; const ARowLabels, AColLabels: StrDyneVec; ANumCases: Integer); procedure ProcessAndReportFrequencies(const AFrequencies: IntDyneMat; const AExpected: DblDyneMat; const ARowLabels, AColLabels: StrDyneVec); procedure ProcessAndReportProportions(const AFrequencies: IntDyneMat; const ARowLabels, AColLabels: StrDyneVec); 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 ChiSqrForm: TChiSqrForm; implementation {$R *.lfm} uses Math, Utils, GridProcs, MatrixUnit; { TChiSqrForm } constructor TChiSqrForm.Create(AOwner: TComponent); begin inherited; if DictionaryFrm = nil then Application.CreateForm(TDictionaryFrm, DictionaryFrm); FReportFrame.Parent := ResultsPage; FReportFrame.BorderSpacing.Left := 0; FReportFrame.BorderSpacing.Top := 0; FReportFrame.BorderSpacing.Bottom := 0; FReportFrame.BorderSpacing.Right := 0; InitToolbar(FReportFrame.ReportToolbar, tpRight); FFrequenciesReportFrame := TReportFrame.Create(self); FFrequenciesReportFrame.Name := ''; FFrequenciesReportFrame.Parent := FrequenciesPage; FFrequenciesReportFrame.Align := alClient; FFrequenciesReportFrame.BorderSpacing.Left := 0; FFrequenciesReportFrame.BorderSpacing.Top := 0; FFrequenciesReportFrame.BorderSpacing.Bottom := 0; FFrequenciesReportFrame.BorderSpacing.Right := 0; InitToolbar(FFrequenciesReportFrame.ReportToolbar, tpRight); FRowColPropsReportFrame := TReportFrame.Create(self); FRowColPropsReportFrame.Name := ''; FRowColPropsReportFrame.Parent := RowColPage; FRowColPropsReportFrame.Align := alClient; FRowColPropsReportFrame.BorderSpacing.Left := 0; FRowColPropsReportFrame.BorderSpacing.Top := 0; FRowColPropsReportFrame.BorderSpacing.Bottom := 0; FRowColPropsReportFrame.BorderSpacing.Right := 0; InitToolbar(FRowColPropsReportFrame.ReportToolbar, tpRight); FCellChiSqrReportFrame := TReportFrame.Create(self); FCellChiSqrReportFrame.Name := ''; FCellChiSqrReportFrame.Parent := CellChiSqrPage; FCellChiSqrReportFrame.Align := alClient; FCellChiSqrReportFrame.BorderSpacing.Left := 0; FCellChiSqrReportFrame.BorderSpacing.Top := 0; FCellChiSqrReportFrame.BorderSpacing.Bottom := 0; FCellChiSqrReportFrame.BorderSpacing.Right := 0; InitToolbar(FCellChiSqrReportFrame.ReportToolbar, tpRight); PageControl.ActivePageIndex := 0; end; procedure TChiSqrForm.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; function TChiSqrForm.CalcGStatistic(const AFrequencies: IntDyneMat; const AExpected: DblDyneMat): Double; var numRows, numCols: Integer; i, j: Integer; begin MatSize(AExpected, numRows, numCols); Result := 0.0; for i := 0 to numRows-1 do for j := 0 to numCols-1 do if (AExpected[i, j] > 0) then Result := Result + AFrequencies[i, j] * ln(AFrequencies[i, j] / AExpected[i, j]); Result := 2.0 * Result; end; function TChiSqrForm.CalcLikelihoodRatio(const AFrequencies: IntDyneMat; const AExpected: DblDyneMat): Double; var numRows, numCols: Integer; i, j: Integer; begin MatSize(AExpected, numRows, numCols); Result := 0.0; for i := 0 to numRows-1 do for j := 0 to numCols-1 do if (AFrequencies[i, j] > 0.0) then Result := Result + AFrequencies[i, j] * ln(AExpected[i, j] / AFrequencies[i, j]); Result := -2.0 * Result; end; function TChiSqrForm.CalcPearsonR(const AFrequencies: IntDyneMat): Double; var numRows, numCols, numCases: Integer; sumX, sumY: Double; varX, varY: Double; i, j: Integer; begin MatSize(AFrequencies, numRows, numCols); dec(numRows); // Do not iterate into the totals row and column dec(numCols); numCases := AFrequencies[numRows, numCols]; SumX := 0; SumY := 0; VarX := 0; VarY := 0; for i := 0 to numRows-1 do sumX := sumX + ( (i+1) * AFrequencies[i, numCols] ); for j := 0 to numCols-1 do sumY := sumY + ( (j+1) * AFrequencies[numRows, j] ); for i := 0 to numRows-1 do varX := varX + ( sqr(i+1) * AFrequencies[i, numCols] ); for j := 0 to numCols-1 do varY := varY + ( sqr(j+1) * AFrequencies[numRows, j] ); varX := varX - sqr(sumX) / numCases; varY := varY - sqr(sumY) / numCases; Result := 0; for i := 0 to numRows-1 do for j := 0 to numCols-1 do Result := Result + ((i+1)*(j+1) * AFrequencies[i, j]); Result := Result - (sumX * sumY / numCases); Result := Result / sqrt(varX * varY); end; procedure TChiSqrForm.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 TChiSqrForm.ColOutClick(Sender: TObject); begin if ColEdit.Text <> '' then begin VarList.Items.Add(ColEdit.Text); ColEdit.Text := ''; end; UpdateBtnStates; end; procedure TChiSqrForm.Compute; var ColNoSelected: IntDyneVec = nil; Freq: IntDyneMat = nil; Expected: DblDyneMat = nil; CellChi: DblDyneMat = nil; RowLabels: StrDyneVec = nil; ColLabels: StrDyneVec = nil; yates : boolean; NoSelected, NCases, NRows, NCols: Integer; i, j, rowNo, colNo, depNo: integer; df: integer; ChiSquare, probChi, phi: double; AdjChiSqr, AdjProbChi, pearsonr, G, likelihood, MantelHaenszel, prob: double; CoefCont, CramerV: double; lReport: TStrings; begin rowNo := GetVariableIndex(OS3MainFrm.DataGrid, RowEdit.Text); colNo := GetVariableIndex(OS3MainFrm.DataGrid, ColEdit.Text); depNo := GetVariableIndex(OS3MainFrm.DataGrid, DepEdit.Text); SetLength(ColNoSelected, NoVariables); 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; SetLength(ColNoSelected, NoSelected); // trim length // Get frequencies from the grid GetFrequencies(ColNoselected, rowNo, colNo, depNo, nRows, nCols, nCases, Freq); df := (nRows - 1) * (nCols - 1); // Calculate expected values and cell chi-squares GetExpectedAndCellChiSqr(Freq, Expected, CellChi, ChiSquare); ProbChi := 1.0 - ChiSquaredProb(ChiSquare, df); // prob. > chi // Yates correction yates := YatesChk.Checked and (nRows = 2) and (nCols = 2); if yates then begin GetYatesCorrection(Freq, AdjChiSqr); AdjProbChi := 1.0 - ChiSquaredProb(AdjChiSqr, df); end else AdjChiSqr := 0; // Get row and column labels for reports SetLength(RowLabels, NRows+1); for i := 1 to NRows do RowLabels[i-1] := Format('Row %d', [i]); RowLabels[NRows] := 'Total'; SetLength(ColLabels, NCols+1); for j := 1 to NCols do ColLabels[j-1] := Format('Col.%d', [j]); ColLabels[NCols] := 'Total'; // Print main results to report frame lReport := TStringList.Create; try lReport.Add('CHI-SQUARE ANALYSIS RESULTS'); lReport.Add(''); lReport.Add('Chi-square: %.3f', [ChiSquare]); lReport.Add(' with %d degrees of freedom', [DF]); lReport.Add(' Probability > value is %.4f', [ProbChi]); lReport.Add(''); if yates then begin lReport.Add('Chi-square using Yates correction: %.3f', [AdjChiSqr]); lReport.Add(' Probability > value is %.4f', [AdjProbChi]); lReport.Add(''); end; likelihood := CalcLikelihoodRatio(Freq, Expected); prob := 1.0 - ChiSquaredProb(likelihood, df); lReport.Add('Likelihood Ratio: %.3f', [likelihood]); lReport.Add(' Probability > value is %.4f', [prob]); lReport.Add(''); G := CalcGStatistic(Freq, Expected); prob := 1.0 - ChiSquaredProb(G, df); lReport.Add('G statistic: %.3f ', [G]); lReport.Add(' Probability > value is %.4f', [prob]); lReport.Add(''); if ((NRows > 1) and (NCols > 1)) then begin phi := sqrt(ChiSquare / NCases); lReport.Add('phi correlation: %.4f', [phi]); lReport.Add(''); pearsonR := CalcPearsonR(Freq); lReport.Add('Pearson Correlation r: %.4f', [pearsonR]); lReport.Add(''); MantelHaenszel := (NCases-1) * sqr(pearsonR); prob := 1.0 - ChiSquaredProb(MantelHaenszel, 1); lReport.Add('Mantel-Haenszel Test of Linear Association: %.3f', [MantelHaenszel]); lReport.Add(' Probability > value is %.4f', [prob]); lReport.Add(''); CoefCont := sqrt(ChiSquare / (ChiSquare + NCases)); lReport.Add('The coefficient of contingency is %.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 is %.3f', [CramerV]); lReport.Add(''); end; FReportFrame.DisplayReport(lReport); finally lReport.Free; end; // Print frequencies tables if requested by user if ObsChk.Checked or ExpChk.Checked then begin FrequenciesPage.TabVisible := true; ProcessAndReportFrequencies(Freq, Expected, RowLabels, ColLabels); end else FrequenciesPage.TabVisible := false; // Print proportions if requested by user if PropsChk.Checked then begin RowColPage.TabVisible := true; ProcessAndReportProportions(Freq, RowLabels, ColLabels); end else RowColPage.TabVisible := false; // Print cell chisqr values if requested by user if CellChiChk.Checked then begin CellChiSqrPage.TabVisible := true; ProcessAndReportCellChiSqr(CellChi, RowLabels, ColLabels, NCases); end else CellChiSqrPage.TabVisible := false; // Save frequency data in grid if elected. // NOTE: THIS WILL CLOSE CURRENT FILE! if SaveFChk.Checked and (MessageDlg('This operation will close the current data file. Continue?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then begin FrequenciesToGrid(Freq); Reset; // the grids contains new variables which must be read. SaveFChk.Checked := false; end; end; procedure TChiSqrForm.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 TChiSqrForm.DepOutClick(Sender: TObject); begin if DepEdit.Text <> '' then begin VarList.Items.Add(DepEdit.Text); DepEdit.Text := ''; end; UpdateBtnStates; end; procedure TChiSqrForm.FrequenciesToGrid(const AFrequencies: IntDyneMat); var numRows, numCols: Integer; row, col: Integer; i, j: Integer; colLabels: StrDyneVec = nil; begin MatSize(AFrequencies, numRows, numCols); // contains totals in last row/col dec(numRows); // we don't need the totals dec(numCols); 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; // get labels for new file SetLength(colLabels, 3); colLabels[0] := 'ROW'; colLabels[1] := 'COL'; colLabels[2] := 'FREQ'; // create new variables OS3MainFrm.DataGrid.ColCount := 4; DictionaryFrm.DictGrid.ColCount := 8; NoVariables := 0; for i := 0 to High(colLabels) do begin col := NoVariables + 1; DictionaryFrm.NewVar(col); // increments NoVariables! DictionaryFrm.DictGrid.Cells[1, col] := ColLabels[i]; OS3MainFrm.DataGrid.Cells[col, 0] := ColLabels[i]; end; OS3MainFrm.DataGrid.RowCount := (numRows * numCols) + 1; row := 0; for i := 0 to numRows-1 do begin for j := 0 to numCols-1 do begin row := row + 1; OS3MainFrm.DataGrid.Cells[0, Row] := Format('Case %d', [row]); OS3MainFrm.DataGrid.Cells[1, Row] := IntToStr(i+1); OS3MainFrm.DataGrid.Cells[2, Row] := IntToStr(j+1); OS3MainFrm.DataGrid.Cells[3, Row] := IntToStr(AFrequencies[i, j]); end; end; NoCases := row; OS3MainFrm.FileNameEdit.Text := 'ChiSqrFreq.laz'; OS3MainFrm.NoCasesEdit.Text := IntToStr(NoCases); OS3MainFrm.NoVarsEdit.Text := IntToStr(NoVariables); end; procedure TChiSqrForm.GetExpectedAndCellChiSqr(const AFrequencies: IntDyneMat; out AExpected, ACellChiSqr: DblDyneMat; out AChiSqr: Double); var n, m, numCases: Integer; i, j: Integer; begin MatSize(AFrequencies, n, m); // contains the totals row/col numCases := AFrequencies[n-1, m-1]; AExpected := nil; ACellChiSqr := nil; SetLength(AExpected, n-1, m-1); // -1: we don't need the totals here SetLength(ACellChiSqr, n-1, m-1); AChiSqr := 0; for i := 0 to n-2 do // -2 instead of -1 to skip the totals row begin for j := 0 to m-2 do // -2 instead of -1 to skip the totals column begin AExpected[i, j] := AFrequencies[n-1, j] * AFrequencies[i, m-1] / numCases; if AExpected[i, j] > 0 then ACellChiSqr[i, j] := sqr(AFrequencies[i, j] - AExpected[i, j]) / AExpected[i, j] else begin ErrorMsg('Zero expected value found.'); ACellChiSqr[i, j] := 0; end; AChiSqr := AChiSqr + ACellChiSqr[i, j]; end; end; end; procedure TChiSqrForm.GetFrequencies(const AColNoSelected: IntDyneVec; ARowIndex, AColIndex, ADepIndex: Integer; out ANumRows, ANumCols, ANumCases: Integer; out AFrequencies: IntDyneMat); var i, j: Integer; row, col: Integer; minRow, maxRow, minCol, maxCol: Integer; FObs: Integer; PObs: Double; begin // Get min and max of row and col numbers minRow := MaxInt; maxRow := -MinRow; minCol := MaxInt; maxCol := -MinCol; for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.DataGrid, i, AColNoSelected) then continue; row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ARowIndex, i]))); col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[AColIndex, 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; ANumRows := maxRow - minRow + 1; ANumCols := maxCol - minCol + 1; AFrequencies := nil; SetLength(AFrequencies, ANumRows+1, ANumCols+1); // +1 for row and column totals ANumCases := 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(OS3MainFrm.DataGrid, i, AColNoSelected) then continue; row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ARowIndex, i]))); col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[AColIndex, i]))); row := row - minRow; col := col - minCol; AFrequencies[row, col] := AFrequencies[row, col] + 1; ANumCases := ANumCases + 1; end; end; 1 : begin // read frequencies data from grid for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.DataGrid, i, AColNoSelected) then continue; row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ARowIndex, i]))); col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[AColIndex, i]))); row := row - minRow; col := col - minCol; FObs := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ADepIndex, i]))); AFrequencies[row, col] := AFrequencies[row, col] + FObs; ANumCases := ANumCases + FObs; end; end; 2 : begin // get no. of cases and proportions for each cell ANumCases := StrToInt(NCasesEdit.Text); for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.Datagrid, i, AColNoSelected) then continue; row := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ARowIndex, i]))); col := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[AColIndex, i]))); row := row - minRow + 1; col := col - minCol + 1; PObs := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ADepIndex, i])); AFrequencies[row, col] := AFrequencies[row, col] + round(PObs * ANumCases); end; end; end; // Get row totals for i := 0 to ANumRows-1 do for j := 0 to ANumCols-1 do AFrequencies[i, ANumCols] := AFrequencies[i, ANumCols] + AFrequencies[i, j]; // Get col totals for j := 0 to ANumCols-1 do for i := 0 to ANumRows-1 do AFrequencies[ANumRows, j] := AFrequencies[ANumRows, j] + AFrequencies[i, j]; // Grand total AFrequencies[ANumRows, ANumCols] := ANumCases; end; // 2 x 2 corrected chi-square procedure TChiSqrForm.GetYatesCorrection(const AFrequencies: IntDyneMat; out AdjChiSqr: Double); var n, m, numCases: Integer; begin MatSize(AFrequencies, n, m); numCases := AFrequencies[n-1, m-1]; AdjChiSqr := abs((AFrequencies[0,0] * AFrequencies[1,1]) - (AFrequencies[0,1] * AFrequencies[1,0])); AdjChiSqr := sqr(AdjChiSqr - numCases / 2.0) * numCases; // numerator AdjChiSqr := AdjChiSqr / (AFrequencies[0,2] * AFrequencies[1,2] * AFrequencies[2,0] * AFrequencies[2,1]); end; procedure TChiSqrForm.GetColProportions(const AFrequencies: IntDyneMat; out AProportions: DblDyneMat); var numRows, numCols: Integer; i, j: Integer; begin AProportions := nil; MatSize(AFrequencies, numRows, numCols); // totals in last row/col SetLength(AProportions, numRows, numCols); for j := 0 to numCols-1 do begin for i := 0 to numRows-1 do // Do not process the totals row begin if AFrequencies[numRows-1, j] > 0.0 then AProportions[i, j] := AFrequencies[i, j] / AFrequencies[numRows-1, j] else AProportions[i, j] := 0.0; end; if AFrequencies[numRows-1, j] > 0.0 then AProportions[numRows-1,j] := 1.0 else AProportions[numRows-1,j] := 0.0; end; end; procedure TChiSqrForm.GetRowProportions(const AFrequencies: IntDyneMat; out AProportions: DblDyneMat); var numRows, numCols: Integer; i, j: Integer; begin AProportions := nil; MatSize(AFrequencies, numRows, numCols); // totals in last row/col SetLength(AProportions, numRows, numCols); for i := 0 to numRows-1 do begin for j := 0 to numCols-1 do // do not touch the totals column here begin if AFrequencies[i, numCols-1] > 0.0 then AProportions[i, j] := AFrequencies[i, j] / AFrequencies[i, numCols-1] else AProportions[i-1, j-1] := 0.0; end; if AFrequencies[i, numCols-1] > 0.0 then AProportions[i, numCols-1] := 1.0 else AProportions[i, numCols-1] := 0.0; end; end; procedure TChiSqrForm.GetTotalProportions(const AFrequencies: IntDyneMat; out AProportions: DblDyneMat); var numRows, numCols, numCases: Integer; i, j: Integer; begin MatSize(AFrequencies, numRows, numCols); // totals in last row/col numCases := AFrequencies[numRows-1, numCols-1]; AProportions := nil; SetLength(AProportions, numRows, numCols); for i := 0 to numRows-1 do for j := 0 to numCols-1 do AProportions[i, j] := AFrequencies[i, j] / numCases; AProportions[numRows-1, numCols-1] := 1.0; end; procedure TChiSqrForm.InputGrpClick(Sender: TObject); begin // InputGrp = 0: have to count cases in each row and col combination // = 1: frequencies available for each row and column combo // = 2: only proportions available - get N size from NCasesEdit DepEdit.Enabled := (InputGrp.ItemIndex > 0); DepLabel.Enabled := DepEdit.Enabled; NCasesEdit.Enabled := (InputGrp.ItemIndex = 2); NCasesLabel.Enabled := NCasesEdit.Enabled; UpdateBtnStates; end; procedure TChiSqrForm.ProcessAndReportCellChiSqr(const ACellChiSqr: DblDyneMat; const ARowLabels, AColLabels: StrDyneVec; ANumCases: Integer); var lReport: TStrings; n, m: Integer; begin MatSize(ACellChiSqr, n, m); lReport := TStringList.Create; try lReport.Add('CHI-SQUARE ANALYSIS RESULTS'); lReport.Add(''); MatPrint(ACellChiSqr, n, m, 'CHI-SQUARED VALUE FOR CELLS', ARowLabels, AColLabels, ANumCases, lReport); FCellChiSqrReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TChiSqrForm.ProcessAndReportFrequencies(const AFrequencies: IntDyneMat; const AExpected: DblDyneMat; const ARowLabels, AColLabels: StrDyneVec); var lReport: TStrings; n, m, numCases: Integer; begin lReport := TStringList.Create; try lReport.Add('CHI-SQUARE ANALYSIS RESULTS'); MatSize(AFrequencies, n, m); // totals in last row and col numCases := AFrequencies[n-1, m-1]; if ObsChk.Checked then begin IntArrayPrint(AFrequencies, n, m, 'Rows', ARowLabels, AColLabels, 'OBSERVED FREQUENCIES', lReport); if ExpChk.Checked then lReport.Add(DIVIDER_SMALL_AUTO); end; if ExpChk.Checked then begin lReport.Add(''); MatPrint(AExpected, n-1, m-1, 'EXPECTED FREQUENCIES', ARowLabels, AColLabels, numCases, lReport); end; FFrequenciesReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TChiSqrForm.ProcessAndReportProportions(const AFrequencies: IntDyneMat; const ARowLabels, AColLabels: StrDyneVec); var lReport: TStrings; n, m, numCases: Integer; prop: DblDyneMat; begin MatSize(AFrequencies, n, m); numCases := AFrequencies[n-1, m-1]; lReport := TStringList.Create; try lReport.Add('CHI-SQUARE ANALYSIS RESULTS'); lReport.Add(''); GetRowProportions(AFrequencies, prop); MatPrint(prop, n, m, 'ROW PROPORTIONS', ARowLabels, AColLabels, numCases, lReport); lReport.Add(DIVIDER_SMALL_AUTO); lReport.Add(''); GetColProportions(AFrequencies, prop); MatPrint(prop, n, m, 'COLUMN PROPORTIONS', ARowLabels, AColLabels, numCases, lReport); lReport.Add(DIVIDER_SMALL_AUTO); lReport.Add(''); GetTotalProportions(AFrequencies, prop); MatPrint(Prop, n, m, 'PROPORTIONS OF TOTAL N', ARowLabels, AColLabels, numCases, lReport); FRowColPropsReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TChiSqrForm.Reset; var i: integer; begin inherited; if FFrequenciesReportFrame <> nil then FFrequenciesReportFrame.Clear; if FRowColPropsReportFrame <> nil then FRowColPropsReportFrame.Clear; if FCellChiSqrReportFrame <> nil then FCellChiSqrReportFrame.Clear; 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; DepLabel.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; FrequenciesPage.TabVisible := false; RowColPage.TabVisible := false; CellChiSqrPage.TabVisible := false; UpdateBtnStates; end; procedure TChiSqrForm.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 TChiSqrForm.RowOutClick(Sender: TObject); begin if RowEdit.Text <> '' then begin VarList.Items.Add(RowEdit.Text); RowEdit.Text := ''; end; UpdateBtnStates; end; procedure TChiSqrForm.UpdateBtnStates; begin inherited; if FFrequenciesReportFrame <> nil then FFrequenciesReportFrame.UpdateBtnStates; if FRowColPropsReportFrame <> nil then FRowColPropsReportFrame.UpdateBtnStates; if FCellChiSqrReportFrame <> nil then FCellChiSqrReportFrame.UpdateBtnStates; 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 TChiSqrForm.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 TChiSqrForm.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 TChiSqrForm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; end.