unit OneCaseANOVAUnit; {$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, StdCtrls, Buttons, ExtCtrls, ComCtrls, LCLVersion, TASources, TACustomSeries, TAStyles, MainUnit, Globals, ReportFrameUnit, ANOVATestsUnit, BasicStatsReportAndChartFormUnit; type { TOneCaseAnovaForm } TOneCaseAnovaForm = class(TBasicStatsReportAndChartForm) ShowPlotsChk: TCheckBox; Plot3DChk: TCheckBox; DepIn: TBitBtn; DepOut: TBitBtn; DepVarEdit: TEdit; Fact1In: TBitBtn; Fact1Out: TBitBtn; Fact2In: TBitBtn; Fact2Out: TBitBtn; Fact3In: TBitBtn; Fact3Out: TBitBtn; Factor1Edit: TEdit; Factor2Edit: TEdit; Factor3Edit: TEdit; PlotGroup: TGroupBox; PostHocGroup: TGroupBox; InteractChk: TCheckBox; Label1: TLabel; Label3: TLabel; Label4: TLabel; NewmanKeulsChk: TCheckBox; OverallAlphaEdit: TEdit; PostAlphaEdit: TEdit; ScheffeChk: TCheckBox; StaticText1: TStaticText; StaticText2: TStaticText; StaticText3: TStaticText; StaticText4: TStaticText; PostHocPage: TTabSheet; TukeyBChk: TCheckBox; TukeyHSDChk: TCheckBox; TukeyKramerChk: TCheckBox; VarList: TListBox; procedure DepInClick(Sender: TObject); procedure DepOutClick(Sender: TObject); procedure Fact1InClick(Sender: TObject); procedure Fact1OutClick(Sender: TObject); procedure Fact2InClick(Sender: TObject); procedure Fact2OutClick(Sender: TObject); procedure Fact3InClick(Sender: TObject); procedure Fact3OutClick(Sender: TObject); procedure Plot3DChkChange(Sender: TObject); procedure ShowPlotsChkChange(Sender: TObject); procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); private { private declarations } N: integer; ColNoSelected: IntDyneVec; Nf1cells, Nf2cells, Nf3cells: Integer; minf1, maxf1, minf2, maxf2, minf3, maxf3, nofactors: integer; NoGrpsA, NoGrpsB, NoGrpsC: integer; SSDep, SSErr, SSF1, SSF2, SSF3, SSF1F2, SSF1F3, SSF2F3: double; MSDep, MSErr, MSF1, MSF2, MSF3, MSF1F2, MSF1F3, MSF2F3: double; DFTot, DFErr, DFF1, DFF2, DFF3, DFF1F2, DFF1F3, DFF2F3: double; Omega, OmegaF1, OmegaF2, OmegaF3, OmegaF1F2: double; OmegaF1F3, OmegaF2F3: double; FF1, FF2, FF1F2, ProbF1, ProbF2, ProbF3, ProbF1F2, ProbF1F3: double; FF3, FF2F3, FF1F3, ProbF2F3: double; MeanDep: Double; SSNonAdd, SSBalance,MSNonAdd, MSBalance, GrandMean, DFBalance: double; FNonAdd, ProbNonAdd: double; 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 // DepValues: DblDyneVec; wsum, wx2: DblDyneCube; ncnt : IntDyneCube; equal_grp : boolean; // check for equal groups for post-hoc tests procedure Init; procedure GetLevels(out DepValues, F1Values, F2Values, F3Values: DblDyneVec); procedure Init2Way; function Calc2Way(const DepValues, F1Values, F2Values: DblDyneVec): Boolean; procedure TwoWayTable; procedure TwoWayContrasts; procedure TwoWayPlot; procedure Init3Way; function Calc3Way(const DepValues, F1Values, F2Values, F3Values: DblDyneVec): Boolean; procedure ThreeWayTable; procedure ThreeWayContrasts; procedure ThreeWayPlot; 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 OneCaseAnovaForm: TOneCaseAnovaForm; implementation {$R *.lfm} uses Math, TACustomSource, TASeries, TALegend, TAChartUtils, Utils, MathUnit, MatrixUnit, GridProcs, ChartFrameUnit; { TOneCaseAnovaForm } constructor TOneCaseAnovaForm.Create(AOwner: TComponent); var panel: TPanel; lbl: TLabel; begin inherited; OverallAlphaEdit.Text := FormatFloat('0.00', DEFAULT_ALPHA_LEVEL); PostAlphaEdit.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); panel := TPanel.Create(FChartFrame.ChartToolbar); lbl := TLabel.Create(panel); lbl.Parent := panel; lbl.Caption := 'Plots:'; FChartCombobox := TCombobox.Create(panel); FChartCombobox.Parent := panel; FChartCombobox.Style := csDropdownList; FChartCombobox.DropdownCount := 24; FChartCombobox.Items.Clear; FChartCombobox.Constraints.MinWidth := 300; lbl.AnchorSideLeft.Side := asrTop; lbl.AnchorSideLeft.Control := panel; lbl.AnchorSideTop.Side := asrCenter; lbl.AnchorSideTop.Control := FChartCombobox; FChartCombobox.AnchorSideLeft.Control := lbl; FChartCombobox.AnchorSideLeft.Side := asrBottom; FChartCombobox.BorderSpacing.Left := 6; FChartCombobox.BorderSpacing.Around := 2; panel.Parent := FChartFrame.ChartToolbar; panel.AutoSize := true; panel.BevelInner := bvNone; panel.BevelOuter := bvNone; FChartFrame.Chart.Margins.Bottom := 0; PageControl.ActivePageIndex := 0; end; procedure TOneCaseAnovaForm.AdjustConstraints; begin inherited; ParamsPanel.Constraints.MinHeight := InteractChk.Top + InteractChk.Height + PostHocGroup.BorderSpacing.Top + PostHocGroup.Height + PostHocGroup.BorderSpacing.Bottom + PostAlphaEdit.Height + ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height; ParamsPanel.Constraints.MinWidth := Max( 4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left, PostHocGroup.Width + PlotGroup.BorderSpacing.Left + PlotGroup.Width ); FChartFrame.ChartToolbar.ButtonHeight := Max( FChartFrame.ChartToolbar.ButtonHeight, FChartCombobox.Height + 2*FChartCombobox.BorderSpacing.Around ); end; procedure TOneCaseAnovaForm.Reset; var i: integer; begin inherited; VarList.Clear; DepVarEdit.Text := ''; Factor1Edit.Text := ''; Factor2Edit.Text := ''; Factor3Edit.Text := ''; //PlotMeans.Checked := false; ScheffeChk.Checked := false; TukeyHSDChk.Checked := false; TukeyBChk.Checked := false; TukeyKramerChk.Checked := false; NewmanKeulsChk.Checked := false; // BonferroniChk.Checked := false; for i := 1 to NoVariables do VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); UpdateBtnStates; end; procedure TOneCaseAnovaForm.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); end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.DepOutClick(Sender: TObject); begin if DepVarEdit.Text <> '' then begin VarList.Items.Add(DepVarEdit.Text); DepVarEdit.Text := ''; end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.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); end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.Fact1OutClick(Sender: TObject); begin if Factor1Edit.Text <> '' then begin VarList.Items.Add(Factor1Edit.Text); Factor1Edit.Text := ''; end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.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); end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.Fact2OutClick(Sender: TObject); begin if Factor2Edit.Text <> '' then begin VarList.Items.Add(Factor2Edit.Text); Factor2Edit.Text := ''; end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.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); end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.Fact3OutClick(Sender: TObject); begin if Factor3Edit.Text <> '' then begin VarList.Items.Add(Factor3Edit.Text); Factor3Edit.Text := ''; end; UpdateBtnStates; end; procedure TOneCaseAnovaForm.Compute; var DepValues, F1Values, F2Values, F3Values: DblDyneVec; begin // Initialize variables Init; // Get min and max of each factor code GetLevels(DepValues, F1Values, F2Values, F3Values); // Analysis case NoFactors of 2 : begin // Two-way anova Init2Way; if Calc2Way(DepValues, F1Values, F2Values) then begin TwoWayTable; TwoWayContrasts; TwoWayPlot; end; end; 3 : begin // Three way anova Init3Way; if Calc3Way(DepValues, F1Values, F2Values, F3Values) then begin ThreeWayTable; ThreeWayContrasts; ThreeWayPlot; end; end; end; end; procedure TOneCaseAnovaForm.Init; begin if Factor3Edit.Text = '' then NoFactors := 2 else NoFactors := 3; // Get column numbers of dependent variable and factors SetLength(ColNoSelected, NoFactors + 1); // +1 because DepVar is at index 0 ColNoSelected[0] := GetVariableIndex(OS3MainFrm.DataGrid, DepVarEdit.Text); ColNoSelected[1] := GetVariableIndex(OS3MainFrm.DataGrid, Factor1Edit.Text); ColNoSelected[2] := GetVariableIndex(OS3MainFrm.DataGrid, Factor2Edit.Text); if Factor3Edit.Text <> '' then ColNoSelected[3] := GetVariableIndex(OS3MainFrm.DataGrid, Factor3Edit.Text); end; procedure TOneCaseAnovaForm.GetLevels(out DepValues, F1Values, F2Values, F3Values: DblDyneVec); var mn, mx: Double; begin 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; // this can be wrong when codes are with gaps. // Extract factor 2 values F2Values := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[2], ColNoSelected); VecMaxMin(F2Values, mx, mn); MaxF2 := round(mx); MinF2 := round(mn); NF2Cells := MaxF2 - MinF2 + 1; // Extract factor 3 values if NoFactors >= 3 then begin F3Values := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[3], ColNoSelected); VecMaxMin(F3Values, mx, mn); MaxF3 := round(mx); MinF3 := round(mn); NF3Cells := MaxF3 - MinF2 + 1; end else NF3Cells := 0; end; procedure TOneCaseAnovaForm.Init2Way; begin 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; function TOneCaseAnovaForm.Calc2Way(const DepValues, F1Values, F2Values: DblDyneVec): Boolean; var i, j, grpA, grpB: integer; Constant, RowsTotCnt, ColsTotCnt: double; X, Xsq, rowMean, colmean: Double; begin // initialize matrix values NoGrpsA := MaxF1 - MinF1 + 1; NoGrpsB := MaxF2 - MinF2 + 1; RowSums := Default(DblDyneVec); ColSums := Default(DblDyneVec); RowCount := Default(IntDyneVec); ColCount := Default(IntDyneVec); counts := Default(IntDyneMat); sums := Default(DblDyneMat); vars := Default(DblDyneMat); // 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; 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; GrandMean := MeanDep / N; SSNonAdd := 0; for i := 0 to NoGrpsA - 1 do begin rowmean := RowSums[i] / RowCount[i]; for j := 0 to NoGrpsB - 1 do begin colmean := ColSums[j] / ColCount[j]; SSNonAdd := SSNonAdd + (colmean - GrandMean) * (rowmean - GrandMean) * sums[i,j]; end; end; if (N > 0) then Constant := sqr(MeanDep) / N else Constant := 0.0; SSDep := SSDep - Constant; SSF1 := SSF1 - Constant; SSF2 := SSF2 - Constant; SSErr := SSDep - (SSF1 + SSF2); SSNonAdd := (SSNonAdd * SSNonAdd) / ((SSF1 * SSF2) / (NoGrpsA * NoGrpsB) ); MSNonAdd := SSNonAdd; SSBalance := SSErr - SSNonAdd; if ((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; DFErr := DFF1 * DFF2; DFBalance := DFErr - 1; MSF1 := SSF1 / DFF1; MSF2 := SSF2 / DFF2; MSErr := SSErr / DFErr; MSDep := SSDep / DFTot; MSBalance := SSBalance / DFBalance; OmegaF1 := (SSF1 - DFF1 * MSErr) / (SSDep + MSErr); OmegaF2 := (SSF2 - DFF2 * MSErr) / (SSDep + MSErr); Omega := OmegaF1 + OmegaF2; MeanDep := MeanDep / N; // F tests for fixed effects FF1 := abs(MSF1 / MSErr); FF2 := abs(MSF2 / MSErr); if (MSBalance > 0.0) then FNonAdd := MSNonAdd / MSBalance else FNonAdd := 0.0; ProbF1 := ProbF(FF1,DFF1,DFErr); ProbF2 := ProbF(FF2,DFF2,DFErr); ProbNonAdd := ProbF(FNonAdd,1.0,DFBalance); if (ProbF1 > 1.0) then ProbF1 := 1.0; if (ProbF2 > 1.0) then ProbF2 := 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 (Omega < 0.0) then Omega := 0.0; Result := True; end; procedure TOneCaseAnovaForm.TwoWayTable; var lReport: TStrings; i, j, groupsize: integer; MinVar, MaxVar, sumvars, sumDFrecip, XBar, V, S, RowSS, ColSS: double; sumfreqlogvar: double; begin lReport := TStringList.Create; try lReport.Add('TWO-WAY ANALYSIS OF VARIANCE'); lReport.Add(''); lReport.Add('Variable analyzed: %s', [DepVarEdit.Text]); lReport.Add(''); lReport.Add('Factor A (rows) variable: %s', [Factor1Edit.Text]); lReport.Add('Factor B (columns) variable: %s', [Factor2Edit.Text]); lReport.Add(''); lReport.Add('SOURCE D.F. SS MS F PROB.> F Omega Squared'); lReport.Add('-------------- ---- ------------ ------------ ---------- -------- -------------'); lReport.Add('Among Rows %4.0f %12.3f %12.3f %10.3f %8.3f %10.3f', [DFF1, SSF1, MSF1, FF1, ProbF1, OmegaF1]); lReport.Add('Among Columns %4.0f %12.3f %12.3f %10.3f %8.3f %10.3f', [DFF2, SSF2, MSF2, FF2, ProbF2, OmegaF2]); lReport.Add('Residual %4.0f %12.3f %12.3f', [DFErr, SSErr, MSErr]); lReport.Add(' NonAdditivity %4.0f %12.3f %12.3f %10.3f %8.3f', [1.0, SSNonAdd, MSNonAdd, FNonAdd, ProbNonAdd]); lReport.Add(' Balance %4.0f %12.3f %12.3f', [DFBalance, SSBalance, MSBalance]); lReport.Add('Total %4.0f %12.3f %12.3f', [DFTot, SSDep, MSDep]); lReport.Add(''); lReport.Add('Omega squared for combined effects: %10.3f', [Omega]); lReport.Add(''); lReport.Add('DESCRIPTIVE STATISTICS'); lReport.Add(''); lReport.Add('GROUP Row Col. N MEAN VARIANCE STD.DEV.'); lReport.Add('----- --- ---- ---- -------- -------- ---------'); groupsize := ceil(counts[0,0]); equal_grp := true; MaxVar := -1e308; MinVar := 1e308; sumvars := 0.0; sumfreqlogvar := 0.0; sumDFrecip := 0.0; // Display cell means, variances, standard deviations 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] - sqr(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.0); sumFreqLogVar := sumFreqLogVar + (counts[i,j] - 1.0) * ln(V); if (counts[i, j] <> groupsize) then equal_grp := false; end else XBar := sums[i, j]; lReport.Add('Cell %3d %3d %3d %8.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]; // OrdMeansA[i] := XBar; RowSS := 0.0; for j := 0 to NoGrpsB - 1 do RowSS := RowSS + vars[i,j]; V := RowSS - sqr(RowSums[i]) / RowCount[i]; V := V / (RowCount[i] - 1.0); S := sqrt(V); lReport.Add('Row %3d %3d %8.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]; // OrdMeansB[j] := XBar; ColSS := 0.0; for i := 0 to NoGrpsA - 1 do ColSS := ColSS + vars[i,j]; if (ColCount[j] > 0) then V := ColSS - sqr(ColSums[j]) / ColCount[j]; if (ColCount[j] > 1) then V := V / (ColCount[j] - 1.0); if (V > 0.0) then S := sqrt(V); lReport.Add('Col %3d %3d %8.3f %8.3f %8.3f', [minf2+j, ColCount[j], XBar, V, S]); end; lReport.Add('TOTAL %3d %8.3f %8.3f %8.3f', [N, MeanDep, MSDep, sqrt(MSDep)]); FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TOneCaseAnovaForm.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 TOneCaseAnovaForm.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'); 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'); 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.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.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 TOneCaseAnovaForm.TwoWayPlot; 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; PopulateChartCombobox(false); FChartCombobox.OnChange := @SelectTwoWayPlot; SelectTwoWayPlot(nil); ChartPage.TabVisible := true; end; procedure TOneCaseAnovaForm.Init3Way; begin RowSums := nil; ColSums := nil; RowCount := nil; ColCount := nil; SlcSums := nil; SlcCount := nil; wsum := nil; wx2 := nil; ncnt := 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(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; function TOneCaseAnovaForm.Calc3Way(const DepValues, F1Values, F2Values, F3Values: DblDyneVec): Boolean; var i, j, k, grpA, grpB, grpC: integer; Constant, RowsTotCnt, ColsTotCnt, SlcsTotCnt, p, n2: double; X, Xsq, rowMean, colMean, sliceMean: Double; begin // initialize matrix values 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; GrandMean := 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 + SlcSums[k] * SlcSums[k] / SlcCount[k]; SlcsTotCnt := SlcsTotCnt + SlcCount[k]; end; SSF3 := SSF3 - Constant; // Get ss for row x col interaction p := 0.0; n2 := 0.0; SSF1F2 := 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 p := p + wsum[i,j,k]; n2 := n2 + ncnt[i,j,k]; end; SSF1F2 := SSF1F2 + p * p / n2; end; end; SSF1F2 := SSF1F2 - SSF1 - SSF2 - Constant; // Get ss for row x slice interaction SSF1F3 := 0; p := 0.0; n2 := 0.0; for i := 0 to NoGrpsA - 1 do begin for k := 0 to NoGrpsC - 1 do begin for j := 0 to NoGrpsB - 1 do begin p := p + wsum[i,j,k]; n2 := n2 + ncnt[i,j,k]; end; SSF1F3 := SSF1F3 + p * p / n2; end; end; SSF1F3 := SSF1F3 - SSF1 - SSF3 - Constant; // Get ss for columns x slices interaction SSF2F3 := 0; p := 0.0; n2 := 0.0; for j := 0 to NoGrpsB - 1 do begin for k := 0 to NoGrpsC - 1 do begin for i := 0 to NoGrpsA - 1 do begin p := p + wsum[i,j,k]; n2 := n2 + ncnt[i,j,k]; end; SSF2F3 := SSF2F3 + p * p / n2; p := 0.0; n2 := 0.0; end; end; SSF2F3 := SSF2F3 - SSF2 - SSF3 - Constant; SSNonAdd := 0; for i := 0 to NoGrpsA - 1 do begin rowmean := RowSums[i] / RowCount[i]; for j := 0 to NoGrpsB - 1 do begin colmean := ColSums[j] / ColCount[j]; for k := 0 to NoGrpsC - 1 do begin sliceMean := SlcSums[k] / SlcCount[k]; SSNonAdd := SSNonAdd + (colmean-GrandMean) * (rowmean-GrandMean) * (sliceMean-GrandMean) * wsum[i,j,k]; end; end; end; SSDep := SSDep - Constant; if InteractChk.Checked then SSErr := SSDep - (SSF1 + SSF2 + SSF3 + SSF1F2 + SSF1F3 + SSF2F3) else SSErr := SSDep - (SSF1 + SSF2 + SSF3); SSNonAdd := SSNonAdd * SSNonAdd / (SSF1 * SSF2 * SSF3); SSNonAdd := SSNonAdd * NoGrpsA * NoGrpsB * NoGrpsC * NoGrpsA * NoGrpsB * NoGrpsC; MSNonAdd := SSNonAdd; SSBalance := SSErr - SSNonAdd; if ((SSF1 < 0.0) or (SSF2 < 0.0) or (SSF3 < 0.0) or (SSF1F2 < 0.0) or (SSF1F3 < 0.0) or (SSF2F3 < 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; if InteractChk.Checked then DFErr := DFTot - DFF1 - DFF2 - DFF3 - DFF1F2 - DFF1F3 - DFF2F3 else DFErr := DFTot - DFF1 - DFF2 - DFF3; DFBalance := DFErr - 1; MSF1 := SSF1 / DFF1; MSF2 := SSF2 / DFF2; MSF3 := SSF3 / DFF3; MSF1F2 := SSF1F2 / DFF1F2; MSF1F3 := SSF1F3 / DFF1F3; MSF2F3 := SSF2F3 / DFF2F3; MSErr := SSErr / DFErr; MSDep := SSDep / DFTot; MSBalance := SSBalance / DFBalance; 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); if InteractChk.Checked then Omega := OmegaF1 + OmegaF2 + OmegaF3 + OmegaF1F2 + OmegaF1F3 + OmegaF2F3 else Omega := OmegaF1 + OmegaF2 + OmegaF3; MeanDep := MeanDep / N; FF1 := abs(MSF1 / MSErr); FF2 := abs(MSF2 / MSErr); FF3 := abs(MSF3 / MSErr); FF1F2 := abs(MSF1F2 / MSErr); FF1F3 := abs(MSF1F3 / MSErr); FF2F3 := abs(MSF2F3 / MSErr); if (MSBalance > 0.0) then FNonAdd := MSNonAdd / MSBalance else FNonAdd := 0.0; 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); ProbNonAdd := probf(FNonAdd,1.0,DFBalance); 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; // 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 (Omega < 0.0) then Omega := 0.0; Result := true; end; procedure TOneCaseAnovaForm.ThreeWayTable; var lReport: TStrings; i, j, k: integer; XBar, V, S, RowSS, ColSS, SlcSS: double; begin lReport := TStringList.Create; try lReport.Add('THREE-WAY ANALYSIS OF VARIANCE'); lReport.Add(''); lReport.Add('Variable analyzed: %s', [DepVarEdit.Text]); lReport.Add(''); lReport.Add('Factor A (rows) variable: %s', [Factor1Edit.Text]); lReport.Add('Factor B (columns) variable: %s', [Factor2Edit.Text]); lReport.Add('Factor C (slices) variable: %s', [Factor3Edit.Text]); lReport.Add(''); lReport.Add('SOURCE D.F. SS MS F PROB.> F Omega Squared'); lReport.Add(''); lReport.Add('Among Rows %4.0f %12.4f %12.4f %12.4f %6.3f %6.3f', [DFF1, SSF1, MSF1, FF1, ProbF1, OmegaF1]); lReport.Add('Among Columns %4.0f %12.4f %12.4f %12.4f %6.3f %6.3f', [DFF2, SSF2, MSF2, FF2, ProbF2, OmegaF2]); lReport.Add('Among Slices %4.0f %12.4f %12.4f %12.4f %6.3f %6.3f', [DFF3, SSF3, MSF3, FF3, ProbF3, OmegaF3]); if InteractChk.Checked then begin lReport.Add('A x B Inter. %4.0f %12.4f %12.4f %12.4f %6.3f %6.3f', [DFF1F2, SSF1F2, MSF1F2, FF1F2, ProbF1F2, OmegaF1F2]); lReport.Add('A x C Inter. %4.0f %12.4f %12.4f %12.4f %6.3f %6.3f', [DFF1F3, SSF1F3, MSF1F3, FF1F3, ProbF1F3, OmegaF1F3]); lReport.Add('B x C Inter. %4.0f %12.4f %12.4f %12.4f %6.3f %6.3f', [DFF2F3, SSF2F3, MSF2F3, FF2F3, ProbF2F3, OmegaF2F3]); end; lReport.Add('Residual %4.0f %12.4f %12.4f', [DFErr, SSErr, MSErr]); lReport.Add(' NonAdditivity %4.0f %12.4f %12.4f %12.4f %6.3f', [1.0, SSNonAdd, MSNonAdd, FNonAdd, ProbNonAdd]); lReport.Add(' Balance %4.0f %12.4f %12.4f', [DFBalance, SSBalance, MSBalance]); lReport.Add('Total %4.0f %12.4f %12.4f', [DFTot, SSDep, MSDep]); lReport.Add(''); lReport.Add('Omega squared for combined effects := %8.4f', [Omega]); lReport.Add(''); lReport.Add(''); lReport.Add('DESCRIPTIVE STATISTICS'); lReport.Add(''); lReport.Add('GROUP N MEAN VARIANCE STD.DEV.'); equal_grp := true; //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 %3d %8.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 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 %3d %8.3f %8.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 %3d %3d %8.3f %8.3f %8.3f', [minf3+k, SlcCount[k], XBar, V, S]); end; lReport.Add('TOTAL %3d %8.3f %8.3f %8.3f', [N, MeanDep, MSDep, sqrt(MSDep)]); FReportFrame.DisplayReport(lReport); finally lReport.Free; 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 TOneCaseAnovaform.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; { OnChange event handler for the ChartCombobox. Displays the chart associated with the current ItemIndex. } procedure TOneCaseAnovaForm.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 TOneCaseAnovaForm.ThreeWayPlot; begin if not ShowPlotsChk.Checked then begin ChartPage.TabVisible := false; exit; end; FChartFrame.Clear; // this destroys the series FChartFrame.SetYTitle('Means'); 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; PopulateChartCombobox(true); FChartCombobox.OnChange := @SelectThreeWayPlot; SelectThreeWayPlot(nil); ChartPage.TabVisible := true; end; procedure TOneCaseAnovaForm.TwoWayContrasts; var lReport: TStrings; i, j, totalCells: integer; value, alpha, posthocAlpha: double; cellCnts: IntDyneVec = nil; cellVars: DblDyneVec = nil; cellSums: DblDyneVec = nil; RowSS, ColSS: double; variances: DblDyneVec = nil; begin if not (ScheffeChk.Checked or TukeyHSDChk.Checked or TukeyBChk.Checked or TukeyKramerChk.Checked or NewmanKeulsChk.Checked) then begin PostHocPage.TabVisible := false; exit; end; totalCells := NF1Cells + NF2Cells + NF3Cells; // allocate space SetLength(CellCnts, totalCells); // array of cell counts SetLength(CellVars, totalCells); // arrray of cell sums of squares variances SetLength(CellSums, totalCells); // array of cell sums means SetLength(Variances, totalCells); alpha := StrToFloat(OverallAlphaEdit.Text); postHocAlpha := StrToFloat(PostAlphaEdit.Text); lReport := TStringList.Create; try // row comparisons if (Nf1cells > 2) and (ProbF1 < alpha) 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]; variances[i] := variances[i] / (RowCount[i] - 1.0); end; if lReport.Count > 0 then lReport.Add(''); 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 (BonferroniChk.Checked) then // Bonferroni(RowSums,RowCount,variances,minf1,maxf1, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,RowSums,RowCount,minf1,maxf1,Alpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, RowSums, RowCount, minf1, maxf1, posthocAlpha, lReport); end; // column comparisons if (Nf2cells > 2) and (ProbF2 < alpha) 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 - (ColSums[j] * ColSums[j] / ColCount[j]); variances[j] := variances[j] / (ColCount[j] - 1.0); 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 (BonferroniChk.Checked) then // Bonferroni(ColSums,ColCount,variances,minf2,maxf2, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,ColSums,ColCount,minf2,maxf2,Alpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, ColSums, ColCount, minf2, maxf2, posthocAlpha, lReport); end; // simple effects for columns within each row if (ProbF3 < alpha) 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 (BonferroniChk.Checked) then // Bonferroni(cellsums,cellcnts,cellvars,minf2,maxf2, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,cellsums,cellcnts,minf2,maxf2,0.05, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, minf2, maxf2, posthocAlpha, lReport); end; end; // simple effects for rows within each column if (ProbF3 < alpha) 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 (BonferroniChk.Checked) then // Bonferroni(cellsums,cellcnts,cellvars,minf1,maxf1, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,cellsums,cellcnts,minf1,maxf1,0.05, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, minf1, maxf1, posthocAlpha, lReport); end; end; FPostHocReportFrame.DisplayReport(lReport); PosthocPage.TabVisible := true; finally lReport.Clear; end; end; procedure TOneCaseAnovaForm.ThreeWayContrasts; var lReport: TStrings; i, j, k, totalCells: integer; value, alpha, posthocAlpha: double; RowSS, ColSS, SlcSS: double; cellCnts: IntDyneVec = nil; cellVars: DblDyneVec = nil; cellSums: DblDyneVec = nil; variances: DblDyneVec = nil; begin if not (ScheffeChk.Checked or TukeyHSDChk.Checked or TukeyBChk.Checked or TukeyKramerChk.Checked or NewmanKeulsChk.Checked) then begin PosthocPage.TabVisible := false; exit; end; alpha := StrToFloat(OverallAlphaEdit.Text); posthocAlpha := StrToFloat(PostAlphaEdit.Text); totalCells := NF1Cells + NF2Cells + NF3Cells; // allocate space SetLength(cellCnts, totalCells); // array of cell counts SetLength(cellVars, totalCells); // arrray of cell sums of squares variances SetLength(cellSums, totalCells); // array of cell sums means SetLength(variances, totalCells); lReport := TStringList.Create; try // row comparisons if (NF1cells > 2) and (ProbF1 < alpha) 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]; variances[i] := variances[i] / (RowCount[i] - 1); end; if lReport.Count > 0 then lReport.Add(''); 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 (BonferroniChk.Checked) then // Bonferroni(RowSums,RowCount,variances,minf1,maxf1, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,RowSums,RowCount,minf1,maxf1,Alpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, RowSums, RowCount, minf1, maxf1, posthocAlpha, lReport); end; // column comparisons if (Nf2cells > 2) and (ProbF2 < alpha) 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]; variances[j] := variances[j] / (ColCount[j] - 1.0); 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 (BonferroniChk.Checked) then // Bonferroni(ColSums,ColCount,variances,minf2,maxf2, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,ColSums,ColCount,minf2,maxf2,Alpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, ColSums, ColCount, minf2, maxf2, posthocAlpha, lReport); end; // slice comparisons if (Nf3cells > 2) and (ProbF3 < alpha) 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]; variances[k] := variances[k] / (SlcCount[k] - 1.0); 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 (BonferroniChk.Checked) then // Bonferroni(SlcSums,SlcCount,variances,minf3,maxf3, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,SlcSums,SlcCount,minf3,maxf3,Alpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, SlcSums, SlcCount, minf3, maxf3, posthocAlpha, lReport); end; // simple effects for columns within each row if (ProbF1F2 < alpha) then begin lReport.Add(''); lReport.Add('COMPARISONS AMONG COLUMNS WITHIN EACH ROW'); for i := 0 to Nf1cells - 1 do begin if lReport.Count > 0 then lReport.Add(''); lReport.Add('ROW %d COMPARISONS', [i+1]); // move cell sums && counts to cellsums && 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 (BonferroniChk.Checked) then // Bonferroni(cellsums,cellcnts,cellvars,minf2,maxf2, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,cellsums,cellcnts,minf2,maxf2,0.05, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, minf2, maxf2, posthocAlpha, lReport); end; end; // simple effects for rows within each column if (ProbF1F2 < alpha) 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 && counts to cellsums && 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 (BonferroniChk.Checked) then // Bonferroni(cellsums,cellcnts,cellvars,minf1,maxf1, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,cellsums,cellcnts,minf1,maxf1,0.05, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, minf1, maxf1, posthocAlpha, lReport); end; end; // simple effects for columns within each slice if (ProbF2F3 < alpha) 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 && counts to cellsums && 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 := 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 (BonferroniChk.Checked) then // Bonferroni(cellsums, cellcnts, cellvars, minf2, maxf2, posthocAlpha, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr, DFErr, cellsums, cellcnts, minf2, maxf2, posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, minf2, maxf2, posthocAlpha, lReport); end; end; // simple effects for rows within each slice if (ProbF1F3 < alpha) 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 && counts to cellsums && 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 := 1e308; 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 (BonferroniChk.Checked) then // Bonferroni(cellsums,cellcnts,cellvars,minf1,maxf1, posthocAlpha, lReport); // if (OrthogonalChk.Checked) then // CONTRASTS(MSErr,DFErr,cellsums,cellcnts,minf1,maxf1,posthocAlpha, lReport); if NewmanKeulsChk.Checked and equal_grp then Newman_Keuls(MSErr, DFErr, value, cellsums, cellcnts, minf1, maxf1, posthocAlpha, lReport); end; end; FPosthocReportFrame.DisplayReport(lReport); PosthocPage.TabVisible := true; finally lReport.Free; end; end; procedure TOneCaseAnovaForm.Plot3DChkChange(Sender: TObject); begin if FSeries <> nil then begin if Plot3DChk.Checked then FSeries.Depth := 20 else FSeries.Depth := 0; end; end; procedure TOneCaseAnovaForm.ShowPlotsChkChange(Sender: TObject); begin Plot3DChk.Enabled := ShowPlotsChk.Checked; end; procedure TOneCaseAnovaForm.UpdateBtnStates; var i: Integer; lSelected: Boolean; begin inherited; if FPosthocReportFrame <> nil then FPostHocReportFrame.UpdateBtnStates; lSelected := false; for i:=0 to VarList.Items.Count-1 do if VarList.Selected[i] then begin lSelected := true; break; end; DepIn.Enabled := lSelected and (DepVarEdit.Text = ''); Fact1In.Enabled := lSelected and (Factor1Edit.Text = ''); Fact2In.Enabled := lSelected and (Factor2Edit.Text = ''); Fact3In.Enabled := lSelected and (Factor3Edit.Text = ''); DepOut.Enabled := DepVarEdit.Text <> ''; Fact1Out.Enabled := Factor1Edit.Text <> ''; Fact2Out.Enabled := Factor2Edit.Text <> ''; Fact3Out.Enabled := Factor3Edit.Text <> ''; end; function TOneCaseAnovaForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; var X: Double; begin Result := false; if (DepVarEdit.Text = '') then begin AMsg := 'Dependent variable is not selected.'; AControl := VarList; exit; end; if (Factor1Edit.Text = '') then begin AMsg := 'Factor 1 variable is not selected.'; AControl := VarList; exit; end; if (Factor2Edit.Text = '') then begin AMsg := 'Factor 2 variable is not selected.'; AControl := VarList; exit; end; // Factor 3 can be left empty to distinguish betwee two-way and three-way ANOVA. if (OverallAlphaEdit.Text = '') then begin AControl := OverallAlphaEdit; AMsg := 'No value specified for overall alpha.'; exit; end; if not TryStrToFloat(OverallAlphaEdit.Text, X) then begin AControl := OverallAlphaEdit; AMsg := 'Overall alpha is not a valid number.'; exit; end; if (PostAlphaEdit.Text = '') then begin AControl := PostAlphaEdit; AMsg := 'No value specified for post-hoc alpha.'; exit; end; if not TryStrToFloat(PostAlphaEdit.Text, x) then begin AControl := PostAlphaEdit; AMsg := 'Post-hoc alpha is not a valid number.'; exit; end; Result := true; end; procedure TOneCaseAnovaForm.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 TOneCaseAnovaForm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; end.