diff --git a/applications/lazstats/source/forms/analysis/descriptive/normalityunit.lfm b/applications/lazstats/source/forms/analysis/descriptive/normalityunit.lfm index 287aede25..6e2c5e422 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/normalityunit.lfm +++ b/applications/lazstats/source/forms/analysis/descriptive/normalityunit.lfm @@ -1,417 +1,221 @@ object NormalityFrm: TNormalityFrm - Left = 721 - Height = 396 - Top = 294 - Width = 402 + Left = 628 + Height = 516 + Top = 275 + Width = 998 HelpType = htKeyword HelpKeyword = 'html/NormalityTests.htm' Caption = 'Normality Tests' - ClientHeight = 396 - ClientWidth = 402 + ClientHeight = 516 + ClientWidth = 998 OnActivate = FormActivate OnCreate = FormCreate - OnShow = FormShow Position = poMainFormCenter LCLVersion = '2.1.0.0' - object Label1: TLabel - AnchorSideLeft.Control = Owner - AnchorSideTop.Control = Owner + object ParamsPanel: TPanel Left = 8 - Height = 15 + Height = 500 Top = 8 - Width = 49 + Width = 320 + Align = alLeft BorderSpacing.Left = 8 BorderSpacing.Top = 8 - Caption = 'Variables:' - ParentColor = False - end - object Label2: TLabel - AnchorSideLeft.Control = TestVarEdit - AnchorSideBottom.Control = VarInBtn - AnchorSideBottom.Side = asrBottom - Left = 261 - Height = 15 - Top = 36 - Width = 93 - Anchors = [akLeft, akBottom] - BorderSpacing.Bottom = 2 - Caption = 'Test Normality of:' - ParentColor = False - end - object VarList: TListBox - AnchorSideLeft.Control = Owner - AnchorSideTop.Control = Label1 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = VarInBtn - AnchorSideBottom.Control = Bevel1 - Left = 8 - Height = 313 - Top = 25 - Width = 209 - Anchors = [akTop, akLeft, akRight, akBottom] - BorderSpacing.Left = 8 - BorderSpacing.Top = 2 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - ItemHeight = 0 - OnSelectionChange = VarListSelectionChange - TabOrder = 0 - end - object VarInBtn: TBitBtn - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = VarList - AnchorSideRight.Control = VarList - AnchorSideRight.Side = asrBottom - Left = 225 - Height = 28 - Top = 25 - Width = 28 - Images = MainDataModule.ImageList - ImageIndex = 1 - OnClick = VarInBtnClick - Spacing = 0 - TabOrder = 1 - end - object VarOutBtn: TBitBtn - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = VarInBtn - AnchorSideTop.Side = asrBottom - Left = 225 - Height = 28 - Top = 57 - Width = 28 - BorderSpacing.Top = 4 - Images = MainDataModule.ImageList - ImageIndex = 0 - OnClick = VarOutBtnClick - Spacing = 0 - TabOrder = 2 - end - object TestVarEdit: TEdit - AnchorSideLeft.Control = VarOutBtn - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Label2 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - Left = 261 - Height = 23 - Top = 53 - Width = 133 - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Left = 8 - BorderSpacing.Top = 2 - BorderSpacing.Right = 8 - TabOrder = 3 - Text = 'TestVarEdit' - end - object GroupBox1: TGroupBox - AnchorSideTop.Control = VarOutBtn - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - Left = 225 - Height = 80 - Top = 97 - Width = 169 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Top = 12 - BorderSpacing.Right = 8 - Caption = 'Shapiro-Wilkes Rresults' - ClientHeight = 60 - ClientWidth = 165 - TabOrder = 4 - object Label3: TLabel - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = WEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 6 - Width = 33 - BorderSpacing.Left = 12 - Caption = 'WWW' - ParentColor = False - end - object Label4: TLabel - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = ProbEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 33 - Width = 57 - BorderSpacing.Left = 12 - Caption = 'Probability' - ParentColor = False - end - object WEdit: TEdit - AnchorSideLeft.Control = ProbEdit - AnchorSideTop.Control = GroupBox1 - AnchorSideRight.Control = GroupBox1 - AnchorSideRight.Side = asrBottom - Left = 77 - Height = 23 - Top = 2 - Width = 80 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 2 - BorderSpacing.Right = 8 - ReadOnly = True - TabOrder = 0 - Text = 'WEdit' - end - object ProbEdit: TEdit - AnchorSideLeft.Control = Label4 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = WEdit - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = GroupBox1 - AnchorSideRight.Side = asrBottom - Left = 77 - Height = 23 - Top = 29 - Width = 80 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Left = 8 - BorderSpacing.Top = 4 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - ReadOnly = True - TabOrder = 1 - Text = 'ProbEdit' - end - end - object GroupBox2: TGroupBox - AnchorSideLeft.Control = GroupBox1 - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - Left = 225 - Height = 155 - Top = 191 - Width = 169 - Anchors = [akTop, akLeft, akRight] - AutoSize = True - BorderSpacing.Right = 8 - Caption = 'Lilliefors Test Results' - ClientHeight = 135 - ClientWidth = 165 - TabOrder = 5 - object Label5: TLabel - AnchorSideLeft.Control = GroupBox2 - AnchorSideTop.Control = SkewEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 6 - Width = 53 - BorderSpacing.Left = 12 - Caption = 'Skewness:' - ParentColor = False - end - object Label6: TLabel - AnchorSideLeft.Control = GroupBox2 - AnchorSideTop.Control = KurtosisEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 33 - Width = 45 - BorderSpacing.Left = 12 - Caption = 'Kurtosis:' - ParentColor = False - end - object Label7: TLabel - AnchorSideLeft.Control = GroupBox2 - AnchorSideTop.Control = StatEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 60 - Width = 67 - BorderSpacing.Left = 12 - Caption = 'Test Statistic:' - ParentColor = False - end - object SkewEdit: TEdit - AnchorSideLeft.Control = StatEdit - AnchorSideTop.Control = GroupBox2 - AnchorSideRight.Control = GroupBox2 - AnchorSideRight.Side = asrBottom - Left = 87 - Height = 23 - Top = 2 - Width = 70 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 2 - BorderSpacing.Right = 8 - ReadOnly = True - TabOrder = 0 - Text = 'SkewEdit' - end - object KurtosisEdit: TEdit - AnchorSideLeft.Control = StatEdit - AnchorSideTop.Control = SkewEdit - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = GroupBox2 - AnchorSideRight.Side = asrBottom - Left = 87 - Height = 23 - Top = 29 - Width = 70 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 4 - BorderSpacing.Right = 8 - ReadOnly = True - TabOrder = 1 - Text = 'KurtosisEdit' - end - object StatEdit: TEdit - AnchorSideLeft.Control = Label7 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = KurtosisEdit - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = GroupBox2 - AnchorSideRight.Side = asrBottom - Left = 87 - Height = 23 - Top = 56 - Width = 70 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Left = 8 - BorderSpacing.Top = 4 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - ReadOnly = True - TabOrder = 2 - Text = 'StatEdit' - end - object Label8: TLabel - AnchorSideLeft.Control = Label5 - AnchorSideTop.Control = StatEdit - AnchorSideTop.Side = asrBottom - Left = 12 - Height = 15 - Top = 87 - Width = 104 - Caption = 'Lillifors Conclusion:' - ParentColor = False - WordWrap = True - end - object ConclusionEdit: TEdit - AnchorSideLeft.Control = Label8 - AnchorSideTop.Control = Label8 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = GroupBox2 - AnchorSideRight.Side = asrBottom - Left = 12 - Height = 23 - Top = 104 - Width = 145 - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 2 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - ReadOnly = True - TabOrder = 3 - Text = 'ConclusionEdit' - end - end - object Panel1: TPanel - AnchorSideRight.Side = asrBottom - Left = 8 - Height = 26 - Top = 362 - Width = 386 - Align = alBottom - AutoSize = True - BorderSpacing.Left = 8 - BorderSpacing.Top = 8 - BorderSpacing.Right = 8 + BorderSpacing.Right = 4 BorderSpacing.Bottom = 8 BevelOuter = bvNone - ClientHeight = 26 - ClientWidth = 386 - TabOrder = 6 - object CloseBtn: TButton - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrCenter - AnchorSideRight.Control = Panel1 + ClientHeight = 500 + ClientWidth = 320 + TabOrder = 0 + object Panel1: TPanel + AnchorSideRight.Control = ParamsPanel AnchorSideRight.Side = asrBottom - Left = 319 - Height = 25 - Top = 1 - Width = 55 - Anchors = [akTop, akRight] + Left = 0 + Height = 26 + Top = 474 + Width = 320 + Align = alBottom AutoSize = True - BorderSpacing.Right = 12 - Caption = 'Close' - ModalResult = 11 - TabOrder = 3 + BorderSpacing.Top = 6 + BevelOuter = bvNone + ClientHeight = 26 + ClientWidth = 320 + TabOrder = 0 + object CloseBtn: TButton + AnchorSideTop.Control = Panel1 + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = Panel1 + AnchorSideRight.Side = asrBottom + Left = 265 + Height = 25 + Top = 1 + Width = 55 + Anchors = [akTop, akRight] + AutoSize = True + BorderSpacing.Left = 8 + Caption = 'Close' + ModalResult = 11 + TabOrder = 0 + end + object ComputeBtn: TButton + AnchorSideTop.Control = Panel1 + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = CloseBtn + Left = 181 + Height = 25 + Top = 1 + Width = 76 + Anchors = [akTop, akRight] + AutoSize = True + BorderSpacing.Left = 8 + Caption = 'Compute' + OnClick = ComputeBtnClick + TabOrder = 1 + end + object ResetBtn: TButton + AnchorSideTop.Control = Panel1 + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = ComputeBtn + Left = 119 + Height = 25 + Top = 1 + Width = 54 + Anchors = [akTop, akRight] + AutoSize = True + BorderSpacing.Right = 8 + Caption = 'Reset' + OnClick = ResetBtnClick + TabOrder = 2 + end end - object ApplyBtn: TButton - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrCenter - AnchorSideRight.Control = CloseBtn - Left = 254 - Height = 25 - Top = 1 - Width = 57 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Right = 8 - Caption = 'Apply' - OnClick = ApplyBtnClick - TabOrder = 2 + object Bevel1: TBevel + AnchorSideLeft.Control = ParamsPanel + AnchorSideRight.Control = ParamsPanel + AnchorSideRight.Side = asrBottom + AnchorSideBottom.Control = Panel1 + Left = 0 + Height = 8 + Top = 460 + Width = 320 + Anchors = [akLeft, akRight, akBottom] + Shape = bsBottomLine end - object PrintBtn: TButton - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrCenter - AnchorSideRight.Control = ApplyBtn - Left = 195 - Height = 25 - Top = 1 - Width = 51 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Right = 8 - Caption = 'Print' - OnClick = PrintBtnClick + object Label1: TLabel + AnchorSideLeft.Control = ParamsPanel + AnchorSideTop.Control = ParamsPanel + Left = 0 + Height = 15 + Top = 0 + Width = 49 + Caption = 'Variables:' + ParentColor = False + end + object VarList: TListBox + AnchorSideLeft.Control = ParamsPanel + AnchorSideTop.Control = Label1 + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = VarInBtn + AnchorSideBottom.Control = Bevel1 + Left = 0 + Height = 443 + Top = 17 + Width = 140 + Anchors = [akTop, akLeft, akRight, akBottom] + BorderSpacing.Top = 2 + BorderSpacing.Right = 6 + ItemHeight = 0 + OnDblClick = VarListDblClick + OnSelectionChange = VarListSelectionChange TabOrder = 1 end - object ResetBtn: TButton - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrCenter - AnchorSideRight.Control = PrintBtn - Left = 133 - Height = 25 - Top = 1 - Width = 54 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Left = 12 - BorderSpacing.Right = 8 - Caption = 'Reset' - OnClick = ResetBtnClick - TabOrder = 0 + object VarInBtn: TBitBtn + AnchorSideLeft.Control = ParamsPanel + AnchorSideLeft.Side = asrCenter + AnchorSideTop.Control = VarList + AnchorSideRight.Control = VarList + AnchorSideRight.Side = asrBottom + Left = 146 + Height = 28 + Top = 17 + Width = 28 + Images = MainDataModule.ImageList + ImageIndex = 1 + OnClick = VarInBtnClick + Spacing = 0 + TabOrder = 2 + end + object VarOutBtn: TBitBtn + AnchorSideLeft.Control = ParamsPanel + AnchorSideLeft.Side = asrCenter + AnchorSideTop.Control = VarInBtn + AnchorSideTop.Side = asrBottom + Left = 146 + Height = 28 + Top = 49 + Width = 28 + BorderSpacing.Top = 4 + BorderSpacing.Bottom = 8 + Images = MainDataModule.ImageList + ImageIndex = 0 + OnClick = VarOutBtnClick + Spacing = 0 + TabOrder = 3 + end + object TestVarEdit: TEdit + AnchorSideLeft.Control = VarOutBtn + AnchorSideLeft.Side = asrBottom + AnchorSideTop.Control = Label2 + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = ParamsPanel + AnchorSideRight.Side = asrBottom + Left = 180 + Height = 23 + Top = 45 + Width = 140 + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 6 + BorderSpacing.Top = 2 + TabOrder = 4 + Text = 'TestVarEdit' + end + object Label2: TLabel + AnchorSideLeft.Control = TestVarEdit + AnchorSideBottom.Control = VarInBtn + AnchorSideBottom.Side = asrBottom + Left = 180 + Height = 15 + Top = 28 + Width = 93 + Anchors = [akLeft, akBottom] + BorderSpacing.Bottom = 2 + Caption = 'Test Normality of:' + ParentColor = False end end - object Bevel1: TBevel - AnchorSideLeft.Control = Owner - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - AnchorSideBottom.Control = Panel1 - Left = 0 - Height = 8 - Top = 346 - Width = 402 - Anchors = [akLeft, akRight, akBottom] - Shape = bsBottomLine + object ParamsSplitter: TSplitter + Left = 332 + Height = 516 + Top = 0 + Width = 5 + ResizeStyle = rsPattern + end + object PageControl: TPageControl + Left = 341 + Height = 500 + Top = 8 + Width = 649 + ActivePage = ChartPage + Align = alClient + BorderSpacing.Left = 4 + BorderSpacing.Top = 8 + BorderSpacing.Right = 8 + BorderSpacing.Bottom = 8 + TabIndex = 1 + TabOrder = 2 + object ReportPage: TTabSheet + Caption = 'Report' + end + object ChartPage: TTabSheet + Caption = 'Chart' + end end end diff --git a/applications/lazstats/source/forms/analysis/descriptive/normalityunit.pas b/applications/lazstats/source/forms/analysis/descriptive/normalityunit.pas index 72c946a3c..6c3f0ab0c 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/normalityunit.pas +++ b/applications/lazstats/source/forms/analysis/descriptive/normalityunit.pas @@ -7,9 +7,9 @@ unit NormalityUnit; interface uses - Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, - StdCtrls, Buttons, ExtCtrls, - MainUnit, Globals, FunctionsLib, DataProcs, OutputUnit; + Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, + StdCtrls, Buttons, ExtCtrls, ComCtrls, + MainUnit, Globals, FunctionsLib, DataProcs, ReportFrameUnit, ChartFrameUnit; type @@ -18,48 +18,45 @@ type TNormalityFrm = class(TForm) Bevel1: TBevel; + PageControl: TPageControl; + ParamsPanel: TPanel; ResetBtn: TButton; - PrintBtn: TButton; - ApplyBtn: TButton; + ComputeBtn: TButton; CloseBtn: TButton; - ConclusionEdit: TEdit; - Label8: TLabel; Panel1: TPanel; - StatEdit: TEdit; - KurtosisEdit: TEdit; - SkewEdit: TEdit; - GroupBox2: TGroupBox; - Label5: TLabel; - Label6: TLabel; - Label7: TLabel; - ProbEdit: TEdit; - Label4: TLabel; - WEdit: TEdit; - GroupBox1: TGroupBox; - Label3: TLabel; + ParamsSplitter: TSplitter; + ReportPage: TTabSheet; + ChartPage: TTabSheet; TestVarEdit: TEdit; Label2: TLabel; VarInBtn: TBitBtn; VarOutBtn: TBitBtn; Label1: TLabel; VarList: TListBox; - procedure ApplyBtnClick(Sender: TObject); + procedure ComputeBtnClick(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); - procedure FormShow(Sender: TObject); - procedure PrintBtnClick(Sender: TObject); procedure ResetBtnClick(Sender: TObject); procedure VarInBtnClick(Sender: TObject); + procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; User: boolean); procedure VarOutBtnClick(Sender: TObject); private { private declarations } + FReportFrame: TReportFrame; + FChartFrame: TChartFrame; FAutoSized: boolean; - function Norm(z : double) : double; + function Calc_ShapiroWilks(const AData: DblDyneVec; out W, Prob: Double): Boolean; + procedure Calc_Lilliefors(const AData: DblDyneVec; + out ASkew, AKurtosis, AStat: Double; out AConclusion: String); + function Norm(z : double): double; + procedure PlotData(AData: DblDyneVec); + function PrepareData(const VarName: String): DblDyneVec; procedure UpdateBtnStates; public { public declarations } + procedure Reset; end; var @@ -67,50 +64,16 @@ var implementation +{$R *.lfm} + uses - Math; + Math, + TAChartUtils, TATextElements, TACustomSeries, TATransformations, + TACustomSource, TASources, + Utils; { TNormalityFrm } -procedure TNormalityFrm.PrintBtnClick(Sender: TObject); -var - lReport: TStrings; -begin - lReport := TStringList.Create; - try - lReport.Add('NORMALITY TESTS FOR '+ TestVarEdit.Text); - lReport.Add(''); - lReport.Add('Shapiro-Wilkes W = ' + WEdit.Text); - lReport.Add('Shapiro-Wilkes Prob. = ' + ProbEdit.Text); - lReport.Add(''); - lReport.Add('Skew = ' + SkewEdit.Text); - lReport.Add('Kurtosis = ' + KurtosisEdit.Text); - lReport.Add('Lilliefors Test Statistic = ' + StatEdit.Text); - lReport.Add('Conclusion: ' + ConclusionEdit.Text); - - DisplayReport(lReport); - finally - lReport.Free; - end; -end; - -procedure TNormalityFrm.ResetBtnClick(Sender: TObject); -var - i: integer; -begin - TestVarEdit.Text := ''; - WEdit.Text := ''; - ProbEdit.Text := ''; - ConclusionEdit.Text := ''; - SkewEdit.Text := ''; - KurtosisEdit.Text := ''; - StatEdit.Text := ''; - VarList.Items.Clear; - for i := 1 to NoVariables do - VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); - UpdateBtnStates; -end; - procedure TNormalityFrm.FormActivate(Sender: TObject); var w: Integer; @@ -118,123 +81,285 @@ begin if FAutoSized then exit; - w := MaxValue([ResetBtn.Width, PrintBtn.Width, ApplyBtn.Width, CloseBtn.Width]); + w := MaxValue([ResetBtn.Width, ComputeBtn.Width, CloseBtn.Width]); ResetBtn.Constraints.MinWidth := w; - PrintBtn.Constraints.MinWidth := w; - ApplyBtn.Constraints.MinWidth := w; + ComputeBtn.Constraints.MinWidth := w; CloseBtn.Constraints.MinWidth := w; + ParamsPanel.Constraints.MinWidth := Max( + 3*w + 2*CloseBtn.BorderSpacing.Left, + Max(Label1.Width, Label2.Width) + VarInBtn.Width + 2*VarList.BorderSpacing.Right); + ParamsPanel.Constraints.MinHeight := VarOutBtn.Top + VarOutBtn.Height + + VarOutBtn.BorderSpacing.Bottom + Bevel1.Height + Panel1.Height + + Panel1.BorderSpacing.Top; + + Constraints.MinWidth := ParamsPanel.Constraints.MinWidth + 300; + Constraints.MinHeight := ParamsPanel.Constraints.MinHeight + 2*ParamsPanel.BorderSpacing.Top; + + Position := poDesigned; FAutoSized := True; end; procedure TNormalityFrm.FormCreate(Sender: TObject); +const + MARKS: array[0..23] of double = (0.001, 0.002, 0.005, 0.01, 0.02, 0.03, 0.05, 0.07, + 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.93, 0.95, 0.98, 0.99, + 0.995, 0.998, 0.999); +var + T : TChartAxisTransformations; + InvNormDistTransform: TAxisTransform; + L: TListChartSource; + i: Integer; begin Assert(OS3MainFrm <> nil); + + InitForm(self); + + FReportFrame := TReportFrame.Create(self); + 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; + + T := TChartAxisTransformations.Create(FChartFrame); + InvNormDistTransform := TCumulNormDistrAxisTransform.Create(T); + InvNormDistTransform.Transformations := T; + FChartFrame.Chart.LeftAxis.Transformations := T; + + FChartFrame.Chart.LeftAxis.Intervals.Tolerance := 10; + FChartFrame.Chart.LeftAxis.Intervals.Count := 30; + FChartFrame.Chart.LeftAxis.Intervals.Options := FChartFrame.Chart.leftAxis.Intervals.options + [aipUseCount]; //aipGraphCoords]; + FChartFrame.Chart.LeftAxis.Marks.OverlapPolicy := opHideNeighbour; + { + L := TListChartSource.Create(FChartFrame.Chart); + for i := 0 to High(MARKS) do + L.Add(MARKS[i], MARKS[i]); + FChartFrame.Chart.LeftAxis.Marks.Source := L; + FChartFrame.Chart.LeftAxis.Marks.Style := smsLabel; + } + Reset; end; -procedure TNormalityFrm.FormShow(Sender: TObject); -begin - ResetBtnClick(nil); -end; -procedure TNormalityFrm.ApplyBtnClick(Sender: TObject); +procedure TNormalityFrm.Calc_Lilliefors(const AData: DblDyneVec; + out ASkew, AKurtosis, AStat: Double; out AConclusion: String); var - w: Double = 0.0; - pw: Double = 0.0; - temp: double; - skew, kurtosis : double; - mean, variance, stddev, deviation, devsqr, M2, M3, M4 : double; - i, j, n, n1, n2, ier : integer; - selcol : integer; - data, a, z, x : DblDyneVec; - freq : IntDyneVec; - fval, jval, DP : DblDyneVec; - F1, DPP, D, A0, C1, D15, D10, D05, D025, t2 : double; - init : boolean; - msg : string; - - procedure Cleanup; - begin - DP := nil; - jval := nil; - fval := nil; - data := nil; - a := nil; - freq := nil; - z := nil; - x := nil; - end; - + i, j, n, n1: Integer; + freq: IntDyneVec = nil; + x: DblDyneVec = nil; + z: DblDyneVec = nil; + fval: DblDyneVec = nil; + jval: DblDyneVec = nil; + DP: DblDyneVec = nil; + mean, variance, stddev: Double; + deviation, devSqr: Double; + M2, M3, M4, F1, DPP, t2: Double; + A0, C1, D025, D05, D10, D15: Double; begin - selcol := 0; - for i := 1 to NoVariables do - if OS3MainFrm.DataGrid.Cells[i,0] = TestVarEdit.Text then - begin - selcol := i; - break; - end; - if selCol = 0 then - begin - MessageDlg('No variable selected.', mtError, [mbOK], 0); - exit; - end; + // Count of data values + n := Length(AData) - 1; // -1 due to ignored element at index 0 - init := false; - n := 0; - - // place values into the data array - SetLength(data, NoCases+1); // arrays start at 1 - SetLength(a, NoCases+1); - SetLength(freq, NoCases+1); - SetLength(z, NoCases+1); - SetLength(x, NoCases+1); - SetLength(fval, NoCases+1); - SetLength(jval, NoCases+1); - SetLength(DP, NoCases+1); - for i := 1 to NoCases do - begin - if not ValidValue(i,selcol) then - continue; - n := n + 1; - data[n] := StrToFloat(OS3MainFrm.DataGrid.Cells[selcol,i]); - end; - n1 := n; - n2 := n div 2; - - // sort into ascending order - for i := 1 to n - 1 do - begin - for j := i + 1 to n do - begin - if data[i] > data[j] then - begin - temp := data[i]; - data[i] := data[j]; - data[j] := temp; - end; - end; - end; - - // call Shapiro-Wilks function - swilk(init, data, n, n1, n2, a, w, pw, ier); - if ier <> 0 then - begin - msg := 'Error encountered = ' + IntToStr(ier); - MessageDlg(msg, mtError, [mbOK], 0); - Cleanup; - exit; - end; - WEdit.Text := Format('%.4f', [w]); - ProbEdit.Text := Format('%.4f', [pw]); + SetLength(freq, n+1); // +1 to make the array 1-based + SetLength(x, n+1); + SetLength(z, n+1); + SetLength(fval, n+1); + Setlength(jval, n+1); + SetLength(DP, n+1); // Now do Lilliefors // Get unique scores and their frequencies n1 := 1; i := 1; freq[1] := 1; + x[1] := AData[1]; + repeat + for j := i + 1 to n do + if AData[j] = x[n1] then freq[n1] := freq[n1] + 1; + i := i + freq[n1]; + if i <= n then + begin + n1 := n1 + 1; + x[n1] := AData[i]; + freq[n1] := 1; + end; + until i > n; + + // Now get skew and kurtosis of scores + mean := 0.0; + variance := 0.0; + for i := 1 to n do + begin + mean := mean + AData[i]; + variance := variance + (AData[i] * AData[i]); + end; + variance := variance - sqr(mean) / n; + variance := variance / (n - 1); + stddev := sqrt(variance); + mean := mean / n; + + // Obtain skew, kurtosis and z scores + M2 := 0.0; + M3 := 0.0; + M4 := 0.0; + for i := 1 to n do + begin + deviation := AData[i] - mean; + devsqr := deviation * deviation; + M2 := M2 + devsqr; + M3 := M3 + (deviation * devsqr); + M4 := M4 + (devsqr * devsqr); + z[i] := (AData[i] - mean) / stddev; + end; + for i := 1 to n1 do + x[i] := (x[i] - mean) / stddev; + ASkew := (n * M3) / ((n - 1) * (n - 2) * stddev * variance); + AKurtosis := (n * (n + 1) * M4) - (3 * M2 * M2 * (n - 1)); + AKurtosis := AKurtosis /( (n - 1) * (n - 2) * (n - 3) * sqr(variance) ); + + // Obtain the test statistic + for i := 1 to n1 do + begin + F1 := Norm(x[i]); + if x[i] >= 0 then + fval[i] := 1.0 - (F1 / 2.0) + else + fval[i] := F1 / 2.0; + end; + + // Cumulative proportions + jval[1] := freq[1] / n; + for i := 2 to n1 do jval[i] := jval[i-1] + freq[i] / n; + for i := 1 to n1 do DP[i] := abs(jval[i] - fval[i]); + + // Sort DP + for i := 1 to n1-1 do + for j := i+1 to n1 do + if DP[j] < DP[i] then + Exchange(DP[i], DP[j]); + + DPP := DP[n1]; + AStat := DPP; + //StatEdit.Text := Format('%.3f', [D]); + A0 := sqrt(n); + C1 := A0 - 0.01 + (0.85 / A0); + D15 := 0.775 / C1; + D10 := 0.819 / C1; + D05 := 0.895 / C1; + D025 := 0.995 / C1; + t2 := AStat; + if t2 > D025 then + AConclusion := 'Strong evidence against normality.'; + if ((t2 <= D025) and (t2 > D05)) then + AConclusion := 'Sufficient evidence against normality.'; + if ((t2 <= D05) and (t2 > D10)) then + AConclusion := 'Suggestive evidence against normality.'; + if ((t2 <= D10) and (t2 > D15)) then + AConclusion := 'Little evidence against normality.'; + if (t2 <= D15) then + AConclusion := 'No evidence against normality.'; +end; + + +{ Call Shapiro-Wilks function } +function TNormalityFrm.Calc_ShapiroWilks(const AData: DblDyneVec; + out W, Prob: Double): boolean; +var + init: Boolean = false; + n, n1, n2: Integer; + a: DblDyneVec = nil; + ier: Integer; // error code +begin + init := false; + n := Length(AData) - 1; // -1 because of unused element at index 0 + n1 := n; + n2 := n div 2; + SetLength(a, n + 1); // again: 1-based vector! + swilk(init, AData, n, n1, n2, a, W, Prob, ier); + Result := (ier = 0); + case ier of + 0: ; + 1: ErrorMsg('Error encountered: N < 3'); + 2: ErrorMsg('Error encountered: N > 5000'); + 3: ErrorMsg('Error encountered: N2 < N/2'); + 4: ErrorMsg('Error encountered: N1 > N or ((N1 < N) and (N < 20))'); + 5: ErrorMsg('Error encountered: The proportion censored (N - N1) / N > 0.8'); + 6: ErrorMsg('Error encountered: Data have zero range.'); + 7: ErrorMsg('Error encounterde: X values are not sorted in ascending order.'); + else ErrorMsg('Error no. ' + IntToStr(ier) + ' encountered'); + end; +end; + + +procedure TNormalityFrm.ComputeBtnClick(Sender: TObject); +var + w: Double = 0.0; + pw: Double = 0.0; + skew, kurtosis: double; + mean, variance, stddev, deviation, devsqr, M2, M3, M4: double; + i, j, n, n1: integer; + data: DblDyneVec = nil; + //a + z, x: DblDyneVec; + freq: IntDyneVec; + fval, jval, DP: DblDyneVec; + F1, DPP, D, A0, C1, D15, D10, D05, D025, t2: double; + init : boolean; + conclusion: String; + lReport: TStrings; +begin + data := PrepareData(TestVarEdit.Text); + if data = nil then + exit; + + n := Length(data) - 1; // Subtract 1 because of unused element at index 0 + (* +// SetLength(a, n+1); // +1 because all arrays are considered to begin at 1 here + SetLength(freq, n+1); + SetLength(z, n+1); + SetLength(x, n+1); + SetLength(fval, n+1); + SetLength(jval, n+1); + SetLength(DP, n+1); + *) + // Sort into ascending order + for i := 1 to n - 1 do + for j := i + 1 to n do + if data[i] > data[j] then + Exchange(data[i], data[j]); + + if not Calc_ShapiroWilks(data, w, pw) then + exit; + (* + // Call Shapiro-Wilks function + init := false; + swilk(init, data, n, n1, n2, a, w, pw, ier); + if ier <> 0 then + begin + ErrorMsg('Error encountered: ' + IntToStr(ier)); + Cleanup; + exit; + end; + *) + + { + WEdit.Text := Format('%.4f', [w]); + ProbEdit.Text := Format('%.4f', [pw]); + } + + // Now do Lilliefors + Calc_Lilliefors(data, skew, kurtosis, D, conclusion); + + (* + // Get unique scores and their frequencies + n1 := 1; + i := 1; + freq[1] := 1; x[1] := data[1]; repeat -//again: for j := i + 1 to n do begin if data[j] = x[n1] then freq[n1] := freq[n1] + 1; @@ -245,11 +370,10 @@ begin n1 := n1 + 1; x[n1] := data[i]; freq[n1] := 1; - //goto again; end; until i > n; - // now get skew and kurtosis of scores + // Now get skew and kurtosis of scores mean := 0.0; variance := 0.0; for i := 1 to n do @@ -262,7 +386,7 @@ begin stddev := sqrt(variance); mean := mean / n; - // obtain skew, kurtosis and z scores + // Obtain skew, kurtosis and z scores M2 := 0.0; M3 := 0.0; M4 := 0.0; @@ -279,10 +403,10 @@ begin skew := (n * M3) / ((n - 1) * (n - 2) * stddev * variance); kurtosis := (n * (n + 1) * M4) - (3 * M2 * M2 * (n - 1)); kurtosis := kurtosis /( (n - 1) * (n - 2) * (n - 3) * (variance * variance) ); - SkewEdit.Text := Format('%.3f', [skew]); - KurtosisEdit.Text := Format('%.3f', [kurtosis]); + //SkewEdit.Text := Format('%.3f', [skew]); + //KurtosisEdit.Text := Format('%.3f', [kurtosis]); - // obtain the test statistic + // Obtain the test statistic for i := 1 to n1 do begin F1 := Norm(x[i]); @@ -292,27 +416,20 @@ begin fval[i] := F1 / 2.0; end; - // cumulative proportions + // Cumulative proportions jval[1] := freq[1] / n; for i := 2 to n1 do jval[i] := jval[i-1] + freq[i] / n; for i := 1 to n1 do DP[i] := abs(jval[i] - fval[i]); - // sort DP + // Sort DP for i := 1 to n1-1 do - begin for j := i+1 to n1 do - begin if DP[j] < DP[i] then - begin - temp := DP[i]; - DP[i] := DP[j]; - DP[j] := temp; - end; - end; - end; + Exchange(DP[i], DP[j]); + DPP := DP[n1]; D := DPP; - StatEdit.Text := Format('%.3f', [D]); + //StatEdit.Text := Format('%.3f', [D]); A0 := sqrt(n); C1 := A0 - 0.01 + (0.85 / A0); D15 := 0.775 / C1; @@ -320,15 +437,142 @@ begin D05 := 0.895 / C1; D025 := 0.995 / C1; t2 := D; - if t2 > D025 then ConclusionEdit.Text := 'Strong evidence against normality.'; - if ((t2 <= D025) and (t2 > D05)) then ConclusionEdit.Text := 'Sufficient evidence against normality.'; - if ((t2 <= D05) and (t2 > D10)) then ConclusionEdit.Text := 'Suggestive evidence against normality.'; - if ((t2 <= D10) and (t2 > D15)) then ConclusionEdit.Text := 'Little evidence against normality.'; - if (t2 <= D15) then ConclusionEdit.Text := 'No evidence against normality.'; + if t2 > D025 then conclusion := 'Strong evidence against normality.'; + if ((t2 <= D025) and (t2 > D05)) then conclusion := 'Sufficient evidence against normality.'; + if ((t2 <= D05) and (t2 > D10)) then conclusion := 'Suggestive evidence against normality.'; + if ((t2 <= D10) and (t2 > D15)) then conclusion := 'Little evidence against normality.'; + if (t2 <= D15) then conclusion := 'No evidence against normality.'; + *) - Cleanup; + lReport := TStringList.Create; + try + lReport.Add('NORMALITY TESTS FOR '+ TestVarEdit.Text); + lReport.Add(''); + lReport.Add('Shapiro-Wilkes Test Results'); + lReport.Add(' W: %8.5f', [w]); // WEdit.Tex + lReport.Add(' Probability: %8.5f', [pw]); // ProbEdit.Text + lReport.Add(''); + lReport.Add('Lilliefors Test Results'); + lReport.Add(' Skew: %8.5f', [skew]); // SkewEdit.Text + lReport.Add(' Kurtosis: %8.5f', [kurtosis]); // KurtosisEdit.Text + lReport.Add(' Test Statistic: %8.5f', [D]); // StatEdit.Text + lReport.Add(' Conclusion: %s', [conclusion]); + + FReportFrame.DisplayReport(lReport); + finally + lReport.Free; + end; + + PlotData(data); end; +function TNormalityFrm.Norm(z : double) : double; +var + p: double; +begin + z := abs(z); + p := 1.0 + z * (0.04986735 + z * (0.02114101 + z * (0.00327763 + + z * (0.0000380036 + z * (0.0000488906 + z * 0.000005383))))); + p := p * p; + p := p * p; + p := p * p; + Result := 1.0 / (p * p); +end; + +procedure TNormalityFrm.PlotData(AData: DblDyneVec); +var + i, n: Integer; + ser: TChartSeries; +begin + n := Length(AData) - 1; // take care of unused value at index 0 + ser := FChartFrame.PlotXY(ptSymbols, nil, nil, nil, nil, '', clBlack); + ser.AxisIndexX := 1; + ser.AxisIndexY := 0; + + // Add data manually + for i := 1 to n do + ser.AddXY(AData[i], i / (N+1)); + + FChartFrame.Chart.Legend.Visible := false; + FChartFrame.SetTitle('Probability Plot of ' + TestVarEdit.Text); + FChartFrame.SetXTitle(TestVaredit.Text); + FChartFrame.SetYTitle('Cumulative probability'); +end; + + +{ Extracts the data values from the data grid and stores them in a float array. + Note that, because the Shapiro-Wilks function has been implemented for 1-based + arrays the data array is considered to be 1-based although the 0-index element + is present as well. } +function TNormalityFrm.PrepareData(const VarName: String): DblDyneVec; +var + selCol: Integer; + i, n: Integer; +begin + // Find data column in the grid + selcol := 0; + for i := 1 to NoVariables do + if OS3MainFrm.DataGrid.Cells[i, 0] = VarName then + begin + selcol := i; + break; + end; + if selCol = 0 then + begin + Result := nil; + MessageDlg('No variable selected.', mtError, [mbOK], 0); + exit; + end; + + // Place values into the data array + SetLength(Result, NoCases+1); + + n := 0; + for i := 1 to NoCases do + begin + if not ValidValue(i, selcol) then + continue; + inc(n); + if not TryStrToFloat(OS3MainFrm.DataGrid.Cells[selcol, i], Result[n]) then + begin + Result := nil; + ErrorMsg('Non-numeric value encountered.'); + exit; + end; + end; + + SetLength(Result, n + 1); // Take care of unused element at index 0 +end; + + +procedure TNormalityFrm.Reset; +var + i: integer; +begin + TestVarEdit.Text := ''; + VarList.Items.Clear; + for i := 1 to NoVariables do + VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); + UpdateBtnStates; +end; + + +procedure TNormalityFrm.ResetBtnClick(Sender: TObject); +begin + Reset; +end; + + + +procedure TNormalityFrm.UpdateBtnStates; +begin + VarInBtn.Enabled := (VarList.ItemIndex > -1) and (TestVarEdit.Text = ''); + VarOutBtn.Enabled := (TestVarEdit.Text <> ''); + FReportFrame.UpdateBtnStates; + FChartFrame.UpdateBtnStates; +end; + + procedure TNormalityFrm.VarInBtnClick(Sender: TObject); var i: integer; @@ -336,17 +580,25 @@ begin i := VarList.ItemIndex; if (i > -1) and (TestVarEdit.Text = '') then begin - TestVarEdit.Text := VarList.Items.Strings[i]; + TestVarEdit.Text := VarList.Items[i]; VarList.Items.Delete(i); end; UpdateBtnStates; end; + +procedure TNormalityFrm.VarListDblClick(Sender: TObject); +begin + VarInBtnClick(nil); +end; + + procedure TNormalityFrm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; + procedure TNormalityFrm.VarOutBtnClick(Sender: TObject); begin if TestVarEdit.Text <> '' then @@ -357,27 +609,6 @@ begin UpdateBtnStates; end; -function TNormalityFrm.Norm(z : double) : double; -var - p : double; -begin - z := abs(z); - p := 1.0 + z * (0.04986735 + z * (0.02114101 + z * (0.00327763 + - z * (0.0000380036 + z * (0.0000488906 + z * 0.000005383))))); - p := p * p; - p := p * p; - p := p * p; - Result := 1.0 / (p * p); -end; - -procedure TNormalityFrm.UpdateBtnStates; -begin - VarInBtn.Enabled := (VarList.ItemIndex > -1) and (TestVarEdit.Text = ''); - VarOutBtn.Enabled := (TestVarEdit.Text <> ''); -end; - -initialization - {$I normalityunit.lrs} end. diff --git a/applications/lazstats/source/units/utils.pas b/applications/lazstats/source/units/utils.pas index 05fc756cb..0f0fa707e 100644 --- a/applications/lazstats/source/units/utils.pas +++ b/applications/lazstats/source/units/utils.pas @@ -25,7 +25,7 @@ procedure Exchange(var a, b: Double); overload; procedure Exchange(var a, b: Integer); overload; procedure Exchange(var a, b: String); overload; -procedure SortOnX(X, Y: DblDyneVec); +procedure SortOnX(X: DblDyneVec; Y: DblDyneVec = nil; Z: DblDyneVec = nil); procedure SortOnX(X: DblDyneVec; Y: DblDyneMat); function IndexOfString(L: StrDyneVec; s: String): Integer; @@ -131,13 +131,15 @@ begin b := tmp; end; -procedure SortOnX(X, Y: DblDyneVec); +procedure SortOnX(X: DblDyneVec; Y: DblDyneVec = nil; Z: DblDyneVec = nil); var i, j, N: Integer; begin N := Length(X); - if N <> Length(Y) then - raise Exception.Create('[SortOnX] Both arrays must have the same length'); + if (Y <> nil) and (N <> Length(Y)) then + raise Exception.Create('[SortOnX] Arrays must have the same length.'); + if (Z <> nil) and (N <> Length(Z)) then + raise Exception.Create('[SortOnX] Arrays must have the same length.'); for i := 0 to N - 2 do begin @@ -146,7 +148,10 @@ begin if X[i] > X[j] then //swap begin Exchange(X[i], X[j]); - Exchange(Y[i], Y[j]); + if Y <> nil then + Exchange(Y[i], Y[j]); + if Z <> nil then + Exchange(Z[i], Z[j]); end; end; end;