Files
lazarus-ccr/applications/lazstats/source/forms/analysis/comparisons/bnestaunit.pas
2020-11-22 22:29:04 +00:00

722 lines
18 KiB
ObjectPascal

{ Test file: ABNested.laz (imported from OpenStat sample data zip file)
Dependent: Dep
Factor A : A
Factor B : B
}
unit BNestAUnit;
{$MODE objfpc}{$H+}
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, LCLVersion,
StdCtrls, Buttons, ExtCtrls, ComCtrls,
TACustomSeries,
MainUnit, Globals, ReportFrameUnit, BasicStatsReportAndChartFormUnit;
type
{ TBNestedAForm }
TBNestedAForm = class(TBasicStatsReportAndChartForm)
ACodesEdit: TEdit;
AInBtn: TBitBtn;
AOutBtn: TBitBtn;
BCodesEdit: TEdit;
BInBtn: TBitBtn;
BOutBtn: TBitBtn;
Plot3DChk: TCheckBox;
PlotOptionsGroup: TGroupBox;
RandomBChk: TCheckBox;
DepInBtn: TBitBtn;
DepOutBtn: TBitBtn;
DepEdit: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
MeansPage: TTabSheet;
ShowPlotsChk: TCheckBox;
VarList: TListBox;
procedure AInBtnClick(Sender: TObject);
procedure AOutBtnClick(Sender: TObject);
procedure BInBtnClick(Sender: TObject);
procedure BOutBtnClick(Sender: TObject);
procedure DepInBtnClick(Sender: TObject);
procedure DepOutBtnClick(Sender: TObject);
procedure Plot3DChkChange(Sender: TObject);
procedure ShowPlotsChkChange(Sender: TObject);
procedure VarListDblClick(Sender: TObject);
procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean);
private
SS, SumSqr, CellMeans, CellSDs : DblDyneMat;
CellCount : IntDyneMat;
ASS, BSS, ASumSqr, BSumSqr, AMeans, BMeans, ASDs : DblDyneVec;
ACount, BCount : IntDyneVec;
MinA, MinB, MaxA, MaxB, NoALevels, NoBLevels: integer;
SSTot, SumSqrTot, TotMean, MSTot, SSA, MSA, SSB, MSB, SSW, MSW : double;
TotN, dfA, dfBwA, dfwcell, dftotal : integer;
function GetVariables(out AValues, BValues, DepValues: DblDyneVec): Boolean;
procedure GetMemory;
procedure GetSums(const AValues, BValues, DepValues: DblDyneVec);
procedure ShowMeans;
procedure GetResults;
procedure ShowResults;
procedure ReleaseMemory;
procedure TwoWayPlot;
private
FMeansReportFrame: TReportFrame;
FChartCombobox: TCombobox;
FSeries: TChartSeries;
procedure PopulateChartCombobox;
procedure SelectPlot(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
BNestedAForm: TBNestedAForm;
implementation
{$R *.lfm}
uses
Math,
TAChartUtils, TACustomSource, TALegend, TASeries,
Utils, MathUnit, MatrixUnit, GridProcs, ChartFrameUnit;
{ TBNestedAForm }
constructor TBNestedAForm.Create(AOwner: TComponent);
begin
inherited;
FMeansReportFrame := TReportFrame.Create(MeansPage);
FMeansReportFrame.Parent := MeansPage;
FMeansReportFrame.Align := alClient;
InitToolbar(FMeansReportFrame.ReportToolbar, tpTop);
MeansPage.PageIndex := 1;
FChartFrame.Chart.Margins.Bottom := 0;
FChartFrame.Chart.BottomAxis.AxisPen.Visible := true;
FChartFrame.Chart.BottomAxis.ZPosition := 1;
FChartFrame.Chart.BottomAxis.Grid.Visible := false;
AddComboboxToToolbar(FChartFrame.ChartToolbar, 'Plots:', FChartCombobox);
FChartCombobox.OnSelect := @SelectPlot;
PageControl.ActivePageIndex := 0;
end;
procedure TBNestedAForm.AdjustConstraints;
begin
inherited;
ParamsPanel.Constraints.MinWidth := Max(
4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left,
Max(Label3.Width, RandomBChk.Width) * 2 + AInBtn.Width + 2*VarList.BorderSpacing.Right
);
ParamsPanel.Constraints.MinHeight := RandomBChk.Top + RandomBChk.Height +
VarList.BorderSpacing.Bottom + PlotOptionsGroup.Height +
ButtonBevel.Height + CloseBtn.Height + CloseBtn.BorderSpacing.Top;
end;
procedure TBNestedAForm.AInBtnClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (ACodesEdit.Text = '') then
begin
ACodesEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
end;
UpdateBtnStates;
end;
procedure TBNestedAForm.AOutBtnClick(Sender: TObject);
begin
if ACodesEdit.Text <> '' then
begin
VarList.Items.Add(ACodesEdit.Text);
ACodesEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TBNestedAForm.BInBtnClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (BCodesEdit.Text = '') then
begin
BCodesEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
end;
UpdateBtnStates;
end;
procedure TBNestedAForm.BOutBtnClick(Sender: TObject);
begin
if BCodesEdit.Text <> '' then
begin
VarList.Items.Add(BCodesEdit.Text);
BCodesEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TBNestedAForm.Compute;
var
dataA: DblDyneVec = nil;
dataB: DblDyneVec = nil;
dataDep: DblDyneVec = nil;
begin
if GetVariables(dataA, dataB, dataDep) then
begin
GetMemory;
GetSums(dataA, dataB, dataDep);
ShowMeans;
GetResults;
ShowResults;
TwoWayPlot;
ReleaseMemory;
end;
end;
procedure TBNestedAForm.DepInBtnClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (DepEdit.Text = '') then
begin
DepEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
end;
UpdateBtnStates;
end;
procedure TBNestedAForm.DepOutBtnClick(Sender: TObject);
begin
if DepEdit.Text <> '' then
begin
VarList.Items.Add(DepEdit.Text);
DepEdit.Text := '';
end;
UpdateBtnStates;
end;
procedure TBNestedAForm.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 TBNestedAForm.ShowPlotsChkChange(Sender: TObject);
begin
ChartPage.TabVisible := ShowPlotsChk.Checked;
Plot3DChk.Enabled := ShowPlotsChk.Checked;
end;
procedure TBNestedAForm.GetMemory;
begin
SS := MatCreate(NoBLevels,NoALevels);
SumSqr := MatCreate(NoBLevels,NoALevels);
CellCount := IntMatCreate(NoBLevels,NoALevels);
CellMeans := MatCreate(NoBLevels,NoALevels);
CellSDs := MatCreate(NoBLevels,NoALevels);
ASS := VecCreate(NoALevels);
BSS := VecCreate(NoBLevels);
ASumSqr := VecCreate(NoALevels);
BSumSqr := VecCreate(NoBLevels);
AMeans := VecCreate(NoALevels);
BMeans := VecCreate(NoBLevels);
ACount := IntVecCreate(NoALevels);
BCount := IntVecCreate(NoBLevels);
ASDs := VecCreate(NoALevels);
end;
procedure TBNestedAForm.GetResults;
VAR
temp, constant : double;
NoBLevelsInA, BLevCount, i, j, celln : integer;
begin
celln := 0;
for i := 0 to NoALevels-1 do
for j := 0 to NoBLevels-1 do
if CellCount[j,i] > celln then celln := CellCount[j,i];
// Assume all cells have same n size
// Get number of levels in A
BLevCount := 0;
for i := 0 to NoALevels-1 do
begin
NoBLevelsInA := 0;
for j := 0 to NoBLevels-1 do
if CellCount[j,i] > 0 then NoBLevelsInA := NoBLevelsInA + 1;
if NoBLevelsInA > BLevCount then BLevCount := NoBLevelsInA;
end;
dfA := NoALevels - 1;
dfBwA := NoALevels * (BLevCount - 1);
dfwcell := NoALevels * BLevCount * (celln - 1);
dftotal := TotN - 1;
constant := SumSqrTot / TotN;
SSTot := SSTot - constant;
MSTot := SSTot / dftotal;
SSA := 0.0;
for i := 0 to NoALevels-1 do SSA := SSA + (ASumSqr[i] / ACount[i]);
temp := SSA;
SSA := SSA - constant;
MSA := SSA / dfA;
SSB := 0.0;
for i := 0 to NoALevels - 1 do
for j := 0 to NoBLevels-1 do
if CellCount[j,i] > 0 then SSB := SSB + (SumSqr[j,i] / CellCount[j,i]);
SSB := SSB - temp;
MSB := SSB / dfBwA;
SSW := SSTot - SSA - SSB;
MSW := SSW / dfwcell;
end;
procedure TBNestedAForm.GetSums(const AValues, BValues, DepValues: DblDyneVec);
var
AIndex, BIndex, i, j: integer;
YValue, YValueSqr: double;
begin
// Accumulate sums and sums of squared values
SSTot := 0;
SumSqrTot := 0;
TotN := 0;
for i := 0 to High(DepValues) do
begin
AIndex := round(AValues[i]) - MinA;
BIndex := round(BValues[i]) - MinB;
YValue := DepValues[i];
YValueSqr := YValue * YValue;
{
strvalue := Trim(OS3MainFrm.DataGrid.Cells[ACol,i]);
Aindex := round(StrToFloat(strvalue));
strvalue := Trim(OS3MainFrm.DataGrid.Cells[BCol,i]);
Bindex := round(StrToFloat(strvalue));
strvalue := Trim(OS3MainFrm.DataGrid.Cells[YCol,i]);
YValue := StrToFloat(strvalue);
Aindex := Aindex - MinA;
Bindex := Bindex - MinB;
}
SS[BIndex, AIndex] := SS[BIndex, AIndex] + YValueSqr;
SumSqr[BIndex, AIndex] := SumSqr[BIndex, AIndex] + YValue;
CellCount[BIndex, AIndex] := CellCount[BIndex, AIndex] + 1;
ACount[AIndex] := ACount[AIndex] + 1;
BCount[BIndex] := BCount[BIndex] + 1;
ASS[AIndex] := ASS[AIndex] + YValueSqr;
BSS[BIndex] := BSS[BIndex] + YValueSqr;
ASumSqr[AIndex] := ASumSqr[AIndex] + YValue;
BSumSqr[BIndex] := BSumSqr[BIndex] + YValue;
SSTot := SSTot + YValueSqr;
SumSqrTot := SumSqrTot + YValue;
TotN := TotN + 1;
end;
// Get cell means and marginal means, SDs plus square of sums
for i := 0 to NoBlevels-1 do
begin
for j := 0 to NoALevels-1 do
begin
if CellCount[i,j] > 0 then
begin
CellMeans[i,j] := SumSqr[i,j] / CellCount[i,j];
SumSqr[i,j] := SumSqr[i,j] * SumSqr[i,j];
CellSDs[i,j] := SS[i,j] - (SumSqr[i,j] / CellCount[i,j]);
CellSDs[i,j] := CellSDs[i,j] / (CellCount[i,j] - 1);
CellSDs[i,j] := Sqrt(CellSDs[i,j]);
end;
end;
end;
for i := 0 to NoBLevels-1 do
begin
BMeans[i] := BSumSqr[i] / BCount[i];
BSumSqr[i] := BSumSqr[i] * BSumSqr[i];
end;
for i := 0 to NoALevels-1 do
begin
AMeans[i] := ASumSqr[i] / ACount[i];
ASumSqr[i] := ASumSqr[i] * ASumSqr[i];
ASDs[i] := ASS[i] - (ASumSqr[i] / ACount[i]);
ASDs[i] := ASDs[i] / (ACount[i] - 1);
ASDs[i] := Sqrt(ASDs[i]);
end;
TotMean := SumSqrTot / TotN;
SumSqrTot := SumSqrTot * SumSqrTot;
end;
function TBNestedAForm.GetVariables(
out AValues, BValues, DepValues: DblDyneVec): Boolean;
var
ColNoSelected: IntDyneVec = nil;
mn, mx: Double;
msg: String;
begin
Result := false;
SetLength(ColNoSelected, 3);
ColNoSelected[0] := GetVariableIndex(OS3MainFrm.DataGrid, ACodesEdit.Text); // A
ColNoSelected[1] := GetVariableIndex(OS3MainFrm.DataGrid, BCodesEdit.Text); // B
ColNoSelected[2] := GetVariableindex(OS3MainFrm.DataGrid, DepEdit.Text); // Dependent
AValues := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[0], ColNoSelected);
if not CheckFactorCodes(ACodesEdit.Text, AValues, msg) then
begin
ErrorMsg(msg);
exit;
end;
BValues := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[1], ColNoSelected);
if not CheckFactorCodes(BCodesEdit.Text, BValues, msg) then
begin
ErrorMsg(msg);
exit;
end;
DepValues := CollectVecValues(OS3MainFrm.DataGrid, ColNoSelected[2], ColNoSelected);
if (Length(DepValues) <> Length(AValues)) or
(Length(DepValues) <> Length(BValues)) then
begin
ErrorMsg('All variables must contain equal amounts of cases.');
exit;
end;
VecMaxMin(AValues, mx, mn);
MaxA := round(mx);
MinA := round(mn);
VecMaxMin(BValues, mx, mn);
MaxB := round(mx);
MinB := round(mn);
NoALevels := MaxA - MinA + 1;
NoBLevels := MaxB - MinB + 1;
Result := true;
end;
procedure TBNestedAForm.ShowMeans;
var
lReport: TStrings;
i, j: integer;
begin
lReport := TStringList.Create;
try
lReport.Add('NESTED ANOVA by Bill Miller');
lReport.Add('');
lReport.Add('File analyzed: %s', [OS3MainFrm.FileNameEdit.Text]);
lReport.Add('Factor A: %s', [ACodesEdit.Text]);
lReport.Add('Factor B: %s', [BCodesEdit.Text]);
lReport.Add('');
lReport.Add('CELL MEANS');
lReport.Add('-------------------------------------------');
lReport.Add('A LEVEL B LEVEL MEAN STD.DEV. ');
lReport.Add('------- ------- ---------- ----------');
for i := 0 to NoALevels-1 do
for j := 0 to NoBLevels-1 do
if CellCount[j,i] > 0 then
lReport.Add('%5d %5d %10.3f %10.3f', [i+MinA, j+MinB, CellMeans[j,i], CellSDs[j,i]]);
lReport.Add('-------------------------------------------');
lReport.Add('');
lReport.Add('A MARGIN MEANS');
lReport.Add('---------------------------------');
lReport.Add('A LEVEL MEAN STD.DEV. ');
lReport.Add('------- ---------- ----------');
for i := 0 to NoALevels-1 do
lReport.Add('%5d %10.3f %10.3f', [i+MinA, AMeans[i], ASDs[i]]);
lReport.Add('---------------------------------');
lReport.Add('');
lReport.Add('GRAND MEAN: %0.3f', [TotMean]);
lReport.Add('');
lReport.Add('');
FMeansReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
end;
procedure TBNestedAForm.ShowResults;
var
F, PF: double;
lReport: TStrings;
begin
lReport := TStringList.Create;
try
lReport.Add('NESTED ANOVA by Bill Miller');
lReport.Add('');
lReport.Add('File analyzed: %s', [OS3MainFrm.FileNameEdit.Text]);
lReport.Add('Factor A: %s', [ACodesEdit.Text]);
lReport.Add('Factor B: %s', [BCodesEdit.Text]);
lReport.Add('');
lReport.Add('ANOVA TABLE');
lReport.Add('-------------------------------------------------------------');
lReport.Add('SOURCE D.F. SS MS F PROB. ');
lReport.Add('--------- ---- ---------- ---------- --------- ---------');
if RandomBChk.Checked then
begin
F := MSA / MSB;
PF := ProbF(F, dfA, dfBwA);
end else
begin
F := MSA / MSW;
PF := ProbF(F, dfA, dfwcell);
end;
lReport.Add('A %4d %10.3f %10.3f %9.3f %9.3f', [dfA, SSA, MSA, F, PF]);
F := MSB / MSW;
PF := ProbF(F,dfBwA,dfwcell);
lReport.Add('B(W) %4d %10.3f %10.3f %9.3f %9.3f', [dfBwA, SSB, MSB, F, PF]);
lReport.Add('w.cells %4d %10.3f %10.3f', [dfwcell, SSW, MSW]);
lReport.Add('Total %4d %10.3f', [dftotal, SSTot]);
lReport.Add('-------------------------------------------------------------');
FReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
end;
procedure TBNestedAForm.PopulateChartCombobox;
var
idx: Integer;
begin
idx := FChartCombobox.ItemIndex;
FChartCombobox.Items.Clear;
FChartCombobox.Items.Add(ACodesEdit.Text);
FChartCombobox.Items.Add(BCodesEdit.Text);
FChartCombobox.ItemIndex := EnsureRange(idx, 0, FChartCombobox.Items.Count-1);
end;
procedure TBNestedAForm.ReleaseMemory;
begin
ASDs := nil;
BCount := nil;
ACount := nil;
// BMeans := nil; // needed for plotting
// AMeans := nil;
BSumSqr := nil;
ASumSqr := nil;
BSS := nil;
ASS := nil;
CellSDs := nil;
CellMeans := nil;
CellCount := nil;
SumSqr := nil;
SS := nil;
end;
procedure TBNestedAForm.Reset;
begin
inherited;
if FMeansReportFrame <> nil then
FMeansReportFrame.Clear;
if FChartCombobox <> nil then
FChartCombobox.Items.Clear;
ACodesEdit.Clear;
BCodesEdit.Clear;
DepEdit.Clear;
CollectVariableNames(OS3MainFrm.DataGrid, VarList.Items);
end;
procedure TBNestedAForm.SelectPlot(Sender: TObject);
var
i: Integer;
begin
FSeries.Clear;
case FChartComboBox.ItemIndex of
0: begin // Plot means vs factor A
for i := 0 to NoALevels-1 do
FSeries.AddXY(MinA + i, AMeans[i], IntToStr(MinA + i));
FChartFrame.SetXTitle(ACodesEdit.Text + ' codes');
FChartFrame.SetTitle('Factor ' + ACodesEdit.Text);
end;
1: begin // Plot means vs factor B
for i := 0 to NoBLevels-1 do
FSeries.AddXY(MinB + i, BMeans[i], IntToStr(MinB + i));
FChartFrame.SetXTitle(BCodesEdit.Text + ' codes');
FChartFrame.SetTitle('Factor ' + BCodesEdit.Text);
end;
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 TBNestedAForm.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;
FChartCombobox.Parent.Left := 0;
PopulateChartCombobox;
SelectPlot(nil);
ChartPage.TabVisible := true;
end;
function TBNestedAForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean;
begin
Result := false;
if DepEdit.Text = '' then
begin
AMsg := 'Dependent variable not specified.';
AControl := VarList;
exit;
end;
if ACodesEdit.Text = '' then
begin
AMsg := 'Factor A variable not specified.';
AControl := VarList;
exit;
end;
if BCodesEdit.Text = '' then
begin
AMsg := 'Factor B variable not specified.';
AControl := VarList;
exit;
end;
Result := true;
end;
procedure TBNestedAForm.VarListDblClick(Sender: TObject);
var
index: Integer;
s: String;
begin
index := VarList.ItemIndex;
if index > -1 then
begin
s := VarList.Items[index];
if DepEdit.Text = '' then
DepEdit.Text := s
else
if ACodesEdit.Text = '' then
ACodesEdit.Text := s
else
if BCodesEdit.Text = '' then
BCodesEdit.Text := s;
VarList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TBNestedAForm.VarListSelectionChange(Sender: TObject; User: boolean);
begin
UpdateBtnStates;
end;
procedure TBNestedAForm.UpdateBtnStates;
var
lSelected: Boolean;
begin
inherited;
if FMeansReportFrame <> nil then
FMeansReportFrame.UpdateBtnStates;
lSelected := AnySelected(VarList);
AInBtn.Enabled := lSelected and (ACodesEdit.Text = '');
BInBtn.Enabled := lSelected and (BCodesEdit.Text = '');
DepInBtn.Enabled := lSelected and (DepEdit.Text = '');
AOutBtn.Enabled := (ACodesEdit.Text <> '');
BOutBtn.Enabled := (BCodesEdit.Text <> '');
DepOutBtn.Enabled := (DepEdit.Text <> '');
end;
end.