From ffd5f1cc88ddb191739d297c40dfeaadee90ccc2 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Tue, 27 Oct 2020 17:14:52 +0000 Subject: [PATCH] LazStats: Inherit BinomialUnit from TBasicStatsReportAndChartFrame git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7817 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- applications/lazstats/source/LazStats.lpi | 2 +- .../analysis/nonparametric/binomialunit.lfm | 211 ++++++--------- .../analysis/nonparametric/binomialunit.pas | 244 +++++++++--------- .../lazstats/source/forms/mainunit.pas | 6 +- .../lazstats/source/frames/chartframeunit.pas | 6 +- .../lazstats/source/units/functionslib.pas | 1 + 6 files changed, 208 insertions(+), 262 deletions(-) diff --git a/applications/lazstats/source/LazStats.lpi b/applications/lazstats/source/LazStats.lpi index b021b4119..53ec6e5d6 100644 --- a/applications/lazstats/source/LazStats.lpi +++ b/applications/lazstats/source/LazStats.lpi @@ -785,7 +785,7 @@ - + diff --git a/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.lfm b/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.lfm index c80d6aef7..b2da8404b 100644 --- a/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.lfm +++ b/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.lfm @@ -1,109 +1,46 @@ -object BinomialFrm: TBinomialFrm +inherited BinomialForm: TBinomialForm Left = 802 - Height = 175 + Height = 409 Top = 338 - Width = 340 + Width = 657 HelpType = htKeyword HelpKeyword = 'html/ProbabilityofaBinomialEvent.htm' - AutoSize = True - BorderStyle = bsSingle Caption = 'Binomial Probability Calculator' - ClientHeight = 175 - ClientWidth = 340 - OnActivate = FormActivate - OnCreate = FormCreate - OnShow = FormShow - Position = poMainFormCenter - LCLVersion = '2.1.0.0' - object ResetBtn: TButton - AnchorSideTop.Control = Bevel1 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = ComputeBtn - Left = 127 - Height = 25 - Top = 145 - Width = 54 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Left = 12 - BorderSpacing.Top = 8 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - Caption = 'Reset' - OnClick = ResetBtnClick - TabOrder = 0 - end - object ComputeBtn: TButton - AnchorSideTop.Control = Bevel1 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = CloseBtn - Left = 189 - Height = 25 - Top = 145 - Width = 76 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Left = 8 - BorderSpacing.Top = 8 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - Caption = 'Compute' - OnClick = ComputeBtnClick - TabOrder = 1 - end - object CloseBtn: TButton - AnchorSideTop.Control = Bevel1 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - Left = 273 - Height = 25 - Top = 145 - Width = 55 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Left = 8 - BorderSpacing.Top = 8 - BorderSpacing.Right = 12 - BorderSpacing.Bottom = 8 - Caption = 'Close' - ModalResult = 11 - TabOrder = 2 - end - object Bevel1: TBevel - AnchorSideLeft.Control = Owner - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - Left = 0 - Height = 9 - Top = 128 - Width = 340 - Anchors = [akTop, akLeft, akRight] - Shape = bsBottomLine - end - object Panel1: TPanel - AnchorSideLeft.Control = Owner - AnchorSideTop.Control = Owner - Left = 12 - Height = 120 - Top = 8 - Width = 292 - Alignment = taRightJustify - AutoSize = True - BorderSpacing.Left = 12 - BorderSpacing.Top = 8 - BorderSpacing.Right = 12 - BevelOuter = bvNone - ClientHeight = 120 - ClientWidth = 292 - TabOrder = 3 - object Label1: TLabel + ClientHeight = 409 + ClientWidth = 657 + inherited ParamsPanel: TPanel + Height = 393 + Width = 299 + ClientHeight = 393 + ClientWidth = 299 + inherited CloseBtn: TButton + Left = 244 + Top = 368 + TabOrder = 6 + end + inherited ComputeBtn: TButton + Left = 160 + Top = 368 + TabOrder = 5 + end + inherited ResetBtn: TButton + Left = 98 + Top = 368 + TabOrder = 4 + end + inherited HelpBtn: TButton + Left = 39 + Top = 368 + end + inherited ButtonBevel: TBevel + Top = 352 + Width = 299 + end + object FreqALabel: TLabel[5] AnchorSideTop.Control = FreqAEdit AnchorSideTop.Side = asrCenter AnchorSideRight.Control = FreqAEdit - Left = 2 + Left = -3 Height = 15 Top = 12 Width = 239 @@ -112,85 +49,87 @@ object BinomialFrm: TBinomialFrm Caption = 'Frequency of events observed in category ''A'':' ParentColor = False end - object Label2: TLabel + object FreqBLabel: TLabel[6] AnchorSideTop.Control = FreqBEdit AnchorSideTop.Side = asrCenter AnchorSideRight.Control = FreqBEdit - Left = 3 + Left = -2 Height = 15 - Top = 39 + Top = 43 Width = 238 Anchors = [akTop, akRight] BorderSpacing.Right = 8 Caption = 'Frequency of events observed in category ''B'':' ParentColor = False end - object Label3: TLabel - AnchorSideLeft.Control = Panel1 + object PropALabel: TLabel[7] AnchorSideTop.Control = PropAEdit AnchorSideTop.Side = asrCenter - Left = 0 + AnchorSideRight.Control = PropAEdit + Left = -5 Height = 15 - Top = 66 + Top = 74 Width = 241 + Anchors = [akTop, akRight] Caption = 'Proportion of events expected in category ''A'':' ParentColor = False end - object FreqAEdit: TEdit + object FreqAEdit: TEdit[8] AnchorSideLeft.Control = PropAEdit - AnchorSideTop.Control = Panel1 - Left = 249 + AnchorSideTop.Control = ParamsPanel + AnchorSideRight.Control = PropAEdit + AnchorSideRight.Side = asrBottom + Left = 244 Height = 23 Top = 8 - Width = 43 + Width = 55 Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] BorderSpacing.Top = 8 TabOrder = 0 Text = 'FreqAEdit' end - object FreqBEdit: TEdit + object FreqBEdit: TEdit[9] AnchorSideLeft.Control = PropAEdit AnchorSideTop.Control = FreqAEdit AnchorSideTop.Side = asrBottom - Left = 249 + AnchorSideRight.Control = PropAEdit + AnchorSideRight.Side = asrBottom + Left = 244 Height = 23 - Top = 35 - Width = 43 + Top = 39 + Width = 55 Alignment = taRightJustify - BorderSpacing.Top = 4 + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 8 TabOrder = 1 Text = 'FreqBEdit' end - object PropAEdit: TEdit - AnchorSideLeft.Control = Label3 + object PropAEdit: TEdit[10] AnchorSideLeft.Side = asrBottom AnchorSideTop.Control = FreqBEdit AnchorSideTop.Side = asrBottom - Left = 249 + AnchorSideRight.Control = ParamsPanel + AnchorSideRight.Side = asrBottom + Left = 244 Height = 23 - Top = 62 - Width = 43 + Top = 70 + Width = 55 Alignment = taRightJustify + Anchors = [akTop, akRight] BorderSpacing.Left = 8 - BorderSpacing.Top = 4 + BorderSpacing.Top = 8 TabOrder = 2 Text = 'ProbAEdit' end - object PlotChk: TCheckBox - AnchorSideLeft.Control = Panel1 - AnchorSideTop.Control = PropAEdit - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = Panel1 - AnchorSideRight.Side = asrBottom - Left = 167 - Height = 19 - Top = 101 - Width = 125 - Alignment = taLeftJustify - Anchors = [akTop, akRight] - BorderSpacing.Top = 16 - Caption = 'Plot the distribution' - TabOrder = 3 - end + end + inherited ParamsSplitter: TSplitter + Left = 311 + Height = 409 + end + inherited PageControl: TPageControl + Left = 320 + Height = 393 + Width = 329 end end diff --git a/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.pas b/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.pas index e4392c794..734e50669 100644 --- a/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.pas +++ b/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.pas @@ -5,174 +5,173 @@ unit BinomialUnit; interface uses - Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, - StdCtrls, ExtCtrls, - OutputUnit, FunctionsLib, GraphLib; + Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, + FunctionsLib, GraphLib, BasicStatsReportAndChartFormUnit; type - { TBinomialFrm } + { TBinomialForm } - TBinomialFrm = class(TForm) - Bevel1: TBevel; - Panel1: TPanel; - PlotChk: TCheckBox; - ResetBtn: TButton; - ComputeBtn: TButton; - CloseBtn: TButton; + TBinomialForm = class(TBasicStatsReportAndChartForm) FreqAEdit: TEdit; FreqBEdit: TEdit; PropAEdit: TEdit; - Label1: TLabel; - Label2: TLabel; - Label3: TLabel; - procedure ComputeBtnClick(Sender: TObject); - procedure FormActivate(Sender: TObject); - procedure FormCreate(Sender: TObject); - procedure FormShow(Sender: TObject); - procedure ResetBtnClick(Sender: TObject); + FreqALabel: TLabel; + FreqBLabel: TLabel; + PropALabel: TLabel; private - function Validate(out AMsg: String; out AControl: TWinControl): Boolean; - { private declarations } + + protected + procedure AdjustConstraints; override; + procedure Compute; override; + function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override; + public - { public declarations } + constructor Create(AOwner: TComponent); override; + procedure Reset; override; end; var - BinomialFrm: TBinomialFrm; + BinomialForm: TBinomialForm; implementation +{$R *.lfm} + uses - Math; + Math, + TACustomSeries, + Globals, MathUnit, ChartFrameUnit; -{ TBinomialFrm } +{ TBinomialForm } -procedure TBinomialFrm.ResetBtnClick(Sender: TObject); +constructor TBinomialForm.Create(AOwner: TComponent); begin - FreqAEdit.Text := ''; - FreqBEdit.Text := ''; - PropAEdit.Text := ''; - FreqAEdit.SetFocus; + inherited; + FChartFrame.Chart.Margins.Bottom := 0; + FChartFrame.Chart.BottomAxis.AxisPen.Visible := true; + FChartFrame.Chart.BottomAxis.ZPosition := 1; end; -procedure TBinomialFrm.FormActivate(Sender: TObject); + +procedure TBinomialForm.AdjustConstraints; +begin + inherited; + + ParamsPanel.Constraints.MinWidth := Max( + PropALabel.Width + PropAEdit.Width + PropAEdit.BorderSpacing.Left, + 4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left + ); + ParamsPanel.Constraints.MinHeight := PropAEdit.Top + PropAEdit.Height +end; + + +procedure TBinomialForm.Reset; +begin + inherited; + FreqAEdit.Clear; + FreqBEdit.Clear; + PropAEdit.Clear; +end; + + +procedure TBinomialForm.Compute; +const + N_MAX = 35; var - w: Integer; -begin - w := MaxValue([ResetBtn.Width, ComputeBtn.Width, CloseBtn.Width]); - ResetBtn.Constraints.MinWidth := w; - ComputeBtn.Constraints.MinWidth := w; - CloseBtn.Constraints.MinWidth := w; -end; - -procedure TBinomialFrm.FormCreate(Sender: TObject); -begin - if GraphFrm = nil then - Application.CreateForm(TGraphFrm, GraphFrm); -end; - -procedure TBinomialFrm.FormShow(Sender: TObject); -begin - ResetBtnClick(self); -end; - -procedure TBinomialFrm.ComputeBtnClick(Sender: TObject); -var - p, Q, Probability, z, CorrectedA, SumProb : double; - A, b, N, X, i: integer; + P, Q, probability, z, correctedA, SumProb, mean, sigma: double; + A, B, N, i: integer; + xPts: DblDyneVec = nil; + yPts: DblDyneVec = nil; lReport: TStrings; - msg: String; - C: TWinControl; + ser: TChartSeries; begin - if not Validate(msg, C) then begin - C.SetFocus; - MessageDlg(msg, mtError,[mbOK], 0); - ModalResult := mrNone; - exit; - end; - - SumProb := 0.0; A := round(StrToFloat(FreqAEdit.Text)); - b := round(StrToFloat(FreqBEdit.Text)); - p := StrToFloat(PropAEdit.Text); - N := A + b; - Q := 1.0 - p; + B := round(StrToFloat(FreqBEdit.Text)); + P := StrToFloat(PropAEdit.Text); + N := A + B; + Q := 1.0 - P; lReport := TStringList.Create; try lReport.Add('BINOMIAL PROBABILITY TEST'); lReport.Add(''); lReport.Add('Frequency of %d out of %d observed', [A, N]); - lReport.Add('The theoretical proportion expected in category A is %.f', [p]); + lReport.Add('The theoretical proportion expected in category A is %.3f', [P]); lReport.Add(''); lReport.Add('The test is for the probability of a value in category A as small or smaller'); lReport.Add('than that observed given the expected proportion.'); + lReport.Add(''); - //Use normal distribution approximation - if (N > 35) then + if (N > N_MAX) then begin - CorrectedA := A; - if A < N * p then CorrectedA := A + 0.5; - if A > N * p then CorrectedA := A - 0.5; - z := (CorrectedA - N * p) / sqrt(N * p * Q); - lReport.Add('Z value for Normal Distribution approximation: %.3f', [z]); - Probability := probz(z); + // Use normal distribution approximation + correctedA := A; + if A < N * P then correctedA := A + 0.5; + if A > N * P then correctedA := A - 0.5; + z := (correctedA - N * P) / sqrt(N * P * Q); + lReport.Add('Z value for normal nistribution approximation: %.3f', [z]); + Probability := NormalDist(z); lReport.Add('Probability: %6.4f', [Probability]); end else - //Use binomial fomula begin - for X := 0 to A do + // Use binomial fomula + sumProb := 0; + for i := 0 to A do begin - Probability := combos(X, N) * Power(p, X) * Power(Q, N - X); - lReport.Add('Probability of %2d: %6.4f', [X, Probability]); - SumProb := SumProb + Probability; + Probability := combos(i, N) * IntPower(P, i) * IntPower(Q, N-i); + lReport.Add('Probability of %d: %6.4f', [i, Probability]); + sumProb := sumProb + Probability; end; - lReport.Add('Binomial Probability of %d or less out of %d: %.4f', [A, N, SumProb]); + lReport.Add(''); + lReport.Add('Binomial Probability of %d or less out of %d: %.4f', [A, N, sumProb]); end; - DisplayReport(lReport); + FReportFrame.DisplayReport(lReport); finally lReport.Free; end; - if PlotChk.Checked then + // Plot the distribution + FChartFrame.Clear; + FChartFrame.SetXTitle('Values'); + FChartFrame.SetYTitle('Probability'); + if N > N_MAX then begin - if N <= 35 then + FChartFrame.SetTitle('Binomial Distribution' + LineEnding + '(approximated by Normal Distribution)'); + mean := N*P; + sigma := sqrt(N * P * Q); + SetLength(xPts, N+1); + SetLength(yPts, N+1); + for i := 0 to N do begin - SetLength(GraphFrm.Xpoints,1,N+1); - SetLength(GraphFrm.Ypoints,1,N+1); - for i := 0 to N do - begin - GraphFrm.Xpoints[0,i] := i; - Probability := combos(i,N) * power(p,i) * power(Q,(N-i)); - GraphFrm.Ypoints[0,i] := Probability; - end; - GraphFrm.GraphType := 2; - GraphFrm.nosets := 1; - GraphFrm.nbars := N; - GraphFrm.BackColor := clCream; - GraphFrm.WallColor := clDkGray; - GraphFrm.FloorColor := clGray; - GraphFrm.Heading := 'Binomial Distribution'; - GraphFrm.XTitle := 'Values'; - GraphFrm.YTitle := 'Probability'; - GraphFrm.barwideprop := 0.5; - GraphFrm.AutoScaled := true; - GraphFrm.ShowLeftWall := true; - GraphFrm.ShowRightWall := true; - GraphFrm.ShowBottomWall := true; - GraphFrm.ShowModal; - GraphFrm.Xpoints := nil; - GraphFrm.Ypoints := nil; - end else - MessageDlg('Cannot plot for N > 35', mtInformation, [mbOK], 0); + xPts[i] := i; + yPts[i] := NormalDistDensity(i, mean, sigma); + end; + end else + begin + FChartFrame.SetTitle('Binomial Distribution'); + SetLength(xPts, N+1); + SetLength(yPts, N+1); + for i := 0 to N do + begin + xPts[i] := i; + yPts[i] := Combos(i, N) * IntPower(p, i) * IntPower(Q, N-i); + end; end; + if N < 50 then + ser := FChartFrame.PlotXY(ptBars, xPts, yPts, nil, nil, '', DATA_COLORS[0]) + else + ser := FChartFrame.PlotXY(ptArea, xPts, yPts, nil, nil, '', DATA_COLORS[0]); + ser.ZPosition := 2; + FChartFrame.Chart.Legend.Visible := false; end; -function TBinomialFrm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; + +function TBinomialForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; var x: Double; + n: Integer; begin Result := false; if (FreqAEdit.Text = '') or (FreqBEdit.Text = '') or (PropAEdit.Text = '') then @@ -183,15 +182,15 @@ begin if PropAEdit.Text = '' then AControl := PropAEdit; exit; end; - if not TryStrToFloat(FreqAEdit.Text, x) then + if not TryStrToInt(FreqAEdit.Text, n) then begin - AMsg := 'No valid number.'; + AMsg := 'No valid integer.'; AControl := FreqAEdit; exit; end; - if not TryStrToFloat(FreqBEdit.Text, x) then + if not TryStrToInt(FreqBEdit.Text, n) then begin - AMsg := 'No valid number.'; + AMsg := 'No valid integer.'; AControl := FreqBEdit; exit; end; @@ -201,12 +200,15 @@ begin AControl := PropAEdit; exit; end; + if (x < 0) or (x > 1) then + begin + AMsg := 'Number between 0 and 1 expected.'; + AControl := PropAEdit; + exit; + end; Result := true; end; -initialization - {$I binomialunit.lrs} - end. diff --git a/applications/lazstats/source/forms/mainunit.pas b/applications/lazstats/source/forms/mainunit.pas index a6b022c4d..da57e4938 100644 --- a/applications/lazstats/source/forms/mainunit.pas +++ b/applications/lazstats/source/forms/mainunit.pas @@ -1896,9 +1896,9 @@ end; // Menu "Analysis" > "Nonparametric" > "Probability of a Binomial Event" procedure TOS3MainFrm.mnuAnalysisNonPar_BinomialClick(Sender: TObject); begin - if BinomialFrm = nil then - Application.CreateForm(TBinomialFrm, BinomialFrm); - BinomialFrm.ShowModal; + if BinomialForm = nil then + Application.CreateForm(TBinomialForm, BinomialForm); + BinomialForm.Show; end; // Menu "Analysis" > "Nonparametric" > Kendall's Tau and Partial Tau" diff --git a/applications/lazstats/source/frames/chartframeunit.pas b/applications/lazstats/source/frames/chartframeunit.pas index bbf06d2bb..a53dbdfc1 100644 --- a/applications/lazstats/source/frames/chartframeunit.pas +++ b/applications/lazstats/source/frames/chartframeunit.pas @@ -208,7 +208,11 @@ begin TBarSeries(Result).BarBrush.Color := AColor; end; ptArea: - Result := TAreaSeries.Create(self); + begin + Result := TAreaSeries.Create(self); + TAreaSeries(Result).AreaBrush.Color := AColor; + TAreaSeries(Result).AreaLinesPen.Style := psClear; + end; else raise Exception.Create('Unknown plot type.'); end; diff --git a/applications/lazstats/source/units/functionslib.pas b/applications/lazstats/source/units/functionslib.pas index 63562fbec..bcc611b37 100644 --- a/applications/lazstats/source/units/functionslib.pas +++ b/applications/lazstats/source/units/functionslib.pas @@ -461,6 +461,7 @@ function probz(z : double) : double; begin Result := 0.5 + simpsonintegral(0.0,z); + // wp: a faster code is in unit MathUnit, NormalDist(z). end; //-----------------------------------------------------------------------