From d36f794dee2091e3046519916114b6fa7e027d8a Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sun, 4 Oct 2020 16:50:11 +0000 Subject: [PATCH] LazStats: Simplification of Compute() method in PlotXYUnit git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7741 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../forms/analysis/descriptive/plotxyunit.lfm | 12 +- .../forms/analysis/descriptive/plotxyunit.pas | 359 ++++++++---------- .../lazstats/source/units/mathunit.pas | 26 ++ 3 files changed, 185 insertions(+), 212 deletions(-) diff --git a/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.lfm b/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.lfm index 8e93add50..dc80cdc91 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.lfm +++ b/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.lfm @@ -48,7 +48,7 @@ inherited PlotXYFrm: TPlotXYFrm AnchorSideLeft.Control = ParamsPanel AnchorSideTop.Control = Label1 AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = XinBtn + AnchorSideRight.Control = XInBtn AnchorSideBottom.Control = ButtonBevel AnchorSideBottom.Side = asrBottom Left = 0 @@ -88,7 +88,7 @@ inherited PlotXYFrm: TPlotXYFrm Caption = 'Y Axis Variable' ParentColor = False end - object XinBtn: TBitBtn[9] + object XInBtn: TBitBtn[9] AnchorSideLeft.Control = ParamsPanel AnchorSideLeft.Side = asrCenter AnchorSideTop.Control = VarList @@ -100,14 +100,14 @@ inherited PlotXYFrm: TPlotXYFrm BorderSpacing.Right = 8 Images = MainDataModule.ImageList ImageIndex = 1 - OnClick = XinBtnClick + OnClick = XInBtnClick Spacing = 0 TabOrder = 4 end object XOutBtn: TBitBtn[10] AnchorSideLeft.Control = ParamsPanel AnchorSideLeft.Side = asrCenter - AnchorSideTop.Control = XinBtn + AnchorSideTop.Control = XInBtn AnchorSideTop.Side = asrBottom Left = 175 Height = 26 @@ -157,7 +157,7 @@ inherited PlotXYFrm: TPlotXYFrm TabOrder = 7 end object XEdit: TEdit[13] - AnchorSideLeft.Control = XinBtn + AnchorSideLeft.Control = XInBtn AnchorSideLeft.Side = asrBottom AnchorSideRight.Control = ParamsPanel AnchorSideRight.Side = asrBottom @@ -193,7 +193,7 @@ inherited PlotXYFrm: TPlotXYFrm Text = 'YEdit' end object OptionsGroup: TGroupBox[15] - AnchorSideLeft.Control = XinBtn + AnchorSideLeft.Control = XInBtn AnchorSideTop.Control = YEdit AnchorSideTop.Side = asrBottom AnchorSideRight.Side = asrBottom diff --git a/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas b/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas index 6d71751f6..5df025b9c 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas +++ b/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas @@ -9,7 +9,7 @@ interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, Buttons, ComCtrls, - MainUnit, Globals, FunctionsLib, DataProcs, BasicStatsReportAndChartFormUnit, + MainUnit, Globals, FunctionsLib, BasicStatsReportAndChartFormUnit, ReportFrameUnit, ChartFrameUnit; type @@ -27,7 +27,7 @@ type Label3: TLabel; XEdit: TEdit; Label2: TLabel; - XinBtn: TBitBtn; + XInBtn: TBitBtn; XOutBtn: TBitBtn; YInBtn: TBitBtn; YOutBtn: TBitBtn; @@ -35,7 +35,7 @@ type VarList: TListBox; procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); - procedure XinBtnClick(Sender: TObject); + procedure XInBtnClick(Sender: TObject); procedure XOutBtnClick(Sender: TObject); procedure YInBtnClick(Sender: TObject); procedure YOutBtnClick(Sender: TObject); @@ -64,7 +64,7 @@ implementation uses TAChartUtils, TAChartAxisUtils, TALegend, TASources, TACustomSeries, TASeries, - Math, Utils; + MathUnit, GridProcs, Utils; { TPlotXYFrm } @@ -90,127 +90,40 @@ begin Marks.Style := smsLabel; Grid.Visible := false; end; + + PageControl.ActivePage := ChartPage; end; -procedure TPlotXYfrm.Reset; -var - i: integer; +procedure TPlotXYFrm.AdjustConstraints; begin - XEdit.Text := ''; - YEdit.Text := ''; - ConfEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT); - LineChk.Checked := false; - MeansChk.Checked := false; - ConfChk.Checked := false; - VarList.Items.Clear; - for i := 1 to NoVariables do - VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); + ParamsPanel.Constraints.MinHeight := OptionsGroup.Top + OptionsGroup.Height + + OptionsGroup.BorderSpacing.Bottom + ButtonBevel.Height + + CloseBtn.Height + CloseBtn.BorderSpacing.Top; + ParamsPanel.Constraints.MinWidth := OptionsGroup.Width * 2 - XInBtn.Width div 2 - XInBtn.BorderSpacing.Left; - if Assigned(FChartFrame) then - FChartFrame.Clear; - if Assigned(FReportFrame) then - FReportFrame.Clear; - - UpdateBtnStates; + Constraints.MinHeight := ParamsPanel.Constraints.MinHeight + ParamsPanel.BorderSpacing.Around*2; + Constraints.MinWidth := ParamsPanel.Constraints.MinWidth + 200; end; -procedure TPlotXYFrm.VarListDblClick(Sender: TObject); -var - index: integer; -begin - index := VarList.ItemIndex; - if index > -1 then - begin - if XEdit.Text = '' then - XEdit.Text := VarList.Items[index] - else - YEdit.Text := VarList.Items[index]; - VarList.Items.Delete(index); - UpdateBtnStates; - end; -end; - - -procedure TPlotXYFrm.XinBtnClick(Sender: TObject); -var - index: integer; -begin - index := VarList.ItemIndex; - if (index > -1) and (XEdit.Text = '') then - begin - XEdit.Text := VarList.Items[index]; - VarList.Items.Delete(index); - UpdateBtnStates; - end; -end; - -procedure TPlotXYFrm.XOutBtnClick(Sender: TObject); -begin - if XEdit.Text <> '' then - begin - VarList.Items.Add(XEdit.Text); - XEdit.Text := ''; - UpdateBtnStates; - end; -end; - -procedure TPlotXYFrm.YInBtnClick(Sender: TObject); -var - index: integer; -begin - index := VarList.ItemIndex; - if (index > -1) and (YEdit.Text = '') then - begin - YEdit.Text := VarList.Items[index]; - VarList.Items.Delete(index); - UpdateBtnStates; - end; -end; - -procedure TPlotXYFrm.YOutBtnClick(Sender: TObject); -begin - if YEdit.Text <> '' then - begin - VarList.Items.Add(YEdit.Text); - YEdit.Text := ''; - UpdateBtnStates; - end; -end; - procedure TPlotXYFrm.Compute; var - Xmin, Xmax, Ymin, Ymax, SSx, SSY, t, DF: double; - Xmean, Ymean, Xvariance, Yvariance, Xstddev, Ystddev, ConfBand: double; - X, Y, R, SEPred, Slope, Intercept, predicted, sedata: double; - i: integer; - Xcol, Ycol, N, NoSelected: integer; - Xpoints: DblDyneVec = nil; - Ypoints: DblDyneVec = nil; + xMean, yMean, xVariance, yVariance, xStddev, yStddev: double; + SXX, SXY, SYY, R, slope, intercept, t, confBand: Double; + sePred, predicted, sedata: double; + i, xCol, yCol, N, DF: integer; + xValues: DblDyneVec = nil; + yValues: DblDyneVec = nil; UpConf: DblDyneVec = nil; lowConf: DblDyneVec = nil; - cellstring: string; ColNoSelected: IntDyneVec= nil; C: TWinControl; msg: String; lReport: TStrings; begin - SetLength(Xpoints, NoCases); - SetLength(Ypoints, NoCases); - SetLength(UpConf, NoCases); - SetLength(lowConf, NoCases); - SetLength(ColNoSelected, NoVariables); - - Xcol := 0; - Ycol := 0; - - for i := 1 to NoVariables do - begin - cellstring := OS3MainFrm.DataGrid.Cells[i,0]; - if cellstring = XEdit.Text then Xcol := i; - if cellstring = YEdit.Text then Ycol := i; - end; + xCol := OS3MainFrm.DataGrid.Rows[0].IndexOf(XEdit.Text); + yCol := OS3MainFrm.DataGrid.Rows[0].IndexOf(YEdit.Text); // Validation if not Validate(msg, C, Xcol, Ycol) then @@ -221,76 +134,36 @@ begin exit; end; - NoSelected := 2; + SetLength(ColNoSelected, 2); ColNoSelected[0] := Xcol; ColNoSelected[1] := Ycol; - Xmax := -Infinity; - Xmin := Infinity; - Ymax := -Infinity; - Ymin := Infinity; - Xmean := 0.0; - Ymean := 0.0; - XVariance := 0.0; - YVariance := 0.0; - SSX := 0.0; - SSY := 0.0; - R := 0.0; - N := 0; - for i := 1 to NoCases do + xValues := CollectValues(OS3MainFrm.DataGrid, xCol, ColNoSelected); + yValues := CollectValues(OS3MainFrm.DataGrid, yCol, ColNoSelected); + if (Length(yValues) = 0) then begin - if not GoodRecord(i, NoSelected, ColNoSelected) then continue; - inc(N); - X := StrToFloat(OS3MainFrm.DataGrid.Cells[Xcol,i]); - Y := StrToFloat(OS3MainFrm.DataGrid.Cells[Ycol,i]); - XPoints[N-1] := X; - YPoints[N-1] := Y; - XMax := Max(X, XMax); - XMin := Min(X, XMin); - YMax := Max(Y, YMax); - YMin := Min(Y, YMin); - XMean := XMean + X; - YMean := YMean + Y; - SSX := SSX + sqr(X); - SSY := SSY + sqr(Y); - R := R + X * Y; - end; - - if N < 1 then - begin - ErrorMsg('No data values.'); + ErrorMsg('No y data'); exit; end; + if (Length(xValues) <> Length(yValues)) then + begin + ErrorMsg('Different count of x and y values.'); + exit; + end; + N := Length(yValues); - // Trim array lengths - SetLength(Xpoints, NoCases); - SetLength(Ypoints, NoCases); - SetLength(UpConf, NoCases); - SetLength(lowConf, NoCases); + Calc_MeanVarStddevSS(xValues, xMean, xVariance, xStdDev, SXX); + Calc_MeanVarStddevSS(yValues, yMean, yVariance, yStdDev, SYY); + SXY := 0; + for i := 0 to N-1 do + SXY := SXY + xValues[i] * yValues[i]; - // sort on X - SortOnX(XPoints, YPoints); + R := (SXY - xMean * yMean * N) / ((N - 1) * xStdDev * yStdDev); + sePred := sqrt(1.0 - sqr(R)) * yStdDev * sqrt((N - 1) / (N - 2)); + slope := R * yStdDev / xStdDev; + intercept := yMean - slope * xMean; - // calculate statistics - XVariance := SSX - sqr(XMean) / N; - XVariance := XVariance / (N - 1); - XStdDev := sqrt(XVariance); - - YVariance := SSY - sqr(YMean) / N; - YVariance := YVariance / (N - 1); - YStdDev := sqrt(YVariance); - - R := R - Xmean * Ymean / N; - R := R / (N - 1); - R := R / (XStdDev * YStdDev); - SEPred := sqrt(1.0 - sqr(R)) * YStdDev; - SEPred := SEPred * sqrt((N - 1) / (N - 2)); - XMean := XMean / N; - YMean := YMean / N; - Slope := R * YStdDev / XStdDev; - Intercept := YMean - Slope * XMean; - - // Now, print the descriptive statistics to the output form if requested + // Print the descriptive statistics to the output frame lReport := TStringList.Create; try lReport.Add('X vs. Y PLOT'); @@ -308,9 +181,9 @@ begin lReport.Add(''); lReport.Add('Regression:'); lReport.Add(' Correlation: %8.3f', [R]); - lReport.Add(' Slope: %8.3f', [Slope]); - lReport.Add(' Intercept: %8.3f', [Intercept]); - lReport.Add(' Standard Error of Estimate: %8.3f', [SEPred]); + lReport.Add(' Slope: %8.3f', [slope]); + lReport.Add(' Intercept: %8.3f', [intercept]); + lReport.Add(' Standard Error of Estimate: %8.3f', [sePred]); lReport.Add(' Number of good cases: %8d', [N]); FReportFrame.DisplayReport(lReport); @@ -321,51 +194,35 @@ begin // Get upper and lower confidence points for each X value if ConfChk.Checked then begin - ConfBand := StrToFloat(ConfEdit.Text) / 100.0; + SortOnX(xValues, yValues); + + SetLength(UpConf, N); + SetLength(lowConf, N); + + confBand := StrToFloat(ConfEdit.Text) / 100.0; DF := N - 2; - t := InverseT(ConfBand, DF); + t := InverseT(confBand, DF); for i := 0 to N-1 do begin - X := XPoints[i]; - predicted := slope * X + intercept; - sedata := SEPred * sqrt(1.0 + (1.0 / N) + (sqr(X - XMean) / SSx)); - UpConf[i] := predicted + (t * sedata); - lowConf[i] := predicted - (t * sedata); - YMax := Max(YMax, UpConf[i]); - YMin := Min(YMin, LowConf[i]); + predicted := slope * xValues[i] + intercept; + seData := sePred * sqrt(1.0 + 1/N + sqr(xValues[i] - XMean) / SXX); + upConf[i] := predicted + t * seData; + lowConf[i] := predicted - t * seData; end; end else - ConfBand := 0.0; + confBand := 0.0; // Plot the values (and optional line and confidence band if elected) - PlotXY(Xpoints, Ypoints, UpConf, LowConf, XMean, YMean, R, Slope, Intercept); - - // cleanup - ColNoSelected := nil; - lowConf := nil; - UpConf := nil; - Ypoints := nil; - Xpoints := nil; -end; - - -procedure TPlotXYFrm.AdjustConstraints; -begin - ParamsPanel.Constraints.MinHeight := OptionsGroup.Top + OptionsGroup.Height + - OptionsGroup.BorderSpacing.Bottom + ButtonBevel.Height + - CloseBtn.Height + CloseBtn.BorderSpacing.Top; - ParamsPanel.Constraints.MinWidth := OptionsGroup.Width * 2 - XInBtn.Width div 2 - XInBtn.BorderSpacing.Left; - - Constraints.MinHeight := ParamsPanel.Constraints.MinHeight + ParamsPanel.BorderSpacing.Around*2; - Constraints.MinWidth := ParamsPanel.Constraints.MinWidth + 200; + PlotXY(xValues, yValues, upConf, lowConf, xMean, yMean, R, slope, intercept); end; procedure TPlotXYFrm.PlotXY(XPoints, YPoints, UpConf, LowConf: DblDyneVec; XMean, YMean, R, Slope, Intercept: Double); var - tmpX, tmpY: array of Double; + tmpX: array of Double = nil; + tmpY: array of Double = nil; xmin, xmax, ymin, ymax: Double; rightLabels: TListChartSource; topLabels: TListChartSource; @@ -429,9 +286,32 @@ begin end; +procedure TPlotXYfrm.Reset; +var + i: integer; +begin + XEdit.Text := ''; + YEdit.Text := ''; + ConfEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT); + LineChk.Checked := false; + MeansChk.Checked := false; + ConfChk.Checked := false; + VarList.Items.Clear; + for i := 1 to NoVariables do + VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); + + if Assigned(FChartFrame) then + FChartFrame.Clear; + if Assigned(FReportFrame) then + FReportFrame.Clear; + + UpdateBtnStates; +end; + + procedure TPlotXYFrm.UpdateBtnStates; begin - XinBtn.Enabled := (VarList.ItemIndex > -1) and (XEdit.Text = ''); + XInBtn.Enabled := (VarList.ItemIndex > -1) and (XEdit.Text = ''); XoutBtn.Enabled := (XEdit.Text <> ''); YinBtn.Enabled := (VarList.ItemIndex > -1) and (YEdit.Text = ''); @@ -449,14 +329,14 @@ function TPlotXYFrm.Validate(out AMsg: String; out AControl: TWinControl; begin Result := false; - if (Xcol = 0) then + if (Xcol < 0) then begin AControl := XEdit; AMsg := 'No case selected for X.'; exit; end; - if (Ycol = 0) then + if (Ycol < 0) then begin AControl := YEdit; AMsg := 'No case selected for Y.'; @@ -466,11 +346,78 @@ begin end; +procedure TPlotXYFrm.VarListDblClick(Sender: TObject); +var + index: integer; +begin + index := VarList.ItemIndex; + if index > -1 then + begin + if XEdit.Text = '' then + XEdit.Text := VarList.Items[index] + else + YEdit.Text := VarList.Items[index]; + VarList.Items.Delete(index); + UpdateBtnStates; + end; +end; + + procedure TPlotXYFrm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; +procedure TPlotXYFrm.XInBtnClick(Sender: TObject); +var + index: integer; +begin + index := VarList.ItemIndex; + if (index > -1) and (XEdit.Text = '') then + begin + XEdit.Text := VarList.Items[index]; + VarList.Items.Delete(index); + UpdateBtnStates; + end; +end; + + +procedure TPlotXYFrm.XOutBtnClick(Sender: TObject); +begin + if XEdit.Text <> '' then + begin + VarList.Items.Add(XEdit.Text); + XEdit.Text := ''; + UpdateBtnStates; + end; +end; + + +procedure TPlotXYFrm.YInBtnClick(Sender: TObject); +var + index: integer; +begin + index := VarList.ItemIndex; + if (index > -1) and (YEdit.Text = '') then + begin + YEdit.Text := VarList.Items[index]; + VarList.Items.Delete(index); + UpdateBtnStates; + end; +end; + + +procedure TPlotXYFrm.YOutBtnClick(Sender: TObject); +begin + if YEdit.Text <> '' then + begin + VarList.Items.Add(YEdit.Text); + YEdit.Text := ''; + UpdateBtnStates; + end; +end; + + end. diff --git a/applications/lazstats/source/units/mathunit.pas b/applications/lazstats/source/units/mathunit.pas index d8e97e9a6..78895d988 100644 --- a/applications/lazstats/source/units/mathunit.pas +++ b/applications/lazstats/source/units/mathunit.pas @@ -48,6 +48,7 @@ function PoissonCDF(n: Integer; a: double): Double; procedure Calc_MaxMin(const AData: DblDyneVec; out AMax, AMin: Double); procedure Calc_MeanStdDev(const AData: DblDyneVec; out AMean, AStdDev: Double); procedure Calc_MeanVarStdDev(const AData: DblDyneVec; out AMean, AVariance, AStdDev: Double); +procedure Calc_MeanVarStdDevSS(const AData: DblDyneVec; out AMean, AVariance, AStdDev, ASumOfSquares: Double); procedure Calc_SumSS(const AData: DblDyneVec; out Sum, SS: Double); @@ -532,6 +533,31 @@ begin end; +procedure Calc_MeanVarStdDevSS(const AData: DblDyneVec; + out AMean, AVariance, AStdDev, ASumOfSquares: Double); +var + sum: Double; + n: Integer; +begin + AMean := NaN; + AVariance := NaN; + AStdDev := NaN; + + n := Length(AData); + if n = 0 then + exit; + + Calc_SumSS(AData, sum, ASumOfSquares); + + AMean := sum / n; + if n = 1 then + exit; + + AVariance := (ASumOfSquares - sqr(sum) / n) / (n - 1); + AStdDev := sqrt(AVariance); +end; + + procedure Calc_SumSS(const AData: DblDyneVec; out Sum, SS: Double); var i: Integer;