// Use file "anova2.laz" for testing unit BlkANOVAUnit; {$mode objfpc}{$H+} {$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, StdCtrls, ExtCtrls, ComCtrls, Buttons, Dialogs, LCLVersion, TACustomSeries, TAStyles, MainUnit, Globals, FunctionsLib, GraphLib, ANOVATestsUnit, ReportFrameUnit, BasicStatsReportAndChartFormUnit; type { TBlksAnovaForm } TBlksAnovaForm = class(TBasicStatsReportAndChartForm) Bevel1: TBevel; BrownForsythe: TCheckBox; ShowPlotsChk: TCheckBox; Plot3dChk: TCheckBox; Fact1Combo: TComboBox; Fact2Combo: TComboBox; Fact3Combo: TComboBox; AlphaGroup: TGroupBox; CorrectionsGroup: TGroupBox; Label6: TLabel; Label7: TLabel; Label8: TLabel; PosthocPage: TTabSheet; Welch: TCheckBox; DepIn: TBitBtn; DepOut: TBitBtn; Fact1In: TBitBtn; Fact1Out: TBitBtn; Fact2In: TBitBtn; Fact2Out: TBitBtn; Fact3In: TBitBtn; Fact3Out: TBitBtn; ScheffeChk: TCheckBox; TukeyHSDChk: TCheckBox; TukeyBChk: TCheckBox; TukeyKramerChk: TCheckBox; NewmanKeulsChk: TCheckBox; BonferroniChk: TCheckBox; OrthoContrastsChk: TCheckBox; DepVarEdit: TEdit; Factor1Edit: TEdit; Factor2Edit: TEdit; Factor3Edit: TEdit; OverallAlphaEdit: TEdit; PostAlphaEdit: TEdit; PosthocGroup: TGroupBox; Label1: TLabel; Label3: TLabel; Label4: TLabel; VarList: TListBox; StaticText1: TStaticText; StaticText2: TStaticText; StaticText3: TStaticText; StaticText4: TStaticText; procedure DepInClick(Sender: TObject); procedure DepOutClick(Sender: TObject); procedure Plot3dChkChange(Sender: TObject); procedure ShowPlotsChkChange(Sender: TObject); procedure VarChange(Sender: TObject); procedure Fact1OutClick(Sender: TObject); procedure Fact2InClick(Sender: TObject); procedure Fact2OutClick(Sender: TObject); procedure Fact3InClick(Sender: TObject); procedure Fact3OutClick(Sender: TObject); procedure Fact1InClick(Sender: TObject); procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); private { private declarations } N: integer; ColNoSelected: IntDyneVec; SSDep, SSErr, SSF1, SSF2, SSF3, SSF1F2, SSF1F3, SSF2F3, SSF1F2F3: double; MSDep, MSErr, MSF1, MSF2, MSF3, MSF1F2, MSF1F3, MSF2F3, MSF1F2F3: double; DFTot, DFErr, DFF1, DFF2, DFF3, DFF1F2, DFF1F3, DFF2F3, DFF1F2F3: double; Omega, OmegaF1, OmegaF2, OmegaF3, OmegaF1F2, F: Double; MinSize: Double; OmegaF1F3, OmegaF2F3, OmegaF1F2F3: double; FF1, FF2, FF1F2, ProbF1, ProbF2, ProbF3, ProbF1F2, ProbF1F3: double; FF3, FF2F3, FF1F3, FF1F2F3, ProbF2F3, ProbF1F2F3: double; MeanDep: Double; Nf1cells, Nf2cells, Nf3cells: integer; MinF1, MaxF1, MinF2, MaxF2, MinF3, MaxF3, NoFactors: integer; equal_grp: boolean; // check for equal groups for post-hoc tests cellcnts: IntDyneVec; // array of cell counts cellvars: DblDyneVec; // arrray of cell sums of squares then variances cellsums: DblDyneVec; // array of cell sums then means counts: IntDyneMat; // matrix for 2-way containing cell sizes sums: DblDyneMat; // matrix for 2-way containing cell sums vars: DblDyneMat; // matrix for 2-way containing sums of squares RowSums: DblDyneVec; // 2 way row sums ColSums: DblDyneVec; // 2 way col sums RowCount: IntDyneVec; // 2 way row count ColCount: IntDyneVec; // 2 way col count SlcSums: DblDyneVec; // 3 way slice sums SlcCount: IntDyneVec; // 3 way slice counts WSum, WX2: DblDyneCube; NCnt: IntDyneCube; NoGrpsA, NoGrpsB, NoGrpsC: integer; // AllAlpha, PostHocAlpha: double; // alphas for tests OKterms: array[1..14] of Boolean; procedure Init; function GetLevels(out DepValues, F1Values, F2Values, F3Values: DblDyneVec): Boolean; procedure Init1Way; function Calc1Way(const DepValues, F1Values: DblDyneVec): Boolean; procedure OneWayTable; procedure OneWayPostHoc; procedure OneWayPlot; procedure Init2Way; function Calc2Way(const DepValues, F1Values, F2Values: DblDyneVec): Boolean; procedure TwoWayTable; procedure TwoWayPlot; procedure TwoWayContrasts; procedure Init3Way; function Calc3Way(const DepValues, F1Values, F2Values, F3Values: DblDyneVec): Boolean; procedure ThreeWayTable; procedure ThreeWayPlot; procedure ThreeWayContrasts; procedure BrownForsytheOneWay(AReport: TStrings); procedure WelchOneWay(AReport: TStrings); procedure WelchtTests(AReport: TStrings); private FPosthocReportFrame: TReportFrame; FSeries: TChartSeries; FChartCombobox: TCombobox; FStyles: TChartStyles; procedure GetDataIndices(out ix, iy, iz: Integer); procedure PopulateChartCombobox(ThreeWay: Boolean); procedure SelectTwoWayPlot(Sender: TObject); procedure SelectThreeWayPlot(Sender: TObject); 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 BlksAnovaForm: TBlksAnovaForm; implementation {$R *.lfm} uses Math, TAChartUtils, TACustomSource, TASeries, TALegend, Utils, MathUnit, MatrixUnit, ChartFrameUnit, GridProcs; const FIXED_RANDOM: array[0..1] of string = ('fixed', 'random'); { TBlksAnovaForm } constructor TBlksAnovaForm.Create(AOwner: TComponent); begin inherited; if GraphFrm = nil then Application.CreateForm(TGraphFrm, GraphFrm); PostAlphaEdit.Text := FormatFloat('0.00', DEFAULT_ALPHA_LEVEL); OverallAlphaEdit.Text := FormatFloat('0.00', DEFAULT_ALPHA_LEVEL); FPosthocReportFrame := TReportFrame.Create(PostHocPage); FPosthocReportFrame.Parent := PostHocPage; FPosthocReportFrame.Align := alClient; InitToolbar(FPosthocReportFrame.ReportToolbar, tpTop); PostHocPage.PageIndex := 1; FStyles := TChartStyles.Create(FChartFrame); AddComboboxToToolbar(FChartFrame.ChartToolbar, 'Plots:', FChartCombobox); FChartFrame.Chart.Margins.Bottom := 0; PageControl.ActivePageIndex := 0; end; procedure TBlksAnovaForm.AdjustConstraints; begin inherited; ParamsPanel.Constraints.MinHeight := Fact3Combo.Top + Fact3Combo.Height + VarList.BorderSpacing.Bottom + CorrectionsGroup.Height + CorrectionsGroup.BorderSpacing.Bottom + AlphaGroup.Height + ShowPlotsChk.BorderSpacing.Top + ShowPlotsChk.Height + ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height; ParamsPanel.Constraints.MinWidth := Max( CorrectionsGroup.Width * 2 + Bevel1.Width, 4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left ); end; procedure TBlksAnovaForm.BrownForsytheOneWay(AReport: TStrings); var i, intValue: integer; c1: array[1..50] of double; cellmeans: array[1..50] of double; sumc1: double; fdegfree: double; Fnumerator, Fdenominator, NewF: double; X, Xsq: Double; begin for i := 1 to 50 do begin c1[i] := 0.0; cellmeans[i] := 0.0; end; for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.DataGrid, i, ColNoSelected) then continue; intvalue := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNoSelected[1], i]))) - MinF1; cellcnts[intvalue] := 0; cellsums[intvalue] := 0.0; cellvars[intvalue] := 0.0; end; MeanDep := 0.0; SSDep := 0.0; SSF1 := 0.0; MSErr := 0.0; N := 0; for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.DataGrid, i, ColNoSelected) then continue; intvalue := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNoSelected[1], i]))) - MinF1; X := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNoSelected[0], i])); Xsq := X*X; cellcnts[intvalue] := cellcnts[intvalue] + 1; cellsums[intvalue] := cellsums[intvalue] + X; cellvars[intvalue] := cellvars[intvalue] + Xsq; MeanDep := MeanDep + X; SSDep := SSDep + Xsq; N := N + 1; end; DFF1 := 0; for i := 0 to Nf1cells-1 do begin if cellcnts[i] > 0 then begin cellvars[i] := (cellvars[i] - sqr(cellsums[i]) /cellcnts[i]) / (cellcnts[i] - 1); SSF1 := SSF1 + (sqr(cellsums[i]) / cellcnts[i]); DFF1 := DFF1 + 1; end; end; SSF1 := SSF1 - (sqr(MeanDep) / N); SSDep := SSDep - (sqr(MeanDep) / N); SSErr := SSDep - SSF1; DFTot := N - 1; DFF1 := DFF1 - 1; DFErr := DFTot - DFF1; MSF1 := SSF1 / DFF1; MSErr := SSErr / DFErr; MSDep := SSDep / DFTot; Omega := (SSF1 - DFF1 * MSErr) / (SSDep + MSErr); F := MSF1 / MSErr; ProbF1 := probf(F,DFF1,DFErr); MeanDep := MeanDep / N; AReport.Add(''); AReport.Add(''); AReport.Add(DIVIDER); AReport.Add('BROWN-FORSYTHE ONE WAY ANALYSIS OF VARIANCE RESULTS'); AReport.Add(''); AReport.Add('Dependent variable is: %s, Independent variable is: %s', [DepVarEdit.Text, Factor1Edit.Text]); AReport.Add(''); AReport.Add('Traditional One-Way ANOVA Results'); AReport.Add(DIVIDER_SMALL); AReport.Add('SOURCE D.F. SS MS F PROB.>F OMEGA SQR.'); AReport.Add('-----------------------------------------------------------------------'); AReport.Add('BETWEEN %4.0f%10.2f%10.2f%10.2f%10.2f%10.2f', [DFF1, SSF1, MSF1, F, ProbF1, Omega]); AReport.Add('WITHIN %4.0f%10.2f%10.2f', [DFErr, SSErr, MSErr]); AReport.Add('TOTAL %4.0f%10.2f', [DFTot, SSDep]); AReport.Add(DIVIDER); sumc1 := 0.0; MSErr := 0.0; for i := 0 to Nf1cells-1 do begin c1[i+1] := (1.0 - (cellcnts[i] / N)) * cellvars[i]; sumc1 := sumc1 + c1[i+1]; end; for i := 1 to Nf1cells do c1[i] := c1[i] / sumc1; fdegfree := 0.0; for i := 1 to Nf1cells do fdegfree := fdegfree + ((c1[i] * c1[i]) / (cellcnts[i-1]-1.0)); fdegfree := round(1.0 / fdegfree); Fnumerator := 0.0; Fdenominator := 0.0; for i := 1 to Nf1cells do begin cellmeans[i] := cellsums[i-1] / cellcnts[i-1]; FNumerator := FNumerator + (cellcnts[i-1] * (sqr(cellmeans[i] - MeanDep))); FDenominator := FDenominator + ((1.0 - (cellcnts[i-1] / N)) * cellvars[i-1]); end; NewF := FNumerator / FDenominator; ProbF1 := probf(NewF,DFF1, fdegfree); AReport.Add(''); AReport.Add(DIVIDER); AReport.Add('Brown-Forsythe F statistic: %8.3f', [NewF]); AReport.Add('Brown-Forsythe denominator degrees of freedom: %8.0f', [fdegfree]); AReport.Add('Brown-Forsythe F probability: %8.3f', [probf1]); AReport.Add(DIVIDER); WelchtTests(AReport); end; function TBlksAnovaForm.Calc1Way(const DepValues, F1Values: DblDyneVec): Boolean; var i, j: integer; X, Xsq: Double; begin // Get working totals N := 0; MeanDep := 0; SSDep := 0; for i := 0 to High(DepValues) do begin j := round(F1Values[i]) - MinF1; X := DepValues[i]; Xsq := X * X; cellCnts[j] := cellCnts[j] + 1; cellSums[j] := cellSums[j] + X; cellVars[j] := CellVars[j] + Xsq; MeanDep := MeanDep + X; SSDep := SSDep + Xsq; N := N + 1; end; DFF1 := 0; SSF1 := 0; for i := 0 to NF1Cells-1 do begin if cellCnts[i] > 0 then begin SSF1 := SSF1 + sqr(cellSums[i]) / cellCnts[i]; DFF1 := DFF1 + 1; end; end; SSF1 := SSF1 - sqr(MeanDep) / N; SSDep := SSDep - sqr(MeanDep) / N; SSErr := SSDep - SSF1; DFTot := N - 1; DFF1 := DFF1 - 1; DFErr := DFTot - DFF1; MSF1 := SSF1 / DFF1; MSErr := SSErr / DFErr; MSDep := SSDep / DFTot; Omega := (SSF1 - DFF1 * MSErr) / (SSDep + MSErr); F := MSF1 / MSErr; ProbF1 := ProbF(F, DFF1, DFErr); MeanDep := MeanDep / N; Result := true; end; function TBlksAnovaForm.Calc2Way(const DepValues, F1Values, F2Values: DblDyneVec): Boolean; var i, j: integer; grpA, grpB: integer; constant, rowsTotCnt, colsTotCnt, SSCells: double; X, Xsq: Double; begin // initialize matrix values NoGrpsA := MaxF1 - MinF1 + 1; NoGrpsB := MaxF2 - MinF2 + 1; // Get working totals N := 0; MeanDep := 0.0; SSDep := 0.0; SSCells := 0.0; for i := 0 to High(DepValues) do begin grpA := round(F1Values[i]) - MinF1; grpB := round(F2Values[i]) - MinF2; X := DepValues[i]; Xsq := X * X; Counts[grpA, grpB] := Counts[grpA, grpB] + 1; Sums[grpA, grpB] := Sums[grpA, grpB] + X; Vars[grpA, grpB] := Vars[grpA, grpB] + Xsq; RowSums[grpA] := RowSums[grpA] + X; ColSums[grpB] := ColSums[grpB] + X; RowCount[grpA] := RowCount[grpA] + 1; ColCount[grpB] := ColCount[grpB] + 1; MeanDep := MeanDep + X; SSDep := SSDep + Xsq; N := N + 1; end; // Calculate results SSF1 := 0; RowsTotCnt := 0; for i := 0 to NoGrpsA-1 do begin SSF1 := SSF1 + sqr(RowSums[i]) / RowCount[i]; RowsTotCnt := RowsTotCnt + RowCount[i]; end; SSF2 := 0; ColsTotCnt := 0; for j := 0 to NoGrpsB-1 do begin SSF2 := SSF2 + sqr(ColSums[j]) / ColCount[j]; ColsTotCnt := ColsTotCnt + ColCount[j]; end; SSCells := 0; for i := 0 to NoGrpsA-1 do for j := 0 to NoGrpsB-1 do if Counts[i, j] > 0 then SSCells := SSCells + sqr(sums[i, j]) / Counts[i, j]; if N > 0 then constant := sqr(MeanDep) / N else constant := 0.0; SSF1 := SSF1 - constant; SSF2 := SSF2 - constant; SSF1F2 := SSCells - SSF1 - SSF2 - constant; SSErr := SSDep - SSCells; SSDep := SSDep - constant; // Must be after SSErr! if (SSF1F2 < 0) or (SSF1 < 0) or (SSF2 < 0) then begin ErrorMsg('A negative SS found. Unbalanced design? Ending analysis.'); Result := false; exit; end; DFTot := N - 1; DFF1 := NoGrpsA - 1; DFF2 := NoGrpsB - 1; DFF1F2 := DFF1 * DFF2; DFErr := DFTot - DFF1 - DFF2 - DFF1F2; MSF1 := SSF1 / DFF1; MSF2 := SSF2 / DFF2; MSF1F2 := SSF1F2 / DFF1F2; MSErr := SSErr / DFErr; MSDep := SSDep / DFTot; OmegaF1 := (SSF1 - DFF1 * MSErr) / (SSDep + MSErr); OmegaF2 := (SSF2 - DFF2 * MSErr) / (SSDep + MSErr); OmegaF1F2 := (SSF1F2 - DFF1F2 * MSErr) / (SSDep + MSErr); Omega := OmegaF1 + OmegaF2 + OmegaF1F2; MeanDep := MeanDep / N; // F tests for fixed effects if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) then begin FF1 := abs(MSF1 / MSErr); FF2 := abs(MSF2 / MSErr); FF1F2 := abs(MSF1F2 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFErr); ProbF2 := ProbF(FF2, DFF2, DFErr); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFErr); end else // F tests if both factors are random if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 1) then begin FF1 := abs(MSF1 / MSF1F2); FF2 := abs(MSF2 / MSF1F2); FF1F2 := abs(MSF1F2 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFF1F2); ProbF2 := ProbF(FF2, DFF2, DFF1F2); ProbF3 := ProbF(FF1F2, DFF1F2, DFErr); end else // F test if factor A is random and factor B is fixed if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 0) then begin FF1 := abs(MSF1 / MSErr); FF2 := abs(MSF2 / MSF1F2); FF1F2 := abs(MSF1F2 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFErr); ProbF2 := ProbF(FF2, DFF2, DFF1F2); ProbF3 := ProbF(FF1F2, DFF1F2, DFErr); end else // F test if factor A is fixed and factor B is random if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 1) then begin FF1 := abs(MSF1 / MSF1F2); FF2 := abs(MSF2 / MSErr); FF1F2 := abs(MSF1F2 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFF1F2); ProbF2 := ProbF(FF2, DFF2, DFErr); ProbF3 := ProbF(FF1F2, DFF1F2, DFErr); end; if (ProbF1 > 1.0) then ProbF1 := 1.0; if (ProbF2 > 1.0) then ProbF2 := 1.0; if (ProbF1F2 > 1.0) then ProbF1F2 := 1.0; // Obtain omega squared (proportion of dependent variable explained) if (OmegaF1 < 0.0) then OmegaF1 := 0.0; if (OmegaF2 < 0.0) then OmegaF2 := 0.0; if (OmegaF1F2 < 0.0) then OmegaF1F2 := 0.0; if (Omega < 0.0) then Omega := 0.0; Result := true; end; function TBlksAnovaForm.Calc3Way(const DepValues, F1Values, F2Values, F3Values: DblDyneVec): Boolean; var i, j, k: integer; grpA, grpB, grpC: integer; Constant, RowsTotCnt, ColsTotCnt, SlcsTotCnt, SSCells: double; p, n2: double; X, Xsq: Double; begin NoGrpsA := MaxF1 - MinF1 + 1; NoGrpsB := MaxF2 - MinF2 + 1; NoGrpsC := MaxF3 - MinF3 + 1; // Get working totals N := 0; MeanDep := 0; SSDep := 0; for i := 0 to High(DepValues) do begin grpA := round(F1Values[i]) - MinF1; grpB := round(F2Values[i]) - MinF2; grpC := round(F3Values[i]) - MinF3; X := DepValues[i]; Xsq := X * X; NCnt[grpA, grpB, grpC] := NCnt[grpA, grpB, grpC] + 1; WSum[grpA, grpB, grpC] := WSum[grpA, grpB, grpc] + X; WX2[grpA, grpB, grpC] := WX2[grpA, grpB, grpC] + Xsq; RowSums[grpA] := RowSums[grpA] + X; ColSums[grpB] := ColSums[grpB] + X; SlcSums[grpC] := SlcSums[grpC] + X; RowCount[grpA] := RowCount[grpA] + 1; ColCount[grpB] := ColCount[grpB] + 1; SlcCount[grpC] := SlcCount[grpC] + 1; MeanDep := MeanDep + X; SSDep := SSDep + Xsq; N := N + 1; end; // Calculate results constant := sqr(MeanDep) / N; // Get SS for rows SSF1 := 0; RowsTotCnt := 0; for i := 0 to NoGrpsA-1 do begin SSF1 := SSF1 + sqr(RowSums[i]) / RowCount[i]; RowsTotCnt := RowsTotCnt + RowCount[i]; end; SSF1 := SSF1 - constant; // Get SS for columns SSF2 := 0; ColsTotCnt := 0; for j := 0 to NoGrpsB-1 do begin SSF2 := SSF2 + sqr(ColSums[j]) / ColCount[j]; ColsTotCnt := ColsTotCnt + ColCount[j]; end; SSF2 := SSF2 - constant; // Get SS for slices SSF3 := 0; SlcsTotCnt := 0; for k := 0 to NoGrpsC-1 do begin SSF3 := SSF3 + sqr(SlcSums[k]) / SlcCount[k]; SlcsTotCnt := SlcsTotCnt + SlcCount[k]; end; SSF3 := SSF3 - constant; // Get SS for row-col interaction SSF1F2 := 0; for i := 0 to NoGrpsA-1 do begin for j := 0 to NoGrpsB-1 do begin p := 0.0; n2 := 0.0; for k := 0 to NoGrpsC-1 do begin p := p + WSum[i, j, k]; n2 := n2 + NCnt[i, j, k]; end; SSF1F2 := SSF1F2 + sqr(p) / n2; end; end; SSF1F2 := SSF1F2 - SSF1 - SSF2 - constant; // Get SS for row-slice interaction SSF1F3 := 0; for i := 0 to NoGrpsA-1 do begin for k := 0 to NoGrpsC-1 do begin p := 0.0; n2 := 0.0; for j := 0 to NoGrpsB-1 do begin p := p + WSum[i, j, k]; n2 := n2 + NCnt[i, j, k]; end; SSF1F3 := SSF1F3 + sqr(p) / n2; end; end; SSF1F3 := SSF1F3 - SSF1 - SSF3 - constant; // Get SS for column-slice interaction SSF2F3 := 0; for j := 0 to NoGrpsB-1 do begin for k := 0 to NoGrpsC-1 do begin p := 0.0; n2 := 0.0; for i := 0 to NoGrpsA-1 do begin p := p + WSum[i, j, k]; n2 := n2 + NCnt[i, j, k]; end; SSF2F3 := SSF2F3 + sqr(p) / n2; end; end; SSF2F3 := SSF2F3 - SSF2 - SSF3 - constant; // Get SS for cells SSCells := 0; for i := 0 to NoGrpsA-1 do for j := 0 to NoGrpsB-1 do for k := 0 to NoGrpsC-1 do SSCells := SSCells + sqr(wsum[i, j, k]) / ncnt[i, j, k]; SSF1F2F3 := SSCells - SSF1 - SSF2 - SSF3 - SSF1F2 - SSF1F3 - SSF2F3 - constant; SSErr := SSDep - SSCells; SSDep := SSDep - constant; if (SSF1 < 0.0) or (SSF2 < 0.0) or (SSF3 < 0.0) or (SSF1F2 < 0.0) or (SSF1F3 < 0.0) or (SSF2F3 < 0.0) or (SSF1F2F3 < 0.0) then begin ErrorMsg('A negative SS found. Unbalanced Design? Ending analysis.'); Result := false; exit; end; DFTot := N - 1; DFF1 := NoGrpsA - 1; DFF2 := NoGrpsB - 1; DFF3 := NoGrpsC - 1; DFF1F2 := DFF1 * DFF2; DFF1F3 := DFF1 * DFF3; DFF2F3 := DFF2 * DFF3; DFF1F2F3 := DFF1 * DFF2 * DFF3; DFErr := DFTot - DFF1 - DFF2 - DFF3 - DFF1F2 - DFF1F3 - DFF2F3 - DFF1F2F3; MSF1 := SSF1 / DFF1; MSF2 := SSF2 / DFF2; MSF3 := SSF3 / DFF3; MSF1F2 := SSF1F2 / DFF1F2; MSF1F3 := SSF1F3 / DFF1F3; MSF2F3 := SSF2F3 / DFF2F3; MSF1F2F3 := SSF1F2F3 / DFF1F2F3; MSErr := SSErr / DFErr; MSDep := SSDep / DFTot; OmegaF1 := (SSF1 - DFF1 * MSErr) / (SSDep + MSErr); OmegaF2 := (SSF2 - DFF2 * MSErr) / (SSDep + MSErr); OmegaF3 := (SSF3 - DFF3 * MSErr) / (SSDep + MSErr); OmegaF1F2 := (SSF1F2 - DFF1F2 * MSErr) / (SSDep + MSErr); OmegaF1F3 := (SSF1F3 - DFF1F3 * MSErr) / (SSDep + MSErr); OmegaF2F3 := (SSF2F3 - DFF2F3 * MSErr) / (SSDep + MSErr); OmegaF1F2F3 := (SSF1F2F3 - DFF1F2F3 * MSErr) / (SSDep + MSErr); Omega := OmegaF1 + OmegaF2 + OmegaF3 + OmegaF1F2 + OmegaF1F3 + OmegaF2F3 + OmegaF1F2F3; MeanDep := MeanDep / N; // F tests for fixed effects if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 0) then begin FF1 := abs(MSF1 / MSErr); FF2 := abs(MSF2 / MSErr); FF3 := abs(MSF3 / MSErr); FF1F2 := abs(MSF1F2 / MSErr); FF1F3 := abs(MSF1F3 / MSErr); FF2F3 := abs(MSF2F3 / MSErr); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFErr); ProbF2 := ProbF(FF2, DFF2, DFErr); ProbF3 := ProbF(FF3, DFF3, DFErr); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFErr); ProbF1F3 := ProbF(FF1F3, DFF1F3, DFErr); ProbF2F3 := ProbF(FF2F3, DFF2F3, DFErr); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F tests if all factors are random for i := 1 to 14 do OKterms[i] := true; // initialize as OK if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 1) then begin if (MSF1F2 + MSF1F3 - MSF1F2F3) <= 0.0 then OKTerms[1] := false else FF1 := abs(MSF1 / (MSF1F2 + MSF1F3 - MSF1F2F3)); if (MSF1F2 + MSF2F3 - MSF1F2F3) <= 0.0 then OKTerms[2] := false else FF2 := abs(MSF2 / (MSF1F2 + MSF2F3 - MSF1F2F3)); if (MSF1F3 + MSF2F3 - MSF1F2F3) <= 0.0 then OKTerms[3] := false else FF3 := abs(MSF3 / (MSF1F3 + MSF2F3 - MSF1F2F3)); FF1F2 := abs(MSF1F2 / MSF1F2F3); FF1F3 := abs(MSF1F3 / MSF1F2F3); FF2F3 := abs(MSF2F3 / MSF1F2F3); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFF1F2F3); ProbF2 := ProbF(FF2, DFF2, DFF1F2F3); ProbF3 := ProbF(FF3, DFF3, DFF1F2F3); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFF1F2F3); probF1F3 := ProbF(FF1F3, DFF1F3, DFF1F2F3); probF2F3 := ProbF(FF2F3, DFF2F3, DFF1F2F3); probF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F test if factor A is random, B and C Fixed if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 0) then begin FF1 := abs(MSF1 / MSErr); FF2 := abs(MSF2 / MSF1F2); FF3 := abs(MSF3 / MSF1F3); FF1F2 := abs(MSF1F2 / MSErr); FF1F3 := abs(MSF1F3 / MSErr); FF2F3 := abs(MSF2F3 / MSF1F2F3); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFErr); ProbF2 := ProbF(FF2, DFF2, DFF1F2); ProbF3 := ProbF(FF3, DFF3, DFF1F3); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFErr); ProbF1F3 := ProbF(FF1F3, DFF1F3, DFErr); ProbF2F3 := ProbF(FF2F3, DFF2F3, DFF1F2F3); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F test if factor b is random and A and C are Fixed if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 0) then begin FF1 := abs(MSF1 / MSF1F2); FF2 := abs(MSF2 / MSErr); FF3 := abs(MSF3 / MSF2F3); FF1F2 := abs(MSF1F2 / MSErr); FF1F3 := abs(MSF1F3 / MSF1F2F3); FF2F3 := abs(MSF2F3 / MSErr); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFF1F2); ProbF2 := ProbF(FF2, DFF2, DFErr); ProbF3 := ProbF(FF3, DFF3, DFF2F3); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFErr); ProbF1F3 := ProbF(FF1F3, DFF1F3, DFF1F2F3); ProbF2F3 := ProbF(FF2F3, DFF2F3, DFErr); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F test if factor c is random and A and B are Fixed if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 1) then begin FF1 := abs(MSF1 / MSF1F3); FF2 := abs(MSF2 / MSF2F3); FF3 := abs(MSF3 / MSErr); FF1F2 := abs(MSF1F2 / MSF1F2F3); FF1F3 := abs(MSF1F3 / MSErr); FF2F3 := abs(MSF2F3 / MSErr); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := ProbF(FF1, DFF1, DFF1F3); ProbF2 := ProbF(FF2, DFF2, DFF2F3); ProbF3 := ProbF(FF3, DFF3, DFErr); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFF1F2F3); ProbF1F3 := ProbF(FF1F3, DFF1F3, DFErr); ProbF2F3 := ProbF(FF2F3, DFF2F3, DFErr); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F tests if A is fixed, B and C are random if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 1) then begin if (MSF1F3 + MSF1F2 - MSF1F2F3) <= 0.0 then OKTerms[1] := false else FF1 := abs(MSF1 / (MSF1F3 + MSF1F2 - MSF1F2F3)); FF2 := abs(MSF2 / MSF2F3); FF3 := abs(MSF3 / MSF2F3); FF1F2 := abs(MSF1F2 / MSF1F2F3); FF1F3 := abs(MSF1F3 / MSF1F2F3); FF2F3 := abs(MSF2F3 / MSErr); FF1F2F3 := abs(MSF1F2F3 / MSErr); if (DFF1F3 + DFF1F2 - DFF1F2F3) <= 0 then OKTerms[8] := false else ProbF1 := ProbF(FF1, DFF1, DFF1F3 + DFF1F2 - DFF1F2F3); ProbF2 := ProbF(FF2, DFF2, DFF2F3); ProbF3 := ProbF(FF3, DFF3, DFF2F3); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFF1F2F3); ProbF1F3 := ProbF(FF1F3, DFF1F3, DFF1F2F3); ProbF2F3 := ProbF(FF2F3, DFF2F3, DFErr); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F tests if B is fixed, A and C are random if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 1) then begin FF1 := abs(MSF2 / MSF1F3); if (MSF2F3 + MSF1F2 - MSF1F2F3) <= 0.0 then OKTerms[2] := false else FF2 := abs(MSF1 / (MSF2F3 + MSF1F2 - MSF1F2F3)); FF3 := abs(MSF3 / MSF1F3); FF1F2 := abs(MSF1F2 / MSF1F2F3); FF1F3 := abs(MSF1F3 / MSErr); FF2F3 := abs(MSF2F3 / MSF1F2F3); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := probf(FF2,DFF2,DFF1F3); if (DFF2F3 + DFF1F2 - DFF1F2F3) <= 0 then OKTerms[9] := false else ProbF2 := ProbF(FF1, DFF1, DFF2F3 + DFF1F2 - DFF1F2F3); ProbF3 := ProbF(FF3, DFF3, DFF1F3); ProbF1F2 := ProbF(FF1F2, DFF1F2, DFF1F2F3); ProbF1F3 := Probf(FF1F3, DFF1F3, DFErr); ProbF2F3 := ProbF(FF2F3, DFF2F3, DFF1F2F3); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; // F tests if C is fixed A and B are random if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 0) then begin FF1 := abs(MSF1 / MSF1F2); FF2 := abs(MSF2 / MSF1F2); if (MSF2F3 + MSF1F3 - MSF1F2F3) <= 0.0 then OKTerms[3] := false else FF3 := abs(MSF3 / (MSF2F3 + MSF1F3 - MSF1F2F3)); FF1F2 := abs(MSF2F3 / MSErr); FF1F3 := abs(MSF1F2 / MSF1F2F3); FF2F3 := abs(MSF1F3 / MSF1F2F3); FF1F2F3 := abs(MSF1F2F3 / MSErr); ProbF1 := probf(FF3,DFF3,DFF1F2); ProbF2 := probf(FF2,DFF2,DFF1F2); if (DFF2F3 + DFF1F3 - DFF1F2F3) <= 0 then OKTerms[10] := false else ProbF3 := ProbF(FF1, DFF1, DFF2F3 + DFF1F3 - DFF1F2F3); ProbF1F2 := ProbF(FF2F3, DFF2F3, DFErr); ProbF1F3 := ProbF(FF1F2, DFF1F2, DFF1F2F3); ProbF2F3 := ProbF(FF1F3, DFF1F3, DFF1F2F3); ProbF1F2F3 := ProbF(FF1F2F3, DFF1F2F3, DFErr); end; if (ProbF1 > 1.0) then ProbF1 := 1.0; if (ProbF2 > 1.0) then ProbF2 := 1.0; if ProbF3 > 1.0 then ProbF3 := 1.0; if (ProbF1F2 > 1.0) then ProbF1F2 := 1.0; if ProbF1F3 > 1.0 then ProbF1F3 := 1.0; if ProbF2F3 > 1.0 then ProbF2F3 := 1.0; if ProbF1F2F3 > 1.0 then ProbF1F2F3 := 1.0; // Obtain omega squared (proportion of dependent variable explained) if (OmegaF1 < 0.0) then OmegaF1 := 0.0; if (OmegaF2 < 0.0) then OmegaF2 := 0.0; if OmegaF3 < 0.0 then OmegaF3 := 0.0; if (OmegaF1F2 < 0.0) then OmegaF1F2 := 0.0; if OmegaF1F3 < 0.0 then OmegaF1F3 := 0.0; if OmegaF2F3 < 0.0 then OmegaF2F3 := 0.0; if OmegaF1F2F3 < 0.0 then OmegaF1F2F3 := 0.0; if (Omega < 0.0) then Omega := 0.0; Result := true; end; procedure TBlksAnovaForm.Compute; var DepValues, F1Values, F2Values, F3Values: DblDyneVec; begin // Get column numbers of dependent variable and factors Init; // Get min and max of each factor code // The function fails when codes are not integers or not consecutive. if not GetLevels(DepValues, F1Values, F2Values, F3Values) then exit; // Do analysis case nofactors of 1 : // Single factor anova begin Init1Way; if Calc1Way(DepValues, F1Values) then begin OneWayTable; OneWayPostHoc; OneWayPlot; end; end; 2 : // Rwo-way anova begin Init2Way; if Calc2Way(DepValues, F1Values, F2Values) then begin TwoWayTable; TwoWayContrasts; TwoWayPlot; end; end; 3 : // three way anova begin Init3Way; if Calc3Way(DepValues, F1Values, F2Values, F3Values) then begin ThreeWayTable; ThreeWayContrasts; ThreeWayPlot; end; end; end; end; procedure TBlksAnovaForm.DepInClick(Sender: TObject); var index: integer; begin index := VarList.ItemIndex; if (index > -1) and (DepVarEdit.Text = '') then begin DepVarEdit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TBlksAnovaForm.DepOutClick(Sender: TObject); begin if DepVarEdit.Text <> '' then begin VarList.Items.Add(DepVarEdit.Text); DepVarEdit.Text := ''; end; UpdateBtnStates; end; procedure TBlksAnovaForm.Fact1InClick(Sender: TObject); var index: integer; begin index := VarList.ItemIndex; if (index > -1) and (Factor1Edit.Text = '') then begin Factor1Edit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TBlksAnovaForm.Fact1OutClick(Sender: TObject); begin if Factor1Edit.Text <> '' then begin VarList.Items.Add(Factor1Edit.Text); Factor1Edit.Text := ''; UpdateBtnStates; end; end; procedure TBlksAnovaForm.Fact2InClick(Sender: TObject); var index: integer; begin index := VarList.ItemIndex; if (index > -1) and (Factor2Edit.Text = '') then begin Factor2Edit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TBlksAnovaForm.Fact2OutClick(Sender: TObject); begin if Factor2Edit.Text <> '' then begin VarList.Items.Add(Factor2Edit.Text); Factor2Edit.Text := ''; UpdateBtnStates; end; end; procedure TBlksAnovaForm.Fact3InClick(Sender: TObject); var index: integer; begin index := VarList.ItemIndex; if (index > -1) and (Factor3Edit.Text = '') then begin Factor3Edit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TBlksAnovaForm.Fact3OutClick(Sender: TObject); begin if Factor3Edit.Text <> '' then begin VarList.Items.Add(Factor3Edit.Text); Factor3Edit.Text := ''; UpdateBtnStates; end; end; { Determines from the ItemIndex of the FChartCombobox which factors will be plotted along x and as y index, and for which C index the interactions will be calculated. } procedure TBlksAnovaForm.GetDataIndices(out ix, iy, iz: Integer); var index, i1, i2, i3: Integer; begin index := FChartComboBox.ItemIndex; i1 := 3; i2 := i1 + NF3Cells; i3 := i2 + NF3Cells; if (index >= i1) and (index < i3) then begin if index < i2 then begin ix := 1; iy := 2; iz := index - i1; end else begin ix := 2; iy := 1; iz := index - i2; end; exit; end; i1 := i3; i2 := i1 + NF2Cells; i3 := i2 + NF2Cells; if (index >= i1) and (index < i3) then begin if index < i2 then begin ix := 1; iy := 3; iz := index - i1; end else begin ix := 3; iy := 1; iz := index - i2; end; exit; end; i1 := i3; i2 := i1 + NF1Cells; i3 := i2 + NF1Cells; if (index >= i1) and (index < i3) then begin if index < i2 then begin ix := 2; iy := 3; iz := index - i1; end else begin ix := 3; iy := 2; iz := index - i2; end; end; end; function TBlksAnovaForm.GetLevels( out DepValues, F1Values, F2Values, F3Values: DblDyneVec): Boolean; var mx, mn: Double; msg: String; begin Result := false; DepValues := nil; F1Values := nil; F2Values := nil; F3Values := nil; // Extract dependent variable values DepValues := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[0], ColNoSelected); // Extract factor 1 values F1Values := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[1], ColNoSelected); VecMaxMin(F1Values, mx, mn); MaxF1 := round(mx); MinF1 := round(mn); NF1Cells := MaxF1 - MinF1 + 1; if not CheckFactorCodes(Factor1Edit.Text, F1Values, msg) then begin ErrorMsg(msg); exit; end; // Extract factor 2 values when available if NoFactors >= 2 then begin F2Values := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[2], ColNoSelected); VecMaxMin(F2Values, mx, mn); MaxF2 := round(mx); MinF2 := round(mn); NF2Cells := MaxF2 - MinF2 + 1; if not CheckFactorCodes(Factor2Edit.Text, F2Values, msg) then begin ErrorMsg(msg); exit; end; end else NF2Cells := 0; // Extract factor 3 values when available if NoFactors = 3 then begin F3Values := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[3], ColNoSelected); VecMaxMin(F3Values, mx, mn); MaxF3 := round(mx); MinF3 := round(mn); NF3Cells := MaxF3 - MinF3 + 1; if not CheckFactorCodes(Factor3Edit.Text, F3Values, msg) then begin ErrorMsg(msg); exit; end; end else NF3cells := 0; Result := true; end; procedure TBlksAnovaForm.Init; begin if Factor2Edit.Text = '' then NoFactors := 1 else if Factor3Edit.Text = '' then NoFactors := 2 else NoFactors := 3; // Get column numbers of dependent variable and factors SetLength(ColNoSelected, NoFactors + 1); // because DepVar is at index 0 ColNoSelected[0] := GetVariableIndex(OS3MainFrm.DataGrid, DepVarEdit.Text); ColNoSelected[1] := GetVariableIndex(OS3MainFrm.DataGrid, Factor1Edit.Text); if Factor2Edit.Text <> '' then ColNoSelected[2] := GetVariableIndex(OS3MainFrm.DataGrid, Factor2Edit.Text) else ColNoSelected[2] := -1; if Factor3Edit.Text <> '' then ColNoSelected[3] := GetVariableIndex(OS3MainFrm.DataGrid, Factor3Edit.Text) else ColNoSelected[3] := -1; CorrectionsGroup.Enabled := (NoFactors = 1); end; procedure TBlksAnovaForm.Init1Way; begin cellCnts := nil; cellSums := nil; cellVars := nil; SetLength(cellCnts, NF1Cells); SetLength(cellSums, NF1Cells); SetLength(cellVars, NF1Cells); end; procedure TBlksAnovaForm.Init2Way; begin Init1Way; RowSums := nil; ColSums := nil; RowCount := nil; ColCount := nil; Counts := nil; Sums := nil; Vars := nil; SetLength(RowSums, NF1Cells); // 2-way row sums SetLength(ColSums, NF2Cells); // 2-way column sums SetLength(RowCount, NF1Cells); // 2-way row counts SetLength(ColCount, NF2Cells); // 2-way column counts SetLength(Counts, NF1Cells, NF2Cells); // matrix for 2-way containing cell sizes SetLength(Sums, NF1Cells, NF2Cells); // matrix for 2-way containing cell sums SetLength(Vars, NF1Cells, NF2Cells); // matrix for 2-way containing sums of squares end; procedure TBlksAnovaForm.Init3Way; begin Init2Way; SlcSums := nil; SlcCount := nil; WSum := nil; WX2 := nil; NCnt := nil; SetLength(SlcSums, NF3Cells); // 3 way slice sums SetLength(SlcCount, NF3Cells); // 3 way slice counts SetLength(WSum, NF1Cells, NF2Cells, NF3Cells); SetLength(WX2, NF1Cells, NF2Cells, NF3Cells); SetLength(NCnt, NF1Cells, NF2Cells, NF3Cells); end; procedure TBlksAnovaForm.OneWayTable; var lReport: TStrings; i, grpsize: integer; minvar, maxvar, sumvar, sumfreqlogvar, sumDFrecip: double; c, bartlett, cochran, hartley, chiprob: double; maxSize: Integer; begin lReport := TStringList.Create; try lReport.Add('ONE WAY ANALYSIS OF VARIANCE RESULTS'); lReport.Add(''); lReport.Add('Dependent variable is: %s', [DepVarEdit.Text]); lReport.Add('Independent variable is: %s', [Factor1Edit.Text]); lReport.Add(''); lReport.Add('-----------------------------------------------------------------------------'); lReport.Add('SOURCE D.F. SS MS F PROB.> F Omega Sqr.'); lReport.Add('------------- ---- ---------- ---------- ---------- -------- ----------'); lReport.Add('Between %4.0f %10.3f %10.3f %10.3f %8.3f %8.3f', [DFF1, SSF1, MSF1, FF1, ProbF1, OmegaF1]); lReport.Add('Within %4.0f %10.3f %10.3f', [DFErr, SSErr, MSErr]); lReport.Add('Total %4.0f %10.3f', [DFTot, SSDep]); lReport.Add('-----------------------------------------------------------------------------'); lReport.Add(''); lReport.Add(''); lReport.Add('MEANS AND VARIABILITY OF THE DEPENDENT VARIABLE'); lReport.Add('FOR LEVELS OF THE INDEPENDENT VARIABLE'); lReport.Add('---------------------------------------------------'); lReport.Add('GROUP MEAN VARIANCE STD.DEV. N'); lReport.Add('----- --------- ---------- ---------- ---------'); // xxxx xxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxx equal_grp := true; minVar := 1e308; maxVar := -minVar; sumVar := 0.0; sumDFRecip := 0.0; sumFreqLogVar := 0.0; grpSize := round(cellCnts[0]); MinSize := grpSize; // initialized minimum group size maxSize := grpSize; // initialize maximum group size for i := 0 to NF1Cells-1 do begin grpSize := round(cellCnts[i]); if grpSize < MinSize then begin MinSize := grpSize; equal_grp := false; end; if grpSize > maxSize then maxSize := grpSize; if cellCnts[i] > 1 then begin cellVars[i] := (cellVars[i] - sqr(cellSums[i]) / cellCnts[i]) / (cellCnts[i] - 1); if cellvars[i] > maxVar then maxVar := cellVars[i]; if cellvars[i] < minVar then minVar := cellVars[i]; sumVar :=sumVar + cellVars[i]; sumDFRecip := sumDFRecip + 1.0 / (cellCnts[i] - 1); sumFreqLogVar := sumFreqLogVar + (cellCnts[i] - 1) * ln(cellVars[i]); // wp: was log10, but all the other s ahve ln() here end; if cellCnts[i] > 0 then lReport.Add('%5d %9.2f %10.2f %10.2f %7d', [ i+1, cellSums[i] / cellCnts[i], cellVars[i], sqrt(cellVars[i]), cellCnts[i] ]); end; lReport.Add('---------------------------------------------------'); lReport.Add('TOTAL %9.2f %10.2f %10.2f %7d', [MeanDep, MSDep, sqrt(MSDep), N]); lReport.Add('---------------------------------------------------'); lReport.Add(''); c := 1.0 + (1.0 / (3 * DFF1)) * (sumDFRecip - (1.0 / DFErr)); bartlett := (2.303 / c) * (DFErr * ln(MSErr) - sumFreqLogVar); // wp: was log10(),but all the others use ln() here chiProb := 1.0 - ChiSquaredProb(bartlett, round(DFF1)); cochran := maxVar / sumVar; hartley := maxVar / minVar; lReport.Add(DIVIDER_AUTO); lReport.Add(''); lReport.Add('TESTS FOR HOMOGENEITY OF VARIANCE'); lReport.Add(''); lReport.Add('Hartley Fmax test statistic: %8.3f (%d and %d degrees of freedom)', [hartley, NF1cells, maxSize-1]); lReport.Add('Cochran C statistic: %8.3f (%d and %d degrees of freedom)', [cochran, Nf1cells, maxSize-1]); lReport.Add('Bartlett Chi-square: %8.3f (%.0f degrees of freedom)', [bartlett, DFF1]); lReport.Add(' probability > Chi-Square: %8.3f', [chiprob]); FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TBlksAnovaForm.OneWayPostHoc; var lReport: TStrings; allAlpha, posthocAlpha: Double; begin if not (ScheffeChk.Checked or TukeyHSDChk.Checked or TukeyBChk.Checked or TukeyKramerChk.Checked or NewmanKeulsChk.Checked or BonferroniChk.Checked or OrthoContrastsChk.Checked) then begin PostHocPage.TabVisible := false; exit; end; allAlpha := StrToFloat(OverallAlphaEdit.Text); posthocAlpha := StrToFloat(PostAlphaEdit.Text); lReport := TStringList.Create; try if ScheffeChk.Checked then ScheffeTest(MSErr, cellSums, cellCnts, minF1, maxF1, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, MinSize, cellSums, cellCnts, minF1, maxF1, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellSums, cellCnts, minF1, maxF1, MinSize, posthocAlpha, lReport); if TukeyKramerChk.Checked then Tukey_Kramer(MSErr, DFErr, MinSize, cellSums, cellCnts, minF1, maxF1, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, MinSize, cellSums, cellCnts, minF1, maxF1, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellSums, cellCnts, cellVars, minF1, maxF1, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellSums, cellCnts, minF1, maxF1, allAlpha, posthocAlpha, lReport); if BrownForsythe.Checked then BrownForsytheOneWay(lReport); if Welch.Checked then WelchOneWay(lReport); FPostHocReportFrame.DisplayReport(lReport); PosthocPage.TabVisible := true; finally lReport.Free; end; end; procedure TBlksAnovaForm.OneWayPlot; var i: Integer; begin if not ShowPlotsChk.Checked then begin ChartPage.TabVisible := false; exit; end; FChartFrame.Clear; FChartFrame.SetYTitle('Mean'); FSeries := FChartFrame.PlotXY(ptBars, nil, nil, nil, nil, '', DATA_COLORS[0]); with TBarSeries(FSeries) do begin Styles := nil; Stacked := false; {$IF LCL_FullVersion >= 2010000} DepthBrightnessDelta := -30; {$ENDIF} end; if Plot3DChk.Checked then FSeries.Depth := 20; FSeries.ListSource.YCount := 1; for i := 0 to NF1Cells-1 do FSeries.AddXY(minF1 + i, cellSums[i] / cellCnts[i], IntToStr(MinF1 + i)); FChartFrame.SetXTitle(Factor1Edit.Text + ' codes (Factor A level)'); FChartCombobox.Parent.Hide; FChartFrame.Chart.Legend.Visible := false; FChartFrame.Chart.BottomAxis.Marks.Source := FSeries.Source; FChartFrame.Chart.BottomAxis.Marks.Style := smsXValue; end; procedure TBlksAnovaForm.Plot3dChkChange(Sender: TObject); begin if FSeries is TBarSeries then begin if Plot3dChk.Checked then TBarSeries(FSeries).Depth := 20 else TBarSeries(FSeries).Depth := 0; end; end; procedure TBlksAnovaForm.PopulateChartCombobox(ThreeWay: Boolean); var a, b, c: String; i, idx: Integer; begin a := Factor1Edit.Text; b := Factor2Edit.Text; c := Factor3Edit.Text; idx := FChartCombobox.ItemIndex; FChartCombobox.Items.Clear; FChartCombobox.Items.Add(a); FChartCombobox.Items.Add(b); if ThreeWay then FChartCombobox.Items.Add(c); { if InteractChk.Checked then begin } if ThreeWay then begin for i := 0 to NF3Cells-1 do FChartCombobox.Items.Add(Format('%s * %s vs %s with %s=%d', [a, b, a, c, Round(MinF3) + i])); for i := 0 to NF3Cells-1 do FChartCombobox.Items.Add(Format('%s * %s vs %s with %s=%d', [a, b, b, c, Round(MinF3) + i])); for i := 0 to NF2Cells-1 do FChartCombobox.Items.Add(Format('%s * %s vs %s with %s=%d', [a, c, a, b, Round(MinF2) + i])); for i := 0 to NF2Cells-1 do FChartCombobox.Items.Add(Format('%s * %s vs %s with %s=%d', [a, c, c, b, Round(MinF2) + i])); for i := 0 to NF1Cells-1 do FChartCombobox.Items.Add(Format('%s * %s vs %s with %s=%d', [b, c, b, a, Round(MinF1) + i])); for i := 0 to NF1Cells-1 do FChartCombobox.Items.Add(Format('%s * %s vs %s with %s=%d', [b, c, c, a, Round(MinF1) + i])); end else begin FChartCombobox.Items.Add(Format('%s * %s vs %s', [a, b, a])); FChartCombobox.Items.Add(Format('%s * %s vs %s', [a, b, b])); end; { end; } FChartComboBox.ItemIndex := EnsureRange(idx, 0, FChartComboBox.Items.Count-1); end; procedure TBlksAnovaForm.Reset; var i: integer; begin inherited; if FPostHocReportFrame <> nil then FPostHocReportFrame.Clear; if FChartCombobox <> nil then FChartCombobox.Items.Clear; VarList.Clear; for i := 1 to NoVariables do VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); DepVarEdit.Clear; Factor1Edit.Clear; Factor2Edit.Clear; Factor3Edit.Clear; Fact1Combo.ItemIndex := 0; Fact2Combo.ItemIndex := 0; Fact3Combo.ItemIndex := 0; ScheffeChk.Checked := false; TukeyHSDChk.Checked := false; TukeyBChk.Checked := false; TukeyKramerChk.Checked := false; NewmanKeulsChk.Checked := false; BonferroniChk.Checked := false; UpdateBtnStates; end; { OnChange event handler for the ChartCombobox. Displays the chart associated with the current ItemIndex. } procedure TBlksAnovaForm.SelectThreeWayPlot(Sender: TObject); var i, j, k, idx, ix, iy, iz: Integer; item: PChartDataItem; begin FStyles.Styles.Clear; FSeries.Clear; case FChartComboBox.ItemIndex of 0: begin // Plot means vs factor A FSeries.ListSource.YCount := 1; for i := 0 to NF1Cells-1 do FSeries.AddXY(minF1 + i, RowSums[i] / RowCount[i], IntToStr(MinF1 + i)); FChartFrame.SetXTitle(Factor1Edit.Text + ' codes'); FChartFrame.SetTitle(Factor1Edit.Text); end; 1: begin // Plot means vs factor B FSeries.ListSource.YCount := 1; for j := 0 to NF2Cells-1 do FSeries.AddXY(minF2 + j, ColSums[j] / ColCount[j], IntToStr(MinF2 + j)); FChartFrame.SetXTitle(Factor2Edit.Text + ' codes'); FChartFrame.SetTitle(Factor2Edit.Text); end; 2: begin // Plot means vs factor C FSeries.ListSource.YCount := 1; for i := 0 to NF3Cells-1 do FSeries.AddXY(minF3 + i, SlcSums[i] / SlcCount[i], IntToStr(MinF3 + i)); FChartFrame.SetXTitle(Factor3Edit.Text + ' codes'); FChartFrame.SetTitle(Factor3Edit.Text); end; else GetDataIndices(ix, iy,iz); if (ix = 1) and (iy = 2) then begin FSeries.ListSource.YCount := NF2Cells; for i := 0 to NF1Cells-1 do begin idx := FSeries.AddXY(minF1 + i, NaN, IntToStr(minF1 + i)); item := FSeries.Source.Item[idx]; for j := 0 to NF2Cells-1 do item^.SetY(j, wsum[i,j,iz] / ncnt[i,j,iz]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"' + LineEnding + '"%s" = %d', [ Factor1Edit.Text, Factor2Edit.Text, Factor3Edit.Text, MinF3 + iz])); FChartFrame.SetXTitle(Factor1Edit.Text + ' codes'); for j := 0 to NF2Cells-1 do with TChartStyle(FStyles.styles.Add) do begin Brush.Color := DATA_COLORS[j mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s = %s', [Factor2Edit.Text, IntToStr(MinF2 + j)]); end; end else if (ix = 2) and (iy = 1) then begin FSeries.ListSource.YCount := NF1Cells; for j := 0 to NF2Cells-1 do begin idx := FSeries.AddXY(minF2 + j, NaN, IntToStr(minF2 + j)); item := FSeries.Source.Item[idx]; for i := 0 to NF1Cells-1 do item^.SetY(i, wsum[i, j, iz] / ncnt[i, j, iz]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"' + LineEnding + '"%s" = %d', [ Factor1Edit.Text, Factor2Edit.Text, Factor3Edit.Text, MinF3 + iz])); FChartFrame.SetXTitle(Factor2Edit.Text + ' codes'); for i := 0 to NF1Cells-1 do with TChartStyle(FStyles.styles.Add) do begin Brush.Color := DATA_COLORS[i mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s = %s', [Factor1Edit.Text, IntToStr(MinF1 + i)]); end; end else if (ix = 1) and (iy = 3) then begin FSeries.ListSource.YCount := NF3Cells; for i := 0 to NF1Cells-1 do begin idx := FSeries.AddXY(minF1 + i, NaN, IntToStr(minF1 + i)); item := FSeries.Source.Item[idx]; for k := 0 to NF3Cells-1 do item^.SetY(k, wsum[i, iz, k] / ncnt[i, iz, k]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"' + LineEnding + '"%s" = %d', [ Factor1Edit.Text, Factor3Edit.Text, Factor2Edit.Text, MinF2 + iz])); FChartFrame.SetXTitle(Factor1Edit.Text + ' codes'); for k := 0 to NF3Cells-1 do with TChartStyle(FStyles.styles.Add) do begin Brush.Color := DATA_COLORS[k mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s = %s', [Factor3Edit.Text, IntToStr(MinF3 + k)]); end; end else if (ix = 3) and (iy = 1) then begin FSeries.ListSource.YCount := NF1Cells; for k := 0 to NF3Cells-1 do begin idx := FSeries.AddXY(minF3 + k, NaN, IntToStr(minF3 + k)); item := FSeries.Source.Item[idx]; for i := 0 to NF1Cells-1 do item^.SetY(i, wsum[i, iz, k] / ncnt[i, iz, k]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"' + LineEnding + '"%s" = %d', [ Factor1Edit.Text, Factor3Edit.Text, Factor2Edit.Text, MinF2 + iz])); FChartFrame.SetXTitle(Factor3Edit.Text + ' codes'); for i := 0 to NF1Cells-1 do with TChartStyle(FStyles.styles.Add) do begin Brush.Color := DATA_COLORS[i mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s = %s', [Factor1Edit.Text, IntToStr(MinF1 + i)]); end; end else if (ix = 2) and (iy = 3) then begin FSeries.ListSource.YCount := NF3Cells; for j := 0 to NF2Cells-1 do begin idx := FSeries.AddXY(minF2 + j, NaN, IntToStr(minF2 + j)); item := FSeries.Source.Item[idx]; for k := 0 to NF3Cells-1 do item^.SetY(k, wsum[iz, j, k] / ncnt[iz, j, k]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"' + LineEnding + '"%s" = %d', [ Factor2Edit.Text, Factor3Edit.Text, Factor1Edit.Text, MinF1 + iz])); FChartFrame.SetXTitle(Factor2Edit.Text + ' codes'); for k := 0 to NF3Cells-1 do with TChartStyle(FStyles.styles.Add) do begin Brush.Color := DATA_COLORS[k mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s = %s', [Factor3Edit.Text, IntToStr(MinF3 + k)]); end; end else if (ix = 3) and (iy = 2) then begin FSeries.ListSource.YCount := NF2Cells; for k := 0 to NF3Cells-1 do begin idx := FSeries.AddXY(minF3 + k, NaN, IntToStr(minF3 + k)); item := FSeries.Source.Item[idx]; for j := 0 to NF2Cells-1 do item^.SetY(j, wsum[iz, j, k] / ncnt[iz, j, k]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"' + LineEnding + '"%s" = %d', [ Factor2Edit.Text, Factor3Edit.Text, Factor1Edit.Text, MinF1 + iz])); FChartFrame.SetXTitle(Factor3Edit.Text + ' codes'); for j := 0 to NF2Cells-1 do with TChartStyle(FStyles.Styles.Add) do begin Brush.Color := DATA_COLORS[j mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s = %s', [Factor2Edit.Text, IntToStr(MinF2 + j)]); end; end else raise Exception.Create('Fatal error in SelectThreeWayPlot'); end; if (FSeries is TBarSeries) then begin if FStyles.Styles.Count > 0 then begin TBarSeries(FSeries).Styles := FStyles; FSeries.Legend.Multiplicity := lmStyle; end else FSeries.Legend.Multiplicity := lmSingle; end; FChartFrame.Chart.BottomAxis.Marks.Source := FSeries.Source; FChartFrame.Chart.BottomAxis.Marks.Style := smsLabel; FChartFrame.Chart.Legend.Visible := FSeries.Source.YCount > 1; FChartFrame.UpdateBtnStates; end; procedure TBlksAnovaForm.SelectTwoWayPlot(Sender: TObject); var i, j, idx: Integer; item: PChartDataItem; begin FStyles.Styles.Clear; FSeries.Clear; case FChartCombobox.ItemIndex of 0: begin // Plot means vs factor A FSeries.ListSource.YCount := 1; for i := 0 to NF1Cells-1 do FSeries.AddXY(minF1 + i, RowSums[i] / RowCount[i], IntToStr(MinF1 + i)); FChartFrame.SetXTitle(Factor1Edit.Text + ' codes'); FChartFrame.SetTitle(Factor1Edit.Text); end; 1: begin // Plot means vs factor B FSeries.ListSource.YCount := 1; for j := 0 to NF2Cells-1 do FSeries.AddXY(minF2 + j, ColSums[j] / ColCount[j], IntToStr(MinF2 + j)); FChartFrame.SetXTitle(Factor2Edit.Text + ' codes'); FChartFrame.SetTitle(Factor2Edit.Text); end; 2: begin // Plot interaction A*B vs A FSeries.ListSource.YCount := NF2Cells; for i := 0 to NF1Cells-1 do begin idx := FSeries.AddXY(minF1 + i, NaN, IntToStr(minF1 + i)); item := FSeries.Source.Item[idx]; for j := 0 to NF2Cells-1 do item^.SetY(j, sums[i, j] / counts[i, j]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "%s"', [Factor1Edit.Text, Factor2Edit.Text])); FChartFrame.SetXTitle(Factor1Edit.Text + ' codes'); for j := 0 to NF2cells-1 do with TChartStyle(FStyles.Styles.Add) do begin Brush.Color := DATA_COLORS[j mod Length(DATA_COLORS)]; UseBrush := true; Text := Format('%s %s', [Factor2Edit.Text, IntToStr(MinF2 + j)]); end; end; 3: begin // Plot means vs interaction A*B vs B FSeries.ListSource.YCount := NF1Cells; for j := 0 to NF2Cells-1 do begin idx := FSeries.AddXY(minF2 + j, NaN, IntToStr(minF2 + j)); item := FSeries.Source.Item[idx]; for i := 0 to NF1Cells-1 do item^.SetY(i, sums[i, j] / counts[i, j]); end; FChartFrame.SetTitle(Format('Factor "%s" x Factor "5s"', [Factor1Edit.Text, Factor2Edit.Text])); FChartFrame.SetXTitle(Factor2Edit.Text + ' codes'); for i := 0 to NF1Cells-1 do with TChartStyle(FStyles.styles.Add) do begin Brush.Color := DATA_COLORS[i mod Length(DATA_COLORS)]; UseBrush := True; Text := Format('%s %s', [Factor1Edit.Text, IntToStr(MinF1 + i)]); end; end; end; // case if (FSeries is TBarSeries) then begin if FStyles.Styles.Count > 0 then begin TBarSeries(FSeries).Styles := FStyles; FSeries.Legend.Multiplicity := lmStyle; end else FSeries.Legend.Multiplicity := lmSingle; end; FChartFrame.Chart.BottomAxis.Marks.Source := FSeries.Source; FChartFrame.Chart.BottomAxis.Marks.Style := smsLabel; FChartFrame.Chart.Legend.Visible := FSeries.Source.YCount > 1; FChartFrame.UpdateBtnStates; end; procedure TBlksAnovaForm.ShowPlotsChkChange(Sender: TObject); begin ChartPage.TabVisible := ShowPlotsChk.Checked; Plot3DChk.Enabled := ShowPlotsChk.Checked; end; procedure TBlksAnovaForm.ThreeWayContrasts; var lReport: TStrings; i, j, k: integer; value: double; variances: DblDyneVec = nil; RowSS, ColSS, SlcSS: double; totalCells: Integer; allAlpha, posthocAlpha: Double; begin if not (ScheffeChk.Checked or TukeyHSDChk.Checked or TukeyBChk.Checked or TukeyKramerChk.Checked or NewmanKeulsChk.Checked or BonferroniChk.Checked or OrthoContrastsChk.Checked) then begin PostHocPage.TabVisible := false; exit; end; allAlpha := StrToFloat(OverallAlphaEdit.Text); PostHocAlpha := StrToFloat(PostAlphaEdit.Text); totalCells := NF1Cells + NF2Cells + NF3Cells; SetLength(variances, totalCells); lReport := TStringList.Create; try // Do row comparisons if (NF1Cells > 2) and (ProbF1 < allAlpha) then begin for i := 0 to NoGrpsA-1 do begin RowSS := 0.0; for j := 0 to NoGrpsB-1 do for k := 0 to NoGrpsC-1 do RowSS := RowSS + wx2[i,j,k]; variances[i] := (RowSS - sqr(RowSums[i]) / RowCount[i]) / (RowCount[i] - 1); end; lReport.Add('COMPARISONS AMONG ROWS'); // get smallest group size value := 1e20; for i := 0 to NF1Cells-1 do if RowCount[i] < value then value := RowCount[i]; if ScheffeChk.Checked then ScheffeTest(MSErr, RowSums, RowCount, MinF1, MaxF1, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, RowSums, RowCount, MinF1, MaxF1, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, RowSums, RowCount, MinF1, MaxF1, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, RowSums, RowCount, MinF1, MaxF1, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, RowSums, RowCount, MinF1, MaxF1, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(RowSums, RowCount, variances, MinF1, MaxF1, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, RowSums, RowCount, MinF1, MaxF1, allAlpha, postHocAlpha, lReport); end; // Do column comparisons if (NF2Cells > 2) and (ProbF2 < allAlpha) then begin for j := 0 to NoGrpsB-1 do begin ColSS := 0.0; for i := 0 to NoGrpsA-1 do for k := 0 to NoGrpsC-1 do ColSS := ColSS + WX2[i,j,k]; variances[j] := (ColSS - sqr(ColSums[j]) / ColCount[j]) / (ColCount[j] - 1); end; if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG COLUMNS'); value := 1e308; for i := 0 to NF2Cells-1 do if ColCount[i] < value then value := ColCount[i]; if ScheffeChk.Checked then ScheffeTest(MSErr, ColSums, ColCount, MinF2, MaxF2, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, ColSums, ColCount, MinF2, MaxF2, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, ColSums, ColCount, MinF2, MaxF2, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, ColSums, ColCount, MinF2, MaxF2, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, ColSums, ColCount, MinF2, MaxF2, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(ColSums, ColCount, variances, MinF2, MaxF2, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, ColSums, ColCount, MinF2, MaxF2, allAlpha, posthocAlpha, lReport); end; // Do slice comparisons if (NF3Cells > 2) and (ProbF3 < allAlpha) then begin for k := 0 to NoGrpsC-1 do begin SlcSS := 0.0; for i := 0 to NoGrpsA-1 do for j := 0 to NoGrpsB-1 do SlcSS := SlcSS + WX2[i,j,k]; variances[k] := (SlcSS - sqr(SlcSums[k]) / SlcCount[k]) / (SlcCount[k] - 1); end; if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG SLICES'); value := 1e308; for i := 0 to NF3Cells-1 do if SlcCount[i] < value then value := SlcCount[i]; if ScheffeChk.Checked then ScheffeTest(MSErr, SlcSums, SlcCount, MinF3, MaxF3, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, SlcSums, SlcCount, MinF3, MaxF3, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, SlcSums, SlcCount, MinF3, MaxF3, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, SlcSums, SlcCount, MinF3, MaxF3, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, SlcSums, SlcCount, MinF3, MaxF3, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(SlcSums, SlcCount, variances, MinF3, MaxF3, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, SlcSums, SlcCount, MinF3, MaxF3, allAlpha, posthocAlpha, lReport); end; // Do simple effects for columns within each row if (ProbF1f2 < allAlpha) then begin if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG COLUMNS WITHIN EACH ROW'); for i := 0 to NF1cells-1 do begin lReport.Add(''); lReport.Add('ROW %d COMPARISONS',[i+1]); // move cell sums and counts to cellsums and cellcnts for j := 0 to NF2Cells-1 do begin for k := 0 to NF3Cells-1 do begin cellSums[j] := WSum[i,j,k]; cellCnts[j] := NCnt[i,j,k]; cellVars[j] := WX2[i,j,k]; end; end; value := 1e308; for j := 0 to NF2Cells-1 do if cellCnts[j] < value then value := cellCnts[j]; if ScheffeChk.Checked then ScheffeTest(MSErr, cellsums, cellcnts, MinF2, MaxF2, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, cellsums, cellcnts, MinF2, MaxF2, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellsums, cellcnts, MinF2, MaxF2, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, cellsums, cellcnts, MinF2, MaxF2, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, MinF2, MaxF2, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellsums, cellcnts, cellvars, MinF2, MaxF2, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellsums, cellcnts, MinF2, MaxF2, allAlpha, posthocAlpha, lReport); end; end; // Do simple effects for rows within each column if (ProbF1f2 < allAlpha) then begin if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG ROWS WITHIN EACH COLUMN'); for j := 0 to NF2Cells-1 do begin lReport.Add(''); lReport.Add('COLUMN %d COMPARISONS', [j+1]); // move cell sums and counts to cellsums and cellcnts for i := 0 to NF1Cells-1 do begin for k := 0 to NF3Cells-1 do begin cellSums[i] := WSum[i,j,k]; cellCnts[i] := NCnt[i,j,k]; cellVars[i] := WX2[i,j,k]; end; end; value := 1e308; for i := 0 to NF1Cells-1 do if cellCnts[j] < value then value := cellCnts[j]; if ScheffeChk.Checked then ScheffeTest(MSErr, cellsums, cellcnts, MinF1, MaxF1, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, cellsums, cellcnts, MinF1, MaxF1, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellsums, cellcnts, MinF1, MaxF1, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, cellsums, cellcnts, MinF1, MaxF1, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, MinF1, MaxF1, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellsums, cellcnts, cellvars, MinF1, MaxF1, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellsums, cellcnts, MinF1, MaxF1, allAlpha, posthocAlpha, lReport); end; end; // Do simple effects for columns within each slice if (ProbF2F3 < allAlpha) then begin if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG COLUMNS WITHIN EACH SLICE'); for k := 0 to NF3Cells-1 do begin lReport.Add(''); lReport.Add('SLICE %d COMPARISONS', [k+1]); // move cell sums and counts to cellsums and cellcnts for j := 0 to NF2Cells-1 do begin for i := 0 to NF1Cells-1 do begin cellSums[j] := WSum[i,j,k]; cellCnts[j] := NCnt[i,j,k]; cellVars[j] := WX2[i,j,k]; end; end; value := 1e20; for j := 1 to NF2cells do if cellCnts[j] < value then value := cellCnts[j]; if ScheffeChk.Checked then ScheffeTest(MSErr, cellsums, cellcnts, MinF2, MaxF2, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, cellsums, cellcnts, MinF2, MaxF2, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellsums, cellcnts, MinF2, MaxF2, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, cellsums, cellcnts, MinF2, MaxF2, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, MinF2, MaxF2, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellsums, cellcnts, cellvars, MinF2, MaxF2, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellsums, cellcnts, MinF2, MaxF2, allAlpha, posthocAlpha, lReport); end; end; // Do simple effects for rows within each slice if (ProbF1F3 < allAlpha) then begin if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG ROWS WITHIN EACH SLICE'); for k := 0 to NF3Cells-1 do begin lReport.Add(''); lReport.Add('SLICE %d COMPARISONS', [k+1]); // move cell sums and counts to cellsums and cellcnts for i := 0 to NF1Cells-1 do begin for j := 0 to NF2Cells-1 do begin cellSums[j] := WSum[i,j,k]; cellCnts[j] := NCnt[i,j,k]; cellVars[j] := WX2[i,j,k]; end; end; value := 1e20; for i := 0 to NF1Cells-1 do if cellCnts[i] < value then value := cellCnts[i]; if ScheffeChk.Checked then ScheffeTest(MSErr, cellsums, cellcnts, MinF1, MaxF1, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, cellsums, cellcnts, MinF1, MaxF1, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellsums, cellcnts, MinF1, MaxF1, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, cellsums, cellcnts, MinF1, MaxF1, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, MinF1, MaxF1, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellsums, cellcnts, cellvars, MinF1, MaxF1, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellsums, cellcnts, MinF1, MaxF1, allAlpha, posthocAlpha, lReport); end; end; FPosthocReportFrame.DisplayReport(lReport); finally lReport.Free; end; PostHocPage.TabVisible := true; end; procedure TBlksAnovaForm.ThreeWayPlot; begin if not ShowPlotsChk.Checked then begin ChartPage.TabVisible := false; exit; end; FChartFrame.Clear; // this destroys the series FChartFrame.SetYTitle('Mean'); FSeries := FChartFrame.PlotXY(ptBars, nil, nil, nil, nil, '', DATA_Colors[0]); with TBarSeries(FSeries) do begin Stacked := false; {$IF LCL_FullVersion >= 2010000} DepthBrightnessDelta := -30; {$IFEND} end; if Plot3DChk.Checked then FSeries.Depth := 20; FChartCombobox.Parent.Left := 0; FChartCombobox.Parent.Visible := true; PopulateChartCombobox(true); FChartCombobox.OnChange := @SelectThreeWayPlot; SelectThreeWayPlot(nil); ChartPage.TabVisible := true; end; procedure TBlksAnovaForm.ThreeWayTable; var lReport: TStrings; groupsize: integer; MinVar, MaxVar, sumvars, sumDFrecip: double; i, j, k: integer; XBar, V, S, RowSS, ColSS, SlcSS: double; sumfreqlogvar, c, bartlett, cochran, hartley, chiprob: double; problem: boolean = false; begin lReport := TStringList.Create; try lReport.Add('THREE-WAY ANALYSIS OF VARIANCE'); lReport.Add(''); lReport.Add('Variable analyzed: %s', [DepVarEdit.Text]); lReport.Add('Factor A (rows) variable: %s (%s levels)', [ Factor1Edit.Text, FIXED_RANDOM[Fact1Combo.ItemIndex] ]); lReport.Add('Factor B (columns) variable: %s (%s levels)', [ Factor2Edit.Text, FIXED_RANDOM[Fact2Combo.ItemIndex] ]); lReport.Add('Factor C (slices) variable: %s (%s levels)', [ Factor3Edit.Text, FIXED_RANDOM[Fact3Combo.ItemIndex] ]); lReport.Add(''); lReport.Add( '--------------------------------------------------------------------------------'); lReport.Add( 'SOURCE D.F. SS MS F PROB.> F Omega Squared'); lReport.Add( '------------- ---- ---------- ---------- ---------- -------- -------------'); if OKTerms[1]and OKTerms[8] then lReport.Add('Among Rows %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF1, SSF1, MSF1, FF1, ProbF1, OmegaF1]) else lReport.Add('Among Rows %4.0f %10.3f %10.3f --- error ---', [DFF1, SSF1, MSF1 ]); if OKTerms[2] and OKTerms[9] then lReport.Add('Among Columns %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF2, SSF2, MSF2, FF2, ProbF2, OmegaF2]) else lReport.Add('Among Columns %4.0f %10.3f %10.3f --- error ---', [DFF2, SSF2, MSF2]); if OKTerms[3] and OKTerms[10] then lReport.Add('Among Slices %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF3, SSF3, MSF3, FF3, ProbF3, OmegaF3]) else lReport.Add('Among Slices %4.0f %10.3f %10.3f --- error ---', [DFF3, SSF3, MSF3]); lReport.Add( 'A x B Inter. %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF1F2, SSF1F2, MSF1F2, FF1F2, ProbF1F2, OmegaF1F2]); lReport.Add( 'A x C Inter. %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF1F3, SSF1F3, MSF1F3, FF1F3, ProbF1F3, OmegaF1F3]); lReport.Add( 'B x C Inter. %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF2F3, SSF2F3, MSF2F3, FF2F3, ProbF2F3, OmegaF2F3]); lReport.Add( 'AxBxC Inter. %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF1F2F3, SSF1F2F3, MSF1F2F3, FF1F2F3, ProbF1F2F3, OmegaF1F2F3]); lReport.Add( 'Within Groups %4.0f %10.3f %10.3f', [DFErr, SSErr, MSErr]); lReport.Add( 'Total %4.0f %10.3f %10.3f', [DFTot, SSDep, MSDep]); lReport.Add( '--------------------------------------------------------------------------------'); lReport.Add( ''); lReport.Add( 'Omega squared for combined effects: %.3f', [Omega]); lReport.Add( ''); if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 0) then lReport.Add('Note: MSErr denominator for all F ratios.') else if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 1) then begin lReport.Add('Note: Error term for A is MSAxB + MSAxC - MSAxBxC'); lReport.Add('Error term for B is MSAxB + MSBxC - MSAxBxC'); lReport.Add('Error term for C is MSAxC + MSBxC - MSAxBxC'); lReport.Add('Error term for AxB, AxC and BxC is MSAxBxC'); lReport.Add('Error term for AxBxC is MSErr.'); end else if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 0) then begin lReport.Add('Note: Error term for A is MSErr'); lReport.Add('Note: Error term for B is MSAxB'); lReport.Add('Note: Error term for C is MSAxC'); lReport.Add('Note: Error term for AxB is MSErr'); lReport.Add('Note: Error term for AxC is MSErr'); lReport.Add('Note: Error term for BxC is MSAxBxC'); lReport.Add('Note: Error term for AxBxC is MSErr'); end else if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 0) then begin lReport.Add('Note: Error term for A is MSAxB'); lReport.Add('Note: Error term for B is MSErr'); lReport.Add('Note: Error term for C is MSBxC'); lReport.Add('Note: Error term for AxB is MSErr'); lReport.Add('Note: Error term for AxC is MSAxBxC'); lReport.Add('Note: Error term for BxC is MSErr'); lReport.Add('Note: Error term for AxBxC is MSErr'); end else if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 1) then begin lReport.Add('Note: Error term for A is MSAxC'); lReport.Add('Note: Error term for B is MSBxC'); lReport.Add('Note: Error term for C is MSErr'); lReport.Add('Note: Error term for AxB is MSAxBxC'); lReport.Add('Note: Error term for AxC is MSErr'); lReport.Add('Note: Error term for BxC is MSErr'); lReport.Add('Note: Error term for AxBxC is MSErr'); end else if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 1) then begin lReport.Add('Note: Error term for A is MSAxC + MSAxB - MSAxBxC'); lReport.Add('Note: Error term for B is MSBxC'); lReport.Add('Note: Error term for C is MSBxC'); lReport.Add('Note: Error term for AxB is MSAxBxC'); lReport.Add('Note: Error term for AxC is MSAxBxC'); lReport.Add('Note: Error term for BxC is MSErr'); lReport.Add('Note: Error term for AxBxC is MSErr'); end else if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 0) and (Fact3Combo.ItemIndex = 1) then begin lReport.Add('Note: Error term for A is MSAxC'); lReport.Add('Note: Error term for B is MSBxC + MSAxB - MSAxBxC'); lReport.Add('Note: Error term for C is MSAxC'); lReport.Add('Note: Error term for AxB is MSAxBxC'); lReport.Add('Note: Error term for AxC is MSErr'); lReport.Add('Note: Error term for BxC is MSAxBxC'); lReport.Add('Note: Error term for AxBxC is MSErr'); end else if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 1) and (Fact3Combo.ItemIndex = 0) then begin lReport.Add('Note: Error term for A is MSAxB'); lReport.Add('Note: Error term for B is MSAxB'); lReport.Add('Note: Error term for C is MSBxC + MSAxC - MSAxBxC'); lReport.Add('Note: Error term for AxB is MSErr'); lReport.Add('Note: Error term for AxC is MSAxBxC'); lReport.Add('Note: Error term for BxC is MSAxBxC'); lReport.Add('Note: Error term for AxBxC is MSErr'); end; for i := 1 to 10 do if not OKTerms[i] then problem := true; if problem then begin lReport.Add(''); lReport.Add('An error occurred due to either an estimate of MS being negative'); lReport.Add('or the degrees of freedom being zero. This may occur in a design'); lReport.Add('with random factors using the expected values for an exact F-test.'); lReport.Add('Quasi-F statistics may be employed where this problem exists. See'); lReport.Add('Winer, B.J., "Statistical Principles in Experimental Design, 1962'); lReport.Add('Section 5.15, pages 199-202 and Glass, G.V. and Stanley, J.C.,'); lReport.Add('1970, Section 18.10, pages 481-482.'); end; lReport.Add(''); lReport.Add(DIVIDER_AUTO); lReport.Add(''); lReport.Add('DESCRIPTIVE STATISTICS'); lReport.Add(''); lReport.Add('-------------------------------------------------------------'); lReport.Add('GROUP ROW COL SLICE N MEAN VARIANCE STD.DEV.'); lReport.Add('----- --- --- ----- ----- -------- ---------- --------'); // Display cell means, variances, standard deviations groupsize := NCnt[1, 1, 1]; equal_grp := true; MaxVar := -1E308; MinVar := 1E308; sumVars := 0.0; sumFreqLogVar := 0.0; sumDFRecip := 0.0; for i := 0 to NoGrpsA-1 do begin for j := 0 to NoGrpsB-1 do begin for k := 0 to NoGrpsC-1 do begin XBar := WSum[i, j, k] / NCnt[i, j, k]; V := (WX2[i, j, k] - sqr(WSum[i, j, k]) / NCnt[i, j, k]) / (NCnt[i, j, k] - 1); S := sqrt(V); sumVars := sumVars + V; if V > MaxVar then MaxVar := V; if V < MinVar then MinVar := V; sumDFRecip := sumDFRecip + (1.0 / (NCnt[i, j, k] - 1)); sumfreqLogVar := sumFreqLogVar + (NCnt[i, j, k] - 1) * ln(V); if NCnt[i, j, k] <> groupsize then equal_grp := false; lReport.Add('Cell %3d %3d %5d %5d %8.3f %10.3f %8.3f', [MinF1+i, MinF2+j, MinF3+k, NCnt[i, j, k], XBar, V, S]); end; end; end; //Display Row means, variances, standard deviations for i := 0 to NoGrpsA-1 do begin XBar := RowSums[i] / RowCount[i]; RowSS := 0.0; for j := 0 to NoGrpsB-1 do for k := 0 to NoGrpsC-1 do RowSS := RowSS + WX2[i, j, k]; V := (RowSS - sqr(RowSums[i]) / RowCount[i]) / (RowCount[i] - 1); S := sqrt(V); lReport.Add('Row %3d %5d %8.3f %10.3f %8.3f', [MinF1+i, RowCount[i], xBar, V, s]); end; //Display means, variances and standard deviations for columns for j := 0 to NoGrpsB-1 do begin XBar := ColSums[j] / ColCount[j]; ColSS := 0.0; for i := 0 to NoGrpsA-1 do for k := 0 to NoGrpsC-1 do ColSS := ColSS + WX2[i, j, k]; V := (ColSS - sqr(ColSums[j]) / ColCount[j]) / (ColCount[j] - 1); S := sqrt(V); lReport.Add('Col %3d %5d %8.3f %10.3f %8.3f', [MinF2+j, ColCount[j], xBar, V, S]); end; //Display means, variances and standard deviations for slices for k := 0 to NoGrpsC-1 do begin XBar := SlcSums[k] / SlcCount[k]; SlcSS := 0.0; for i := 0 to NoGrpsA-1 do for j := 0 to NoGrpsB-1 do SlcSS := SlcSS + WX2[i, j, k]; V := (SlcSS - sqr(SlcSums[k]) / SlcCount[k]) / (SlcCount[k] - 1); S := sqrt(V); lReport.Add('Slice %5d %5d %8.3f %10.3f %8.3f', [MinF3+k, SlcCount[k], xBar, V,S]); end; lReport.Add('-------------------------------------------------------------'); lReport.Add('TOTAL %5d %8.3f %10.3f %8.3f', [N, MeanDep, MSDep, sqrt(MSDep)]); lReport.Add('-------------------------------------------------------------'); lReport.Add(''); c := 1.0 + (1.0 / (3.0 * NoGrpsA * NoGrpsB * NoGrpsC - 1)) * (sumDFrecip - (1.0 / DFErr)); bartlett := (2.303 / c) * ((DFErr * ln(MSErr)) - SumFreqLogVar); chiProb := ChiSquaredProb(bartlett, round(NoGrpsA * NoGrpsB * NoGrpsC - 1)); cochran := MaxVar / SumVars; hartley := MaxVar / MinVar; lReport.Add(DIVIDER); lReport.Add(''); lReport.Add('TESTS FOR HOMOGENEITY OF VARIANCE'); lReport.Add(''); lReport.Add('Hartley Fmax test statistic: %8.3f with %d and %d degrees of freedom.', [hartley, NoGrpsA*NoGrpsB, groupsize-1]); lReport.Add('Cochran C statistic: %8.3f with %d and %d degrees of freedom.', [cochran, NoGrpsA*NoGrpsB, groupsize-1]); lReport.Add('Bartlett Chi-square statistic: %8.3f with %d degrees of freedom; probability > value: %.3f', [bartlett, NoGrpsA*NoGrpsB-1, 1-chiProb]); FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TBlksAnovaForm.TwoWayContrasts; var lReport: TStrings; i, j: integer; value: double; variances: DblDyneVec = nil; RowSS, ColSS: double; totalCells: Integer; allAlpha, posthocAlpha: Double; begin if not (ScheffeChk.Checked or TukeyHSDChk.Checked or TukeyBChk.Checked or TukeyKramerChk.Checked or NewmanKeulsChk.Checked or BonferroniChk.Checked or OrthoContrastsChk.Checked) then begin PostHocPage.TabVisible := false; exit; end; allAlpha := StrToFloat(OverallAlphaEdit.Text); posthocAlpha := StrToFloat(PostAlphaEdit.Text); totalCells := NF1Cells + NF2Cells + NF3Cells; SetLength(variances, totalCells); lReport := TStringList.Create; try // Do row comparisons if (NF1cells > 2) and (ProbF1 < allAlpha) and (Fact2Combo.ItemIndex = 0) then begin for i := 0 to NoGrpsA-1 do begin RowSS := 0.0; for j := 0 to NoGrpsB-1 do RowSS := RowSS + vars[i,j]; variances[i] := (RowSS - sqr(RowSums[i]) / RowCount[i]) / (RowCount[i] - 1); end; lReport.Add('COMPARISONS AMONG ROWS'); // Get smallest group size value := 1e308; for i := 0 to NF1Cells-1 do if RowCount[i] < value then value := RowCount[i]; if ScheffeChk.Checked then ScheffeTest(MSErr, RowSums, RowCount, minf1, maxf1, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, RowSums, RowCount, minf1, maxf1, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, RowSums, RowCount, minf1, maxf1, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, RowSums, RowCount, minf1, maxf1, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, RowSums, RowCount, minf1, maxf1, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(RowSums, RowCount, variances, minf1, maxf1, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, RowSums, RowCount, minf1, maxf1, AllAlpha, posthocAlpha, lReport); end; // Do column comparisons if (NF2Cells > 2) and (ProbF2 < allAlpha) and (Fact2Combo.ItemIndex = 0) then begin for j := 0 to NoGrpsB-1 do begin ColSS := 0.0; for i := 0 to NoGrpsA-1 do ColSS := ColSS + vars[i,j]; variances[j] := (ColSS - sqr(ColSums[j]) / ColCount[j]) / (ColCount[j] - 1); end; if lReport.Count <> 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG COLUMNS'); value := 1e308; for i := 0 to NF2cells-1 do if ColCount[i] < value then value := ColCount[i]; if ScheffeChk.Checked then ScheffeTest(MSErr, ColSums, ColCount, minf2, maxf2, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, ColSums, ColCount, minf2, maxf2, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, ColSums, ColCount, minf2, maxf2, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, ColSums, ColCount, minf2, maxf2, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, ColSums, ColCount, minf2, maxf2, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(ColSums, ColCount, variances, minf2, maxf2, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, ColSums, ColCount, minf2, maxf2, AllAlpha, postHocAlpha, lReport); end; // do simple effects for columns within each row if (ProbF3 < allAlpha) and (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) then begin if lReport.Count <> 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG COLUMNS WITHIN EACH ROW'); for i := 0 to NF1Cells-1 do begin lReport.Add(''); lReport.Add('ROW %d COMPARISONS',[i+1]); // move cell sums and counts to cellsums and cellcnts for j := 0 to NF2Cells-1 do begin cellSums[j] := Sums[i,j]; cellCnts[j] := Counts[i,j]; cellVars[j] := Vars[i,j]; end; value := 1e308; for j := 0 to NF2Cells-1 do if cellCnts[j] < value then value := cellCnts[j]; if ScheffeChk.Checked then ScheffeTest(MSErr, cellSums, cellCnts, MinF2, MaxF2, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, cellSums, cellCnts, MinF2, MaxF2, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellSums, cellCnts, MinF2, MaxF2, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, cellSums, cellCnts, MinF2, MaxF2, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellSums, cellCnts, MinF2, MaxF2, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellSums, cellCnts, cellVars, MinF2, MaxF2, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellSums, cellCnts, MinF2, MaxF2, allAlpha, PostHocAlpha, lReport); end; end; // do simple effects for rows within each column if (ProbF3 < allAlpha) and (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) then begin if lReport.Count > 0 then lReport.Add(''); lReport.Add('COMPARISONS AMONG ROWS WITHIN EACH COLUMN'); for j := 0 to NF2Cells-1 do begin lReport.Add(''); lReport.Add('COLUMN %d COMPARISONS', [j+1]); // move cell sums and counts to cellsums and cellcnts for i := 0 to NF1Cells-1 do begin cellSums[i] := Sums[i,j]; cellCnts[i] := Counts[i,j]; cellVars[i] := Vars[i,j]; end; value := 1e308; for i := 0 to NF1Cells-1 do if cellCnts[j] < value then value := cellCnts[j]; if ScheffeChk.Checked then ScheffeTest(MSErr, cellSums, cellCnts, MinF1, MaxF1, N, posthocAlpha, lReport); if TukeyHSDChk.Checked and equal_grp then Tukey(MSErr, DFErr, value, cellSums, cellCnts, MinF1, MaxF1, posthocAlpha, lReport); if TukeyBChk.Checked and equal_grp then TukeyBTest(MSErr, DFErr, cellSums, cellCnts, MinF1, MaxF1, value, posthocAlpha, lReport); if TukeyKramerChk.Checked and equal_grp then Tukey_Kramer(MSErr, DFErr, value, cellSums, cellCnts, MinF1, MaxF1, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellSums, cellCnts, MinF1, MaxF1, posthocAlpha, lReport); if BonferroniChk.Checked then Bonferroni(cellSums, cellCnts, cellVars, MinF1, MaxF1, posthocAlpha, lReport); if OrthoContrastsChk.Checked then Contrasts(MSErr, DFErr, cellSums, cellCnts, MinF1, MaxF1, allAlpha, postHocAlpha, lReport); end; end; FPosthocReportFrame.DisplayReport(lReport); finally lReport.Free; end; PostHocPage.TabVisible := true; end; procedure TBlksAnovaForm.TwoWayPlot; begin if not ShowPlotsChk.Checked then begin ChartPage.TabVisible := false; exit; end; FChartFrame.Clear; FChartFrame.SetYTitle('Mean'); FSeries := FChartFrame.PlotXY(ptBars, nil, nil, nil, nil, '', DATA_COLORS[0]); with TBarSeries(FSeries) do begin Stacked := false; {$IF LCL_FullVersion >= 2010000} DepthBrightnessDelta := -30; {$ENDIF} end; if Plot3DChk.Checked then FSeries.Depth := 20; FChartCombobox.Parent.Left := 0; FChartCombobox.Parent.Visible := true; PopulateChartCombobox(false); FChartCombobox.OnChange := @SelectTwoWayPlot; SelectTwoWayPlot(nil); ChartPage.TabVisible := true; end; procedure TBlksAnovaForm.TwoWayTable; var lReport: TStrings; groupsize: integer; MinVar, MaxVar, sumvars, sumDFrecip: double; i, j: integer; XBar, V, S, RowSS, ColSS: double; sumfreqlogvar, c, bartlett, cochran, hartley, chiprob: double; begin lReport := TStringList.Create; try lReport.Add('TWO-WAY ANALYSIS OF VARIANCE'); lReport.Add(''); lReport.Add('Variable analyzed: %s', [DepVarEdit.Text]); lReport.Add('Factor A (rows) variable: %s (%s levels)',[ Factor1Edit.Text, FIXED_RANDOM[Fact1Combo.ItemIndex] ]); lReport.Add('Factor B (columns) variable: %s (%s levels)', [ Factor2Edit.Text, FIXED_RANDOM[Fact2Combo.ItemIndex] ]); lReport.Add(''); lReport.Add( '--------------------------------------------------------------------------------'); lReport.Add( 'SOURCE D.F. SS MS F PROB.> F Omega Squared'); lReport.Add( '------------- ---- ---------- ---------- ---------- -------- -------------'); lReport.Add( 'Among Rows %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF1, SSF1, MSF1, FF1, ProbF1, OmegaF1]); lReport.Add( 'Among Columns %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF2, SSF2, MSF2, FF2, ProbF2, OmegaF2]); lReport.Add( 'Interaction %4.0f %10.3f %10.3f %10.3f %8.3f %10.3f', [DFF1F2, SSF1F2, MSF1F2, FF1F2, ProbF1F2, OmegaF1F2]); lReport.Add( 'Within Groups %4.0f %10.3f %10.3f', [DFErr, SSErr, MSErr]); lReport.Add( 'Total %4.0f %10.3f %10.3f', [DFTot, SSDep, MSDep]); lReport.Add( '--------------------------------------------------------------------------------'); lReport.Add( ''); lReport.Add('Omega squared for combined effects; %8.3f', [Omega]); lReport.Add(''); if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 0) then lReport.Add('Note: Denominator of F ratio is MSErr'); if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 1) then lReport.Add('Note: Denominator of F ratio is MSAxB'); if (Fact1Combo.ItemIndex = 0) and (Fact2Combo.ItemIndex = 1) then begin lReport.Add('Note: Denominator of F ratio for A is MSAxB'); lReport.Add(' and denominator for B and AxB is MSErr'); end; if (Fact1Combo.ItemIndex = 1) and (Fact2Combo.ItemIndex = 0) then begin lReport.Add('Note: Denominator of F ratio for B is MSAxB'); lReport.Add('and denominator for A and AxB is MSErr'); end; lReport.Add(''); lReport.Add(DIVIDER_AUTO); lReport.Add(''); lReport.Add('DESCRIPTIVE STATISTICS'); // lReport.Add(''); lReport.Add('------------------------------------------------------'); lReport.Add('GROUP Row Col. N MEAN VARIANCE STD.DEV.'); lReport.Add('----- --- ---- ----- --------- -------- --------'); groupsize := counts[0, 0]; equal_grp := true; MaxVar := 0.0; MinVar := 1e20; sumvars := 0.0; sumfreqlogvar := 0.0; sumDFrecip := 0.0; // Display cell means, variances, standard deviations V := 0.0; XBar := 0.0; S := 0.0; for i := 0 to NoGrpsA-1 do begin for j := 0 to NoGrpsB-1 do begin if counts[i,j] > 1 then begin XBar := sums[i,j] / counts[i,j]; V := vars[i,j] - ( (sums[i,j] * sums[i,j]) / counts[i,j]); V := V / (counts[i,j] - 1.0); S := sqrt(V); sumvars := sumvars + V; if V > MaxVar then MaxVar := V; if V < MinVar then MinVar := V; sumDFrecip := sumDFrecip + (1.0 / (counts[i,j] - 1)); sumfreqlogvar := sumfreqlogvar + ((counts[i,j] - 1) * ln(V)); if counts[i,j] <> groupsize then equal_grp := false; end; lReport.Add('Cell %3d %3d %4d %9.3f %8.3f %8.3f', [MinF1+i, MinF2+j, counts[i,j], XBar, V, S]); end; end; //Display Row means, variances, standard deviations for i := 0 to NoGrpsA-1 do begin XBar := RowSums[i] / RowCount[i]; RowSS := 0.0; for j := 0 to NoGrpsB-1 do RowSS := RowSS + vars[i,j]; V := RowSS - (RowSums[i] * RowSums[i] / RowCount[i]); V := V / (RowCount[i] - 1); S := sqrt(V); lReport.Add('Row %3d %4d %9.3f %8.3f %8.3f', [minf1+i, RowCount[i], XBar, V, S]); end; //Display means, variances and standard deviations for columns for j := 0 to NoGrpsB-1 do begin XBar := ColSums[j] / ColCount[j]; ColSS := 0.0; for i := 0 to NoGrpsA-1 do ColSS := ColSS + vars[i,j]; if ColCount[j] > 0 then V := ColSS - (ColSums[j] * ColSums[j] / ColCount[j]); if ColCount[j] > 1 then V := V / (ColCount[j] - 1); if V > 0.0 then S := sqrt(V); lReport.Add('Col %3d %4d %9.3f %8.3f %8.3f', [minf2+j, ColCount[j], XBar, V, S]); end; lReport.Add('------------------------------------------------------'); lReport.Add('TOTAL %4d %9.3f %8.3f %8.3f', [N, MeanDep, MSDep, sqrt(MSDep)]); lReport.Add('------------------------------------------------------'); lReport.Add(''); c := 1.0 + (1.0 / (3.0 * NoGrpsA * NoGrpsB - 1.0)) * (sumDFrecip - (1.0 / DFErr)); bartlett := (2.303 / c) * ((DFErr * ln(MSErr)) - sumfreqlogvar); chiprob := 1.0 - ChiSquaredProb(bartlett, round(NoGrpsA * NoGrpsB - 1)); cochran := maxvar / sumvars; hartley := maxvar / minvar; lReport.Add(DIVIDER_AUTO); lReport.Add(''); lReport.Add('TESTS FOR HOMOGENEITY OF VARIANCE'); lReport.Add(''); //DIVIDER_SMALL_AUTO); lReport.Add('Hartley Fmax test statistic: %8.3f with %d and %d degrees of freedom.', [hartley, NoGrpsA*NoGrpsB, groupsize-1]); lReport.Add('Cochran C statistic: %8.3f with %d and %d degrees of freedom.', [cochran, NoGrpsA*NoGrpsB, groupsize - 1]); lReport.Add('Bartlett Chi-square statistic: %8.3f with %d degrees of freedom; prob. > value: %.3f', [bartlett, NoGrpsA*NoGrpsB - 1, chiprob]); // lReport.Add(DIVIDER_AUTO); FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TBlksAnovaForm.UpdateBtnStates; begin inherited; if FPosthocReportFrame <> nil then FPostHocReportFrame.UpdateBtnStates; DepIn.Enabled := (VarList.ItemIndex > -1) and (DepVarEdit.Text = ''); DepOut.Enabled := DepVarEdit.Text <> ''; Fact1In.Enabled := (VarList.ItemIndex > -1) and (Factor1Edit.Text = ''); Fact1Out.Enabled := Factor1Edit.Text <> ''; Fact2In.Enabled := (VarList.ItemIndex > -1) and (Factor2Edit.Text = ''); Fact2Out.Enabled := Factor2Edit.Text <> ''; Fact3In.Enabled := (VarList.ItemIndex > -1) and (Factor3Edit.Text = ''); Fact3Out.Enabled := Factor3Edit.Text <> ''; end; function TBlksAnovaForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; var a: Double; begin Result := false; if DepVarEdit.Text = '' then begin AMsg := 'Dependent variable not specified.'; AControl := VarList; exit; end; if Factor1Edit.Text = '' then begin AMsg := 'Factor 1 variable not specified.'; AControl := VarList; exit; end; if (Factor2Edit.Text = '') and (Factor3Edit.Text <> '') then begin AMsg := 'A two-way ANOVA needs factors 1 and 2 (not 3), a three-way analysis needs all factors.'; AControl := VarList; exit; end; if OverallAlphaEdit.Text = '' then begin AMsg := 'Overall alpha level not specified.'; AControl := OverallAlphaEdit; exit; end; if not TryStrToFloat(OverallAlphaEdit.Text, a) or (a <= 0) or (a >= 1) then begin AMsg := 'Overall alpha level is not a valid number between 0 and 1.'; AControl := OverallAlphaEdit; exit; end; if PostAlphaEdit.Text = '' then begin AMsg := 'Post-hoc alpha level not specified.'; AControl := PostAlphaEdit; exit; end; if not TryStrToFloat(PostAlphaEdit.Text, a) or (a <= 0) or (a >= 1) then begin AMsg := 'Post-hoc alpha level is not a valid number between 0 and 1.'; AControl := PostAlphaEdit; exit; end; Result := true; end; procedure TBlksAnovaForm.VarChange(Sender: TObject); begin UpdateBtnStates; end; procedure TBlksAnovaForm.VarListDblClick(Sender: TObject); var index: Integer; s: String; begin index := VarList.ItemIndex; if index > -1 then begin s := VarList.Items[index]; if DepVarEdit.Text = '' then DepVarEdit.Text := s else if Factor1Edit.Text = '' then Factor1Edit.Text := s else if Factor2Edit.Text = '' then Factor2Edit.Text := s else if Factor3Edit.Text = '' then Factor3Edit.Text := s; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TBlksAnovaForm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; procedure TBlksAnovaForm.WelchOneWay(AReport: TStrings); var i, intValue: integer; W, v, barx, numerator, denominator: double; wj: array[1..50] of double; c1: array[1..50] of double; barxj: array[1..50] of double; sumc1: double; fdegfree, term1, term2, term3: double; X, Xsq: Double; begin for i := 1 to 50 do begin wj[i] := 0.0; c1[i] := 0.0; barxj[i] := 0.0; end; for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.DataGrid, i, ColNoSelected) then continue; intvalue := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNoSelected[1], i]))); // X := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[DepVarCol,i])); intvalue := intvalue - minf1; cellcnts[intvalue] := 0; cellsums[intvalue] := 0.0; cellvars[intvalue] := 0.0; end; MeanDep := 0.0; SSDep := 0.0; SSF1 := 0.0; MSErr := 0.0; N := 0; for i := 1 to NoCases do begin if not GoodRecord(OS3MainFrm.DataGrid, i, ColNoSelected) then continue; intvalue := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNoSelected[1], i]))); X := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[ColNoSelected[0], i])); Xsq := X*X; intvalue := intvalue - minf1; cellcnts[intvalue] := cellcnts[intvalue] + 1; cellsums[intvalue] := cellsums[intvalue] + X; cellvars[intvalue] := cellvars[intvalue] + Xsq; MeanDep := MeanDep + X; SSDep := SSDep + Xsq; barxj[intvalue+1] := barxj[intvalue+1] + X; N := N + 1; end; DFF1 := 0; W := 0.0; barx := 0.0; v := 0.0; for i := 0 to Nf1cells-1 do begin if cellcnts[i] > 0 then begin cellvars[i] := cellvars[i] - (cellsums[i] * cellsums[i] /cellcnts[i]); cellvars[i] := cellvars[i] / (cellcnts[i] - 1.0); wj[i+1] := cellcnts[i] / cellvars[i]; W := W + wj[i+1]; barxj[i+1] := barxj[i+1] / cellcnts[i]; SSF1 := SSF1 + (sqr(cellsums[i]) / cellcnts[i]); DFF1 := DFF1 + 1; end; end; for i := 1 to Nf1cells do barx := barx + (wj[i] * barxj[i]); barx := barx / W; numerator := 0.0; for i := 1 to Nf1cells do numerator := numerator + (wj[i]* sqr(barxj[i]-barx)); numerator := numerator / (Nf1cells - 1.0); denominator := 0.0; for i := 1 to Nf1cells do v := v + ( (1.0 /(cellcnts[i-1]-1.0)) * (sqr(1.0 - wj[i]/W)) ); v := 3.0 * v; term1 := sqr(Nf1cells) - 1.0; v := term1 / v; for i := 1 to Nf1cells do begin term1 := 1.0 / (cellcnts[i-1] - 1.0); term2 := sqr(1.0 - (wj[i] / W)); denominator := denominator + (term1 * term2); end; term1 := sqr(Nf1cells) - 1.0; term2 := 2.0 * (Nf1cells - 2.0); term3 := 1.0; denominator := term3 + ((term2 / term1) * denominator); F := numerator / denominator; DFF1 := Nf1cells - 1; SSF1 := SSF1 - (sqr(MeanDep) / float(N)); SSDep := SSDep - (sqr(MeanDep) / float(N)); SSErr := SSDep - SSF1; DFTot := N - 1; DFErr := DFTot - DFF1; MSF1 := SSF1 / DFF1; MeanDep := MeanDep / float(N); sumc1 := 0.0; for i := 0 to Nf1cells-1 do begin MSErr := MSErr + (((1.0 - cellcnts[i] / N) * cellvars[i])/ DFF1); c1[i+1] := (1.0 - (cellcnts[i] / N)) * cellvars[i]; sumc1 := sumc1 + c1[i+1]; end; for i := 1 to Nf1cells do c1[i] := c1[i] / sumc1; fdegfree := 0.0; for i := 1 to Nf1cells do fdegfree := fdegfree + (c1[i] * c1[i]) / (cellcnts[i-1]-1.0); fdegfree := round(1.0 / fdegfree); MSDep := SSDep / DFTot; Omega := (SSF1 - DFF1 * MSErr) / (SSDep + MSErr); // F := MSF1 / MSErr; // ProbF1 := probf(F,DFF1, DFErr); AReport.Add(''); AReport.Add(DIVIDER); AReport.Add('WELCH ONE WAY ANALYSIS OF VARIANCE RESULTS'); AReport.Add(''); AReport.Add('Dependent variable is: %s', [DepVarEdit.Text]); AReport.Add('Independent variable is: %s', [Factor1Edit.Text]); AReport.Add(''); { OutputFrm.RichEdit.Lines.Add('---------------------------------------------------------------------'); OutputFrm.RichEdit.Lines.Add('SOURCE D.F. SS MS F PROB.>F OMEGA SQR.'); OutputFrm.RichEdit.Lines.Add('---------------------------------------------------------------------'); outline := format('BETWEEN %4.0f%10.2f%10.2f%10.2f%10.2f%10.2f', [DFF1,SSF1,MSF1,F,ProbF1,Omega]); OutputFrm.RichEdit.Lines.Add(outline); outline := format('WITHIN %4.0f%10.2f%10.2f',[DFErr,SSErr,MSErr]); OutputFrm.RichEdit.Lines.Add(outline); outline := format('TOTAL %4.0f%10.2f',[DFTot,SSDep]); OutputFrm.RichEdit.Lines.Add(outline); OutputFrm.RichEdit.Lines.Add('---------------------------------------------------------------------'); OutputFrm.RichEdit.Lines.Add(''); } AReport.Add('Welch F statistic: %8.4f', [F]); AReport.Add('Welch denominator degrees of freedom: %8.0f', [v]); probF1 := probf(F,DFF1,v); AReport.Add('Welch F probability: %8.3f', [probf1]); WelchtTests(AReport); end; procedure TBlksAnovaForm.WelchtTests(AReport: TStrings); var i, j, NoCompares: integer; t: double; // Welch t value gnu: double; // degrees of freedom var1, var2: double; // variance estimates for two variables mean1, mean2: double; // means for two variables probability: double; // t probability numerator, denominator, term1, term2: double; // work values v: integer; // rounded degrees of freedom begin NoCompares := Nf1cells; AReport.Add(''); AReport.Add(DIVIDER); AReport.Add('WELCH T-TESTS AMONG GROUPS'); AReport.Add(DIVIDER_SMALL); for i := 1 to NoCompares - 1 do begin for j := i + 1 to NoCompares do begin AReport.Add('Comparison of group %d with group %d', [i, j]); mean1 := cellsums[i-1] / cellcnts[i-1]; mean2 := cellsums[j-1] / cellcnts[j-1]; var1 := cellvars[i-1]; var2 := cellvars[j-1]; denominator := sqrt((var1 / cellcnts[i-1]) + (var2 / cellcnts[j-1])); numerator := mean1 - mean2; t := numerator / denominator; AReport.Add('Mean %d: %8.3f', [i, mean1]); AReport.Add('Mean %d: %8.3f', [j, mean2]); AReport.Add('Welch t: %8.3f' ,[t]); numerator := sqr((var1 /cellcnts[i-1]) + (var2 / cellcnts[j-1])); term1 := sqr(var1) / (sqr(cellcnts[i-1]) * (cellcnts[i-1]-1.0)); term2 := sqr(var2) / (sqr(cellcnts[j-1]) * (cellcnts[j-1]-1.0)); denominator := term1 + term2; numerator := sqr((var1 / cellcnts[i-1]) + (var2 / cellcnts[j-1])); gnu := numerator / denominator; AReport.Add('Degrees of freedom: %8.3f', [gnu]); v := round(gnu); AReport.Add('Rounded degrees of freedom: %8d', [v]); probability := ProbT(t, gnu); AReport.Add('Probability > t: %8.3f', [probability]); AReport.Add(''); end; end; end; end.