From 0e50c16f6b2a1312c2d695774d4fa783cde02df5 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Wed, 30 Sep 2020 10:19:19 +0000 Subject: [PATCH] LazStats: More refactoring in CompareDistUnit. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7722 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- applications/lazstats/source/LazStats.lpi | 7 +- applications/lazstats/source/LazStats.res | Bin 55556 -> 55600 bytes .../analysis/descriptive/comparedistunit.lfm | 227 ++++-- .../analysis/descriptive/comparedistunit.pas | 667 +++++++++++++----- .../lazstats/source/frames/chartframeunit.pas | 6 +- 5 files changed, 679 insertions(+), 228 deletions(-) diff --git a/applications/lazstats/source/LazStats.lpi b/applications/lazstats/source/LazStats.lpi index c93669b35..3256533b8 100644 --- a/applications/lazstats/source/LazStats.lpi +++ b/applications/lazstats/source/LazStats.lpi @@ -1,11 +1,13 @@ - + + + + - @@ -93,7 +95,6 @@ - diff --git a/applications/lazstats/source/LazStats.res b/applications/lazstats/source/LazStats.res index 73ed432afb324c3520946bbd4db1fef113637198..608538ce6860335e76dbb5ac9dd06e922256845e 100644 GIT binary patch delta 60 zcmZqK#Jph>^8^LP2OAZqvZ*N8<`k#e=%WZuUc=_L`3sxC FB>=UH7exR7 delta 26 icmdn6iMeGH^8^LPO&b-bvQ2)%X2Zn5uvvj!;1U3aAPD9F diff --git a/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.lfm b/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.lfm index 55e68a191..9254afe16 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.lfm +++ b/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.lfm @@ -1,19 +1,19 @@ inherited CompareDistFrm: TCompareDistFrm - Left = 671 - Height = 413 - Top = 327 + Left = 459 + Height = 504 + Top = 178 Width = 924 HelpType = htKeyword HelpKeyword = 'html/ComparisonsWithTheoreticalDistri.htm' Caption = 'Compare Cumulative Distributions' - ClientHeight = 413 + ClientHeight = 504 ClientWidth = 924 OnActivate = FormActivate OnCreate = FormCreate Position = poMainFormCenter object ParamsPanel: TPanel[0] Left = 8 - Height = 397 + Height = 488 Top = 8 Width = 288 Align = alLeft @@ -22,7 +22,7 @@ inherited CompareDistFrm: TCompareDistFrm BorderSpacing.Right = 4 BorderSpacing.Bottom = 8 BevelOuter = bvNone - ClientHeight = 397 + ClientHeight = 488 ClientWidth = 288 TabOrder = 0 object ResetBtn: TButton @@ -31,7 +31,7 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideBottom.Side = asrBottom Left = 87 Height = 25 - Top = 372 + Top = 463 Width = 54 Anchors = [akRight, akBottom] AutoSize = True @@ -47,7 +47,7 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideBottom.Side = asrBottom Left = 149 Height = 25 - Top = 372 + Top = 463 Width = 76 Anchors = [akRight, akBottom] AutoSize = True @@ -65,7 +65,7 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideBottom.Side = asrBottom Left = 233 Height = 25 - Top = 372 + Top = 463 Width = 55 Anchors = [akRight, akBottom] AutoSize = True @@ -83,7 +83,7 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideBottom.Control = ResetBtn Left = 0 Height = 8 - Top = 356 + Top = 447 Width = 288 Anchors = [akLeft, akRight, akBottom] Shape = bsBottomLine @@ -93,14 +93,14 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideBottom.Control = Bevel1 Left = 0 Height = 83 - Top = 273 - Width = 302 + Top = 364 + Width = 298 Anchors = [akLeft, akBottom] AutoSize = True BorderSpacing.Top = 8 Caption = 'Option:' ClientHeight = 63 - ClientWidth = 298 + ClientWidth = 294 TabOrder = 3 object BothChk: TCheckBox AnchorSideLeft.Control = OptionsGroup @@ -108,15 +108,15 @@ inherited CompareDistFrm: TCompareDistFrm Left = 16 Height = 19 Top = 6 - Width = 270 + Width = 266 BorderSpacing.Left = 16 BorderSpacing.Top = 6 BorderSpacing.Right = 12 BorderSpacing.Bottom = 8 - Caption = 'Plot both Frequency and cumulative Frequency' + Caption = 'Plot both frequency and cumulative frequency' TabOrder = 0 end - object VertBarBtn: TSpeedButton + object BarPlotBtn: TSpeedButton AnchorSideLeft.Control = OptionsGroup AnchorSideTop.Control = BothChk AnchorSideTop.Side = asrBottom @@ -126,35 +126,25 @@ inherited CompareDistFrm: TCompareDistFrm Width = 23 BorderSpacing.Left = 16 BorderSpacing.Bottom = 8 + Down = True + GroupIndex = 1 Images = MainDataModule.ImageList ImageIndex = 8 end object LinePlotBtn: TSpeedButton - AnchorSideLeft.Control = VertBarBtn + AnchorSideLeft.Control = BarPlotBtn AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = VertBarBtn + AnchorSideTop.Control = BarPlotBtn AnchorSideTop.Side = asrCenter Left = 43 Height = 22 Top = 33 Width = 23 BorderSpacing.Left = 4 + GroupIndex = 1 Images = MainDataModule.ImageList ImageIndex = 10 end - object ThreeDChk: TCheckBox - AnchorSideLeft.Control = LinePlotBtn - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = VertBarBtn - AnchorSideTop.Side = asrCenter - Left = 82 - Height = 19 - Top = 35 - Width = 34 - BorderSpacing.Left = 16 - Caption = '3D' - TabOrder = 1 - end end object Label1: TLabel AnchorSideLeft.Control = ParamsPanel @@ -173,7 +163,7 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideRight.Control = Var1InBtn AnchorSideBottom.Control = OptionsGroup Left = 0 - Height = 248 + Height = 339 Top = 17 Width = 99 Anchors = [akTop, akLeft, akRight, akBottom] @@ -287,49 +277,155 @@ inherited CompareDistFrm: TCompareDistFrm AnchorSideRight.Control = CompareGroup AnchorSideRight.Side = asrBottom Left = 105 - Height = 60 + Height = 191 Top = 165 Width = 183 - PageIndex = 1 + PageIndex = 0 AutoSize = True Anchors = [akTop, akLeft, akRight] BorderSpacing.Bottom = 8 TabOrder = 9 object TheoreticalDistPage: TPage - object DistGroup: TRadioGroup + object DistGroup: TGroupBox AnchorSideLeft.Control = TheoreticalDistPage AnchorSideTop.Control = TheoreticalDistPage AnchorSideRight.Control = TheoreticalDistPage AnchorSideRight.Side = asrBottom Left = 0 - Height = 139 - Top = 16 + Height = 191 + Top = 0 Width = 183 Anchors = [akTop, akLeft, akRight] - AutoFill = True AutoSize = True - BorderSpacing.Top = 16 - Caption = 'Theoretical Distributions:' - ChildSizing.LeftRightSpacing = 18 - ChildSizing.TopBottomSpacing = 8 - ChildSizing.VerticalSpacing = 2 - ChildSizing.EnlargeHorizontal = crsHomogenousChildResize - ChildSizing.EnlargeVertical = crsHomogenousChildResize - ChildSizing.ShrinkHorizontal = crsScaleChilds - ChildSizing.ShrinkVertical = crsScaleChilds - ChildSizing.Layout = cclLeftToRightThenTopToBottom - ChildSizing.ControlsPerLine = 1 - ClientHeight = 119 + Caption = 'Theoretical Distribution' + ClientHeight = 171 ClientWidth = 179 - Items.Strings = ( - 'Normal Distribution' - 't-Distribution' - 'Chi-Sq Distribution' - 'F Distribution' - 'Poisson Distribution' - ) - OnClick = DistGroupClick TabOrder = 0 + object NormalDistChk: TRadioButton + AnchorSideLeft.Control = DistGroup + AnchorSideTop.Control = DistGroup + Left = 16 + Height = 19 + Top = 2 + Width = 125 + BorderSpacing.Left = 16 + BorderSpacing.Top = 2 + BorderSpacing.Right = 16 + Caption = 'Normal Distribution' + OnChange = DistChange + TabOrder = 0 + end + object tDistChk: TRadioButton + AnchorSideLeft.Control = NormalDistChk + AnchorSideTop.Control = NormalDistChk + AnchorSideTop.Side = asrBottom + Left = 16 + Height = 19 + Top = 23 + Width = 89 + BorderSpacing.Top = 2 + Caption = 't Distribution' + OnChange = DistChange + TabOrder = 1 + end + object ChiSqDistChk: TRadioButton + AnchorSideLeft.Control = NormalDistChk + AnchorSideTop.Control = tDistChk + AnchorSideTop.Side = asrBottom + Left = 16 + Height = 19 + Top = 44 + Width = 121 + BorderSpacing.Top = 2 + BorderSpacing.Right = 12 + Caption = 'Chi-Sq Distribution' + OnChange = DistChange + TabOrder = 2 + end + object FDistChk: TRadioButton + AnchorSideLeft.Control = NormalDistChk + AnchorSideTop.Control = ChiSqDistChk + AnchorSideTop.Side = asrBottom + Left = 16 + Height = 19 + Top = 65 + Width = 91 + BorderSpacing.Top = 2 + Caption = 'F Distribution' + OnChange = DistChange + TabOrder = 3 + end + object PoissonDistChk: TRadioButton + AnchorSideLeft.Control = NormalDistChk + AnchorSideTop.Control = FDistChk + AnchorSideTop.Side = asrBottom + Left = 16 + Height = 19 + Top = 86 + Width = 126 + BorderSpacing.Top = 2 + BorderSpacing.Right = 12 + BorderSpacing.Bottom = 8 + Caption = 'Poisson Distribution' + OnChange = DistChange + TabOrder = 4 + end + object DF1Label: TLabel + AnchorSideTop.Control = DF1Edit + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = DF1Edit + Left = 48 + Height = 15 + Top = 117 + Width = 29 + Anchors = [akTop, akRight] + BorderSpacing.Right = 8 + Caption = 'D.F. 1' + ParentColor = False + end + object DF1Edit: TEdit + AnchorSideTop.Control = PoissonDistChk + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = DistGroup + AnchorSideRight.Side = asrBottom + Left = 85 + Height = 23 + Top = 113 + Width = 82 + Anchors = [akTop, akRight] + BorderSpacing.Right = 12 + BorderSpacing.Bottom = 4 + TabOrder = 5 + Text = 'DF1Edit' + end + object DF2Edit: TEdit + AnchorSideLeft.Control = DF1Edit + AnchorSideTop.Control = DF1Edit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = DF1Edit + AnchorSideRight.Side = asrBottom + Left = 85 + Height = 23 + Top = 140 + Width = 82 + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Bottom = 8 + TabOrder = 6 + Text = 'DF2Edit' + end + object DF2Label: TLabel + AnchorSideTop.Control = DF2Edit + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = DF2Edit + Left = 48 + Height = 15 + Top = 144 + Width = 29 + Anchors = [akTop, akRight] + BorderSpacing.Right = 8 + Caption = 'D.F. 2' + ParentColor = False + end end end object VariablePage: TPage @@ -396,27 +492,30 @@ inherited CompareDistFrm: TCompareDistFrm end object PageControl1: TPageControl[1] Left = 309 - Height = 397 + Height = 488 Top = 8 Width = 607 - ActivePage = ReportPage + ActivePage = CumFreqChartPage Align = alClient BorderSpacing.Left = 4 BorderSpacing.Top = 8 BorderSpacing.Right = 8 BorderSpacing.Bottom = 8 - TabIndex = 0 + TabIndex = 1 TabOrder = 1 object ReportPage: TTabSheet Caption = 'Report' end - object ChartPage: TTabSheet - Caption = 'Chart' + object CumFreqChartPage: TTabSheet + Caption = 'Cumulative frequency plot' + end + object FreqChartPage: TTabSheet + Caption = 'Frequency plot' end end object ParamsSplitter: TSplitter[2] Left = 300 - Height = 413 + Height = 504 Top = 0 Width = 5 ResizeStyle = rsPattern diff --git a/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.pas b/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.pas index 2c3ed8f4f..01a9dc6dd 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.pas +++ b/applications/lazstats/source/forms/analysis/descriptive/comparedistunit.pas @@ -13,18 +13,30 @@ uses BasicStatsFormUnit, ReportFrameUnit, ChartFrameUnit; type + TCompareTo = (ctTheoreticalDistrib, ctVariable); + TCompareDist = (cd_Normal, cd_t, cd_ChiSq, cd_F, cd_Poisson); { TCompareDistFrm } TCompareDistFrm = class(TBasicStatsForm) Bevel1: TBevel; + DF1Edit: TEdit; + DF2Edit: TEdit; + DistGroup: TGroupBox; + DF1Label: TLabel; + DF2Label: TLabel; PageControl1: TPageControl; ParamsSplitter: TSplitter; + NormalDistChk: TRadioButton; + FreqChartPage: TTabSheet; + tDistChk: TRadioButton; + ChiSqDistChk: TRadioButton; + FDistChk: TRadioButton; + PoissonDistChk: TRadioButton; ReportPage: TTabSheet; - ChartPage: TTabSheet; - ThreeDChk: TCheckBox; + CumFreqChartPage: TTabSheet; Notebook: TNotebook; - VertBarBtn: TSpeedButton; + BarPlotBtn: TSpeedButton; LinePlotBtn: TSpeedButton; TheoreticalDistPage: TPage; VariablePage: TPage; @@ -35,7 +47,6 @@ type ComputeBtn: TButton; CloseBtn: TButton; CompareGroup: TRadioGroup; - DistGroup: TRadioGroup; VarOneEdit: TEdit; VarTwoEdit: TEdit; Label2: TLabel; @@ -49,9 +60,9 @@ type procedure CloseBtnClick(Sender: TObject); procedure CompareGroupClick(Sender: TObject); procedure ComputeBtnClick(Sender: TObject); - procedure DistGroupClick(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); + procedure DistChange(Sender: TObject); procedure ResetBtnClick(Sender: TObject); procedure Var1InBtnClick(Sender: TObject); procedure Var1OutBtnClick(Sender: TObject); @@ -62,11 +73,22 @@ type private FReportFrame: TReportFrame; - FChartFrame: TChartFrame; + FCumFreqChartFrame: TChartFrame; + FFreqChartFrame: TChartFrame; FAutoSized: Boolean; - CompareTo: integer; - DistType: integer; + CompareTo: TCompareTo; + CompareDist: TCompareDist; + procedure CalcFreq(XValues, FreqValues, CumFreqValues: DblDyneVec; + AMin, AMax: Double; ANumIntervals, ANumCases: Integer; DF1: Integer = -1; + DF2: Integer = -1); + procedure CalcTheoreticalDist(XValues, FreqValues, CumFreqValues: DblDyneVec; + ANumIntervals, ANumCases: Integer; out AName: String); + procedure Plot(AChartFrame: TChartFrame; Y1Values, Y2Values: DblDyneVec; + AName1, AName2: String); procedure UpdateBtnStates; + procedure UpdateDF1; + function Validate(ANumCases: Integer; + out AMsg: String; out AControl: TWinControl): Boolean; public procedure Reset; override; @@ -83,11 +105,125 @@ implementation uses Math, + TACustomSeries, TASeries, Utils, MathUnit; { TCompareDistFrm } +procedure TCompareDistFrm.CalcFreq(XValues, FreqValues, CumFreqValues: DblDyneVec; + AMin, AMax: Double; ANumIntervals, ANumCases: Integer; DF1: Integer = -1; + DF2: Integer = -1); +var + dx: Double; + i: Integer; + + procedure Calc(AProb1, AProb2: Double); + begin + FreqValues[i] := abs(AProb2 - AProb1) * ANumCases; + end; + +begin + dx := (AMax - AMin) / ANumIntervals; + for i := 0 to ANumIntervals do + XValues[i] := AMin + i * dx; + + for i := 0 to ANumIntervals - 1 do + case CompareDist of + cd_Normal: + Calc(NormalDist(XValues[i]), NormalDist(XValues[i+1])); + cd_t: + Calc(0.5 * ProbT(XValues[i], DF1), 0.5 * ProbT(XValues[i+1], DF1)); + cd_ChiSq: + Calc(ChiSquaredProb(XValues[i], DF1), ChiSquaredProb(XValues[i+1], DF1)); + cd_F: + Calc(ProbF(XValues[i], DF1, DF2), ProbF(XValues[i+1], DF1, DF2)); + cd_Poisson: + Calc(PoissonCDF(round(XValues[i]), DF1), PoissonCDF(round(XValues[i+1]), DF1)); +// Calc(PoissonPDF(round(XValues[i]), DF1), 0); + end; + + CumFreqValues[0] := FreqValues[0]; + for i := 1 to ANumIntervals - 1 do + CumFreqValues[i] := CumFreqValues[i-1] + FreqValues[i]; +end; + + +procedure TCompareDistFrm.CalcTheoreticalDist(XValues, FreqValues, CumFreqValues: DblDyneVec; + ANumIntervals, ANumCases: Integer; out AName: String); +var + min, max: Double; + DF1: Integer = -1; + DF2: Integer = -1; + a: Double; +begin + if TryStrToFloat(DF1Edit.Text, a) then + DF1 := round(a); + if TryStrToFloat(DF2Edit.Text, a) then + DF2 := round(a); + + case CompareDist of + cd_Normal: + begin + min := -3.0; + max := 3.0; + AName := 'Normal dist'; + end; + cd_t: + begin + min := -3.0; + max := 3.0; + AName := 't dist'; + end; + cd_ChiSq: + begin + min := 0.0; + max := 20.0; + AName := 'Chi-sq dist'; + end; + cd_F: + begin + min := 0.0; + max := 2.0; + AName := 'F dist'; + end; + cd_Poisson: + ; // will be handled separately + end; + + CalcFreq(XValues, FreqValues, CumFreqValues, min, max, ANumIntervals, ANumCases, DF1, DF2); +end; + + + +(* + if NormalDistChk.Checked then // normal distribution curve + begin + name2 := 'Normal'; + min2 := -3.0; + max2 := 3.0; + range2 := max2 - min2; + incrsize2 := range2 / noints; + Xvalue2[0] := min2; + Xvalue2[noints] := max2; + for i := 1 to noInts do + begin + Xvalue2[i-1] := min2 + (i-1) * incrSize2; + Xvalue2[i] := min2 + (i) * incrSize2; + prob1 := probz(abs(Xvalue2[i-1])); + prob2 := probz(abs(Xvalue2[i])); + if prob1 > prob2 then + Var2Freq[i-1] := round((prob1 - prob2) * nCases) + else + Var2Freq[i-1] := round((prob2 - prob1) * nCases) + end; + Cumfreq2[0] := Var2Freq[0]; + for i := 1 to noints do + Cumfreq2[i] := Cumfreq2[i-1] + Var2Freq[i]; + end +end; +*) + procedure TCompareDistFrm.CloseBtnClick(Sender: TObject); begin Close; @@ -96,61 +232,55 @@ end; procedure TCompareDistFrm.CompareGroupClick(Sender: TObject); begin - compareTo := CompareGroup.ItemIndex; + CompareTo := TCompareTo(CompareGroup.ItemIndex); Notebook.PageIndex := CompareGroup.ItemIndex; - { - Label3.Enabled := (compareTo = 1); - VarTwoEdit.Enabled := (compareTo = 1); - Var2InBtn.Enabled := (compareTo = 1); - Var2OutBtn.Enabled := (compareTo = 1); - } end; procedure TCompareDistFrm.ComputeBtnClick(Sender: TObject); var - Var1Freq : IntDyneVec = nil; - Var2Freq : IntDyneVec = nil; - XValue1 : DblDyneVec = nil; - XValue2 : DblDyneVec = nil; - Cumfreq1 : DblDyneVec = nil; - Cumfreq2 : DblDyneVec = nil; - i, j, k, col1, col2, Ncases, noints : integer; - min1, max1, min2, max2, range1, range2, value : double; - incrsize1, incrsize2, prob1,prob2, {%H-}KS, mean, DegFree : double; - cellval, name1, name2 : string; - df1, df2 : integer; - xtitle : string; + var1Freq: DblDyneVec = nil; // could be IntDyneVec, but simpler charting this way + var2Freq: DblDyneVec = nil; + xValue1: DblDyneVec = nil; + xValue2: DblDyneVec = nil; + cumfreq1: DblDyneVec = nil; + cumfreq2: DblDyneVec = nil; + i, j, k, col1, col2, nCases, noInts: integer; + min1, max1, min2, max2, range1, range2, value: double; + incrSize1, incrSize2, {%H-}KS: double; + cellVal, name1, name2: string; msg: String; + C: TWinControl; lReport: TStrings; begin - SetLength(Var1Freq, NoCases + 1); - SetLength(Var2Freq, NoCases + 1); - SetLength(XValue1, NoCases + 1); - SetLength(XValue2, NoCases + 1); - SetLength(Cumfreq1, NoCases + 1); - SetLength(Cumfreq2, NoCases + 1); + SetLength(var1Freq, NoCases + 1); + SetLength(var2Freq, NoCases + 1); + SetLength(xValue1, NoCases + 1); + SetLength(xValue2, NoCases + 1); + SetLength(cumfreq1, NoCases + 1); + SetLength(cumfreq2, NoCases + 1); // Get columns of the variables col1 := 0; - col2 := 0; for i := 1 to NoVariables do - begin - if VarOneEdit.Text = OS3MainFrm.DataGrid.Cells[i,0] then col1 := i; - if compareto = 1 then - begin - if VarTwoEdit.Text = OS3MainFrm.DataGrid.Cells[i,0] then col2 := i; - end; - end; + if VarOneEdit.Text = OS3MainFrm.DataGrid.Cells[i, 0] then col1 := i; + col2 := 0; + if CompareTo = ctVariable then + for i := 1 to NoVariables do + if VarTwoEdit.Text = OS3MainFrm.DataGrid.Cells[i, 0] then col2 := i; + + // Check existence of required variables msg := ''; case CompareTo of - 0: if col1 = 0 then - msg := 'Variable not specified.'; - 1: if col1 = 0 then - msg := 'Variable One is not specified.' - else if col2 = 0 then - msg := 'Variable Two is not specified.'; + ctTheoreticalDistrib: + if col1 = 0 then + msg := 'Variable not specified.'; + ctVariable: + if col1 = 0 then + msg := 'Variable One is not specified.' + else if col2 = 0 then + msg := 'Variable Two is not specified.'; end; if msg <> '' then begin @@ -158,88 +288,110 @@ begin exit; end; - // get min and max values for variable in col1 - min1 := 1.0e308; - max1 := -1.0e308; - Ncases := 0; + // Get min and max values for variable in col1, as well as true number of cases + min1 := Infinity; + max1 := -Infinity; + nCases := 0; for j := 1 to NoCases do begin - if not ValidValue(j,col1) then continue; - value := StrToFloat(OS3MainFrm.DataGrid.Cells[col1,j]); + if not ValidValue(j, col1) then continue; + value := StrToFloat(OS3MainFrm.DataGrid.Cells[col1, j]); if value > max1 then max1 := value; if value < min1 then min1 := value; - inc(Ncases); + inc(nCases); end; - noints := NoCases - 1; // number of intervals - if noints > 20 then noints := 20; + // Validate + if not Validate(nCases, msg, C) then begin + C.SetFocus; + ErrorMsg(msg); + end; + + // Get number of intervals + noInts := NoCases - 1; // wp: why NoCases here, and not nCases? + if noInts > 20 then noints := 20; + range1 := max1 - min1 + 1.0; - incrsize1 := range1 / noints; + incrSize1 := range1 / noInts; name1 := VarOneEdit.Text; - if compareTo = 1 then + // Repeat for variable 2 (if Compare To is selected as "Another variable") + if CompareTo = ctVariable then begin - min2 := 1.0e32; - max2 := -1.0e32; + min2 := Infinity; + max2 := -Infinity; for j := 1 to NoCases do begin - if Not ValidValue(j,col2) then continue; - value := StrToFloat(OS3MainFrm.DataGrid.Cells[col2,j]); + if not ValidValue(j, col2) then continue; + value := StrToFloat(OS3MainFrm.DataGrid.Cells[col2, j]); if value > max2 then max2 := value; if value < min2 then min2 := value; end; range2 := max2 - min2 + 1.0; - incrsize2 := range2 / noints; + incrSize2 := range2 / noInts; name2 := VarTwoEdit.Text; end; - //Now, get frequency of cases in each interval + // Get frequency of cases in each interval for j := 1 to noints+1 do - Var1Freq[j-1] := 0; + var1Freq[j-1] := 0; + for j := 1 to NoCases do begin - if Not ValidValue(j,col1) then continue; - value := StrToFloat(OS3MainFrm.DataGrid.Cells[col1,j]); - for k := 1 to noints do + if not ValidValue(j, col1) then continue; + value := StrToFloat(OS3MainFrm.DataGrid.Cells[col1, j]); + for k := 1 to noInts do begin - if (value >= min1 + ((k-1) * incrsize1)) and - (value < min1 + (k * incrsize1)) - then - Var1Freq[k-1] := Var1Freq[k-1] + 1; + if (value >= min1 + (k-1) * incrSize1) and (value < min1 + k * incrSize1) then + var1Freq[k-1] := var1Freq[k-1] + 1; end; end; - Cumfreq1[0] := Var1Freq[0]; - for j := 1 to noints+1 do - XValue1[j-1] := min1 + (j-1) * incrsize1; - for j := 1 to noints do - Cumfreq1[j] := Cumfreq1[j-1] + Var1Freq[j]; - if compareTo = 1 then // do same for second variable - begin - for j := 1 to noints+1 do - Var2Freq[j-1] := 0; - for j := 1 to NoCases do - begin - if Not ValidValue(j,col2) then continue; - value := StrToFloat(OS3MainFrm.DataGrid.Cells[col2,j]); - for k := 1 to noints do - begin - if (value >= min2 + ((k-1) * incrsize2)) and - (value < min2 + (k * incrsize2)) - then - Var2Freq[k-1] := Var2Freq[k-1] + 1; - end; - end; - Cumfreq2[0] := Var2Freq[0]; - for j := 1 to noints+1 do - XValue2[j-1] := min2 + (j-1) * incrsize2; - for j := 1 to noints do - Cumfreq2[j] := Cumfreq2[j-1] + Var2Freq[j]; - end; - // Get theoretical distribution frequencies for selected dist. - if compareTo = 0 then + cumFreq1[0] := var1Freq[0]; + for j := 1 to noInts+1 do + xValue1[j-1] := min1 + (j-1) * incrSize1; + for j := 1 to noInts do + cumFreq1[j] := cumFreq1[j-1] + var1Freq[j]; + + // Repeat for 2nd variable, if required + if CompareTo = ctVariable then begin - if DistGroup.ItemIndex = 0 then // normal curve + for j := 1 to noInts+1 do + var2Freq[j-1] := 0; + + for j := 1 to NoCases do + begin + if not ValidValue(j, col2) then continue; + value := StrToFloat(OS3MainFrm.DataGrid.Cells[col2, j]); + for k := 1 to noInts do + begin + if (value >= min2 + (k-1) * incrsize2) and (value < min2 + k * incrsize2) then + var2Freq[k-1] := var2Freq[k-1] + 1; + end; + end; + + cumfreq2[0] := var2Freq[0]; + for j := 1 to noInts+1 do + xValue2[j-1] := min2 + (j-1) * incrSize2; + for j := 1 to noInts do + cumFreq2[j] := cumFreq2[j-1] + var2Freq[j]; + end; + + // Get theoretical distribution frequencies for selected distribution, if required. + if CompareDist = cd_Poisson then + begin + CalcFreq(xValue2, var2Freq, cumFreq2, min1, min2, noInts, nCases, StrToInt(DF1Edit.Text)); + name2 := 'Poisson'; + end + else + CalcTheoreticalDist(xValue2, var2Freq, cumFreq2, noInts, nCases, name2); + + (* + + + if CompareTo = ctTheoreticalDistrib then + begin + if NormalDistChk.Checked then // normal distribution curve begin name2 := 'Normal'; min2 := -3.0; @@ -248,76 +400,76 @@ begin incrsize2 := range2 / noints; Xvalue2[0] := min2; Xvalue2[noints] := max2; - for i := 1 to noints do + for i := 1 to noInts do begin - Xvalue2[i-1] := min2 + (i-1) * incrsize2; - Xvalue2[i] := min2 + (i) * incrsize2; + Xvalue2[i-1] := min2 + (i-1) * incrSize2; + Xvalue2[i] := min2 + (i) * incrSize2; prob1 := probz(abs(Xvalue2[i-1])); prob2 := probz(abs(Xvalue2[i])); if prob1 > prob2 then - Var2Freq[i-1] := round((prob1-prob2) * Ncases) + Var2Freq[i-1] := round((prob1 - prob2) * nCases) else - Var2Freq[i-1] := round((prob2-prob1) * Ncases) + Var2Freq[i-1] := round((prob2 - prob1) * nCases) end; Cumfreq2[0] := Var2Freq[0]; for i := 1 to noints do Cumfreq2[i] := Cumfreq2[i-1] + Var2Freq[i]; end else - if DistGroup.ItemIndex = 1 then // t-distribution + if tDistChk.Checked then // t-distribution begin name2 := 't-Dist.'; min2 := -3.0; max2 := 3.0; - df1 := Ncases - 1; + df1 := nCases - 1; range2 := max2 - min2; incrsize2 := range2 / noints; Xvalue2[0] := min2; Xvalue2[noints] := max2; - for i := 1 to noints do + for i := 1 to noInts do begin - Xvalue2[i-1] := min2 + (i-1) * incrsize2; - Xvalue2[i] := min2 + (i) * incrsize2; + Xvalue2[i-1] := min2 + (i-1) * incrSize2; + Xvalue2[i] := min2 + (i) * incrSize2; prob1 := 0.5 * probt(Xvalue2[i-1],df1); prob2 := 0.5 * probt(Xvalue2[i],df1); if prob1 > prob2 then - Var2Freq[i-1] := round((prob1-prob2) * Ncases) + Var2Freq[i-1] := round((prob1-prob2) * nCases) else - Var2Freq[i-1] := round((prob2-prob1) * Ncases) + Var2Freq[i-1] := round((prob2-prob1) * nCases) end; Cumfreq2[0] := Var2Freq[0]; - for i := 1 to noints do + for i := 1 to noInts do Cumfreq2[i] := Cumfreq2[i-1] + Var2Freq[i]; end else - if DistGroup.ItemIndex = 2 then // chi squared distribution + if ChiSqDistChk.Checked then // chi squared distribution begin cellval := InputBox('Deg. Freedom 1 Entry','DF 1',''); df1 := StrToInt(cellval); - name2 := 'Chi Sqrd'; + name2 := 'Chi Sq'; min2 := 0.0; max2 := 20.0; range2 := max2 - min2; - incrsize2 := range2 / noints; - Xvalue2[0] := min2; - Xvalue2[noints] := max2; + incrSize2 := range2 / noInts; + xValue2[0] := min2; + xValue2[noInts] := max2; for i := 1 to noints do begin - Xvalue2[i-1] := min2 + (i-1) * incrsize2; - Xvalue2[i] := min2 + (i) * incrsize2; + Xvalue2[i-1] := min2 + (i-1) * incrSize2; + Xvalue2[i] := min2 + (i) * incrSize2; prob1 := chisquaredprob(Xvalue2[i-1],df1); prob2 := chisquaredprob(Xvalue2[i],df1); if prob1 > prob2 then - Var2Freq[i-1] := round((prob1-prob2) * Ncases) + Var2Freq[i-1] := round((prob1-prob2) * nCases) else - Var2Freq[i-1] := round((prob2-prob1) * Ncases) + Var2Freq[i-1] := round((prob2-prob1) * nCases) end; - Cumfreq2[0] := Var2Freq[0]; + cumfreq2[0] := var2Freq[0]; for i := 1 to noints do - Cumfreq2[i] := Cumfreq2[i-1] + Var2Freq[i]; + cumfreq2[i] := cumfreq2[i-1] + var2Freq[i]; end else - if DistGroup.ItemIndex = 3 then // F distribution + if FDistChk.Checked then // F distribution begin // get degrees of freedom cellval := InputBox('Deg. Freedom 1 Entry','DF 1',''); @@ -331,23 +483,23 @@ begin incrsize2 := range2 / noints; Xvalue2[0] := min2; Xvalue2[noints] := max2; - for i := 1 to noints do + for i := 1 to noInts do begin - Xvalue2[i-1] := min2 + (i-1) * incrsize2; - Xvalue2[i] := min2 + (i) * incrsize2; + Xvalue2[i-1] := min2 + (i-1) * incrSize2; + Xvalue2[i] := min2 + (i) * incrSize2; prob1 := ProbF(Xvalue2[i-1],df1,df2); prob2 := ProbF(Xvalue2[i],df1,df2); if prob1 > prob2 then - Var2Freq[i-1] := round((prob1-prob2) * Ncases) + Var2Freq[i-1] := round((prob1-prob2) * nCases) else - Var2Freq[i-1] := round((prob2-prob1) * Ncases) + Var2Freq[i-1] := round((prob2-prob1) * nCases) end; - Cumfreq2[0] := Var2Freq[0]; - for i := 1 to noints do - Cumfreq2[i] := Cumfreq2[i-1] + Var2Freq[i]; + cumfreq2[0] := Var2Freq[0]; + for i := 1 to noInts do + cumfreq2[i] := cumfreq2[i-1] + var2Freq[i]; end else - if DistGroup.ItemIndex = 4 then // Poisson distribution + if PoissonDistChk.Checked then // Poisson distribution begin name2 := 'Poisson'; mean := 0; // use as parameter a in pdf call @@ -358,33 +510,34 @@ begin ErrorMsg('Value > 13 found. Factorial too large - exiting.'); exit; end; - for i := 1 to Ncases do - mean := mean + StrToFloat(OS3MainFrm.DataGrid.Cells[col1,i]); - mean := mean / Ncases; + for i := 1 to nCases do + mean := mean + StrToFloat(OS3MainFrm.DataGrid.Cells[col1, i]); + mean := mean / nCases; cellval := IntToStr(round(mean)); - cellval := InputBox('Parameter Entry (mean)','DF 1',cellval); + cellval := InputBox('Parameter Entry (mean)', 'DF 1', cellval); degfree := StrToFloat(cellval); range2 := max2 - min2; - incrsize2 := range2 / noints; + incrsize2 := range2 / noInts; // Xvalue2[0] := min2; Xvalue2[noints] := max2; for i := 1 to noints do begin - Xvalue2[i-1] := min2 + (i-1) * incrsize2; - Xvalue2[i] := min2 + (i) * incrsize2; + Xvalue2[i-1] := min2 + (i-1) * incrSize2; + Xvalue2[i] := min2 + (i) * incrSize2; poisson_pdf ( round(Xvalue2[i-1]), degfree, prob1 ); // prob1 := (Xvalue2[i-1],df1); // prob2 := chisquaredprob(Xvalue2[i],df1); // if prob1 > prob2 then - Var2Freq[i-1] := round((prob1) * Ncases); + Var2Freq[i-1] := round((prob1) * nCases); // else Var2Freq[i-1] := round((prob2-prob1) * Ncases) end; - Cumfreq2[0] := Var2Freq[0]; - for i := 1 to noints do - Cumfreq2[i] := Cumfreq2[i-1] + Var2Freq[i]; + cumfreq2[0] := var2Freq[0]; + for i := 1 to noInts do + cumfreq2[i] := cumfreq2[i-1] + var2Freq[i]; end; end; + *) lReport := TStringList.Create; try lReport.Add('DISTRIBUTION COMPARISON by Bill Miller'); @@ -395,12 +548,14 @@ begin lReport.Add('%10s %10s %10s %10s %10s %10s', [ 'X1 Value', 'Frequency', 'Cum. Freq.', 'X2 Value', 'Frequency', 'Cum. Freq.' ]); + lReport.Add('---------- ---------- ---------- ---------- ---------- ----------'); for i := 1 to noints do - lReport.Add('%10.3f %10d %10.3f %10.3f %10d %10.3f', [ + lReport.Add('%10.3f %10.0f %10.3f %10.3f %10.0f %10.3f', [ XValue1[i-1], Var1Freq[i-1], Cumfreq1[i-1], XValue2[i-1], Var2Freq[i-1], Cumfreq2[i-1] ]); + lReport.Add(''); cellval := 'D'; - KS := KolmogorovTest(noints, Cumfreq1,noints, Cumfreq2, cellval, lReport); + KS := KolmogorovTest(noInts, Cumfreq1, noInts, Cumfreq2, cellVal, lReport); // lReport.Add('Kolmogorov-Smirnov statistic := %5.3f', [KS]); FReportFrame.DisplayReport(lReport); @@ -409,6 +564,17 @@ begin end; // plot the cdfs + Plot(FCumFreqChartFrame, cumFreq1, cumFreq2, VarOneEdit.Text, name2); + + if BothChk.Checked then + Plot(FFreqChartFrame, var1Freq, var2Freq, VarOneEdit.Text, name2); + + FreqChartPage.TabVisible := BothChk.Checked; + + (* + + + xtitle := 'Red = ' + VarOneEdit.Text + ' Blue = ' + name2; cellval := 'Plot of Cumulative Distributions'; { @@ -489,6 +655,7 @@ begin GraphFrm.Xpoints := nil; GraphFrm.Ypoints := nil; end; + *) // clean up Cumfreq2 := nil; @@ -500,9 +667,36 @@ begin end; -procedure TCompareDistFrm.DistGroupClick(Sender: TObject); +procedure TCompareDistFrm.DistChange(Sender: TObject); begin - DistType := DistGroup.ItemIndex; + DF1Edit.Visible := CompareDist <> cd_Normal; + DF1Label.Visible := DF1Edit.Visible; + DF1Label.Caption := 'D.F.'; + + DF2Edit.Visible := CompareDist = cd_F; + DF2Label.Visible := DF2Edit.Visible; + + if NormalDistChk.Checked then + CompareDist := cd_Normal + else if tDistChk.Checked then + begin + CompareDist := cd_t; + UpdateDF1; + end + else if ChiSqDistChk.Checked then + CompareDist := cd_ChiSq + else if FDistChk.Checked then + begin + CompareDist := cd_F; + DF1Label.Caption := 'D.F. 1'; + end + else if PoissonDistChk.Checked then + begin + CompareDist := cd_Poisson; + DF1Label.Caption := 'Mean'; + UpdateDF1; + end else + raise Exception.Create('Distribution not supported.'); end; @@ -534,7 +728,7 @@ begin if Width < Constraints.MinWidth then Width := 1; if Height < Constraints.MinHeight then Height := 1; - Notebook.AutoSize := false; +// Notebook.AutoSize := false; Position := poDesigned; FAutoSized := true; end; @@ -551,31 +745,81 @@ begin FReportFrame.Parent := ReportPage; FReportFrame.Align := alClient; - FChartFrame := TChartFrame.Create(self); - FChartFrame.Parent := ChartPage; - FChartFrame.Align := alClient; - FChartFrame.Chart.BottomAxis.Intervals.MaxLength := 80; - FChartFrame.Chart.BottomAxis.Intervals.MinLength := 30; + FCumFreqChartFrame := TChartFrame.Create(self); + FCumFreqChartFrame.Parent := CumFreqChartPage; + FCumFreqChartFrame.Align := alClient; + FCumFreqChartFrame.Chart.BottomAxis.Intervals.MaxLength := 80; + FCumFreqChartFrame.Chart.BottomAxis.Intervals.MinLength := 30; + FCumFreqChartFrame.SetYTitle('Cumulative frequency'); + FCumFreqChartFrame.SetTitle('Plot of Cumulative Distributions'); + + FFreqChartFrame := TChartFrame.Create(self); + FFreqChartFrame.Parent := FreqChartPage; + FFreqChartFrame.Align := alClient; + FFreqChartFrame.Chart.BottomAxis.Intervals.MaxLength := 80; + FFreqChartFrame.Chart.BottomAxis.Intervals.MinLength := 30; + FFreqChartFrame.SetYTitle('Frequency'); + FFreqChartFrame.SetTitle('Plot of Distributions'); Reset; end; +procedure TCompareDistFrm.Plot(AChartFrame: TChartFrame; Y1Values, Y2Values: DblDyneVec; + AName1, AName2: String); +var + ser1, ser2: TChartSeries; + plotType: TPlotType; +begin + AChartFrame.Clear; + + if BarPlotBtn.Down then + plotType := ptBars + else + plotType := ptLines; + + ser1 := AChartFrame.PlotXY(plotType, nil, Y1Values, nil, nil, AName1, DATA_COLORS[0]); + ser2 := AChartFrame.PlotXY(plotType, nil, Y2Values, nil, nil, AName2, DATA_Colors[1]); + + if (ser1 is TBarSeries) then + begin + with ser1 as TBarSeries do + begin + BarWidthPercent := 40; + BarOffsetPercent := -20; + end; + with ser2 as TBarSeries do + begin + BarWidthPercent := 40; + BarOffsetPercent := +20; + end; + end; +end; + + procedure TCompareDistFrm.Reset; var i: integer; begin VarList.Clear; + VarOneEdit.Text := ''; VarTwoEdit.Text := ''; + DF1Edit.Text := ''; + DF2Edit.Text := ''; + for i := 1 to NoVariables do VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); + CompareGroup.ItemIndex := 0; CompareGroupClick(nil); - DistGroup.ItemIndex := 0; + NormalDistChk.Checked := true; + DistChange(nil); FReportFrame.Clear; - FChartFrame.Clear; + FCumFreqChartFrame.Clear; + FFreqChartFrame.Clear; + FreqChartPage.TabVisible := false; end; @@ -593,7 +837,109 @@ begin Var2OutBtn.Enabled := VarTwoEdit.Text <> ''; FReportFrame.UpdateBtnStates; - FChartFrame.UpdateBtnStates; + FCumFreqChartFrame.UpdateBtnStates; + FFreqChartFrame.UpdateBtnStates; +end; + + +procedure TCompareDistFrm.UpdateDF1; + + procedure DFCandidates(AVarName: String; out AMean: Double; out ANumCases: Integer); + var + col, i: Integer; + begin + for col := 1 to NoVariables do + if AVarName = OS3MainFrm.DataGrid.Cells[col, 0] then + begin + AMean := 0; + ANumCases := 0; + for i := 1 to NoCases do + if ValidValue(i, col) then + begin + AMean := AMean + StrToFloat(OS3MainFrm.DataGrid.Cells[col, i]); + inc(ANumCases); + end; + if ANumCases > 1 then + AMean := AMean / ANumCases + else + AMean := NaN; + exit; + end; + AMean := NaN; + ANumCases := -1; + end; + +var + m: Double; + n: Integer; +begin + if (CompareTo = ctTheoreticalDistrib) then + begin + DFCandidates(VarOneEdit.Text, m, n); + case CompareDist of + cd_t : + if n > 0 then + DF1Edit.Text := IntToStr(n-1); + cd_Poisson: + if not IsNaN(m) then + DF1Edit.Text := FormatFloat('0', m); + end; + end; +end; + + +function TCompareDistFrm.Validate(ANumCases: Integer; + out AMsg: String; out AControl: TWinControl): Boolean; +var + n: Integer = 0; +begin + Result := false; + + if CompareDist <> cd_Normal then + begin + if DF1Edit.Text = '' then + begin + AMsg := 'This control cannot be empty.'; + AControl := DF1Edit; + exit; + end; + if not TryStrToInt(DF1Edit.Text, n) or (n < 0) then + begin + AMsg := 'Positive integer value required.'; + AControl := DF1Edit; + exit; + end; + if (n >= ANumCases) and (CompareDist <> cd_Poisson) then + begin + AMsg := 'Degrees of freedom cannot be greater than the number of cases.'; + AControl := DF1Edit; + exit; + end; + end; + + if CompareDist = cd_F then + begin + if DF2Edit.Text = '' then + begin + AMsg := 'This control cannot be empty.'; + AControl := DF2Edit; + exit; + end; + if not TryStrToInt(DF2Edit.Text, n) or (n < 0) then + begin + AMsg := 'Positive integer value required.'; + AControl := DF2Edit; + exit; + end; + if n >= ANumCases then + begin + AMsg := 'Degrees of freedom cannot be greater than the number of cases.'; + AControl := DF2Edit; + exit; + end; + end; + + Result := true; end; @@ -613,6 +959,7 @@ begin inc(i); end; UpdateBtnStates; + UpdateDF1; end; diff --git a/applications/lazstats/source/frames/chartframeunit.pas b/applications/lazstats/source/frames/chartframeunit.pas index 1874f4fdc..ef0915448 100644 --- a/applications/lazstats/source/frames/chartframeunit.pas +++ b/applications/lazstats/source/frames/chartframeunit.pas @@ -69,6 +69,7 @@ uses constructor TChartFrame.Create(AOwner: TComponent); begin inherited; + Name := ''; {$IF LCL_FullVersion >= 2010000} ZoomDragTool.LimitToExtent := [zdDown]; PanDragTool.LimitToExtent := [pdDown]; @@ -174,7 +175,10 @@ begin end; end; ptBars: - Result := TBarSeries.Create(self); + begin + Result := TBarSeries.Create(self); + TBarSeries(Result).BarBrush.Color := AColor; + end; ptArea: Result := TAreaSeries.Create(self); else