diff --git a/applications/lazstats/source/LazStats.lpi b/applications/lazstats/source/LazStats.lpi index 00d42de95..35f8652dc 100644 --- a/applications/lazstats/source/LazStats.lpi +++ b/applications/lazstats/source/LazStats.lpi @@ -809,7 +809,7 @@ - + diff --git a/applications/lazstats/source/forms/mainunit.pas b/applications/lazstats/source/forms/mainunit.pas index 5c546c84b..213096e04 100644 --- a/applications/lazstats/source/forms/mainunit.pas +++ b/applications/lazstats/source/forms/mainunit.pas @@ -1373,9 +1373,9 @@ end; // Menu "Simulations" > "Bivariate Scatter Plot" procedure TOS3MainFrm.mnuSimBivarScatterPlotClick(Sender: TObject); begin - if CorSimFrm = nil then - Application.CreateForm(TCorSimFrm, CorSimFrm); - CorSimFrm.ShowModal; + if CorSimForm = nil then + Application.CreateForm(TCorSimForm, CorSimForm); + CorSimForm.Show; end; // Menu "Simulations" > "Chisquare Probability" diff --git a/applications/lazstats/source/forms/simulations/corsimunit.lfm b/applications/lazstats/source/forms/simulations/corsimunit.lfm index 3c2a4439f..da6f2fb3d 100644 --- a/applications/lazstats/source/forms/simulations/corsimunit.lfm +++ b/applications/lazstats/source/forms/simulations/corsimunit.lfm @@ -1,4 +1,4 @@ -object CorSimFrm: TCorSimFrm +inherited CorSimForm: TCorSimForm Left = 542 Height = 447 Top = 126 @@ -8,223 +8,263 @@ object CorSimFrm: TCorSimFrm Caption = 'Correlation Simulation' ClientHeight = 447 ClientWidth = 857 - OnShow = FormShow - Position = poMainFormCenter - LCLVersion = '2.1.0.0' - object Image1: TImage - Left = 8 - Height = 389 - Top = 8 - Width = 841 - Align = alClient - BorderSpacing.Left = 8 - BorderSpacing.Top = 8 - BorderSpacing.Right = 8 - end - object Panel1: TPanel - AnchorSideTop.Side = asrCenter - Left = 8 - Height = 26 - Top = 413 - Width = 841 - Align = alBottom - AutoSize = True - BorderSpacing.Around = 8 - BevelOuter = bvNone - ClientHeight = 26 - ClientWidth = 841 - TabOrder = 0 - object Label1: TLabel - AnchorSideLeft.Control = Panel1 - AnchorSideTop.Control = MeanX + inherited ParamsPanel: TPanel + Height = 431 + Width = 267 + ClientHeight = 431 + ClientWidth = 267 + inherited CloseBtn: TButton + Left = 212 + Top = 406 + TabOrder = 10 + end + inherited ComputeBtn: TButton + Left = 128 + Top = 406 + TabOrder = 9 + end + inherited ResetBtn: TButton + Left = 66 + Top = 406 + TabOrder = 8 + end + inherited HelpBtn: TButton + Left = 7 + Top = 406 + TabOrder = 7 + Visible = False + end + inherited ButtonBevel: TBevel + Top = 390 + Width = 267 + end + object Label1: TLabel[5] + AnchorSideLeft.Control = ParamsPanel + AnchorSideTop.Control = MeanXEdit AnchorSideTop.Side = asrCenter Left = 0 Height = 15 - Top = 6 + Top = 4 Width = 43 Caption = 'Mean X:' ParentColor = False end - object Label2: TLabel - AnchorSideLeft.Control = MeanX + object MeanXEdit: TEdit[6] + AnchorSideLeft.Control = Label1 AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Label1 - Left = 108 + AnchorSideTop.Control = ParamsPanel + Left = 51 + Height = 23 + Top = 0 + Width = 60 + Alignment = taRightJustify + BorderSpacing.Left = 8 + TabOrder = 0 + Text = 'MeanXEdit' + end + object Label2: TLabel[7] + AnchorSideLeft.Control = ParamsPanel + AnchorSideTop.Control = MeanYEdit + AnchorSideTop.Side = asrCenter + Left = 0 Height = 15 - Top = 6 + Top = 35 Width = 43 - BorderSpacing.Left = 16 Caption = 'Mean Y:' ParentColor = False end - object Label3: TLabel - AnchorSideLeft.Control = MeanY + object MeanYEdit: TEdit[8] + AnchorSideLeft.Control = MeanXEdit + AnchorSideTop.Control = MeanXEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = MeanXEdit + AnchorSideRight.Side = asrBottom + Left = 51 + Height = 23 + Top = 31 + Width = 60 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 8 + TabOrder = 2 + Text = 'MeanYEdit' + end + object Label3: TLabel[9] + AnchorSideLeft.Control = MeanXEdit AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Label2 - Left = 221 + AnchorSideTop.Control = StdDevXEdit + AnchorSideTop.Side = asrCenter + Left = 135 Height = 15 - Top = 6 + Top = 4 Width = 50 - BorderSpacing.Left = 16 + BorderSpacing.Left = 24 Caption = 'Std.Dev.X' ParentColor = False end - object Label4: TLabel - AnchorSideLeft.Control = SDX + object StdDevXEdit: TEdit[10] + AnchorSideLeft.Control = Label3 AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Label3 - Left = 345 + AnchorSideTop.Control = ParamsPanel + Left = 193 + Height = 23 + Top = 0 + Width = 60 + Alignment = taRightJustify + BorderSpacing.Left = 8 + TabOrder = 1 + Text = 'StdDevXEdit' + end + object Label4: TLabel[11] + AnchorSideLeft.Control = Label3 + AnchorSideTop.Control = StdDevYEdit + AnchorSideTop.Side = asrCenter + Left = 135 Height = 15 - Top = 6 + Top = 35 Width = 50 - BorderSpacing.Left = 16 Caption = 'Std.Dev.Y' ParentColor = False end - object Label5: TLabel - AnchorSideLeft.Control = SDY + object StdDevYEdit: TEdit[12] + AnchorSideLeft.Control = StdDevXEdit + AnchorSideTop.Control = MeanYEdit + AnchorSideRight.Control = StdDevXEdit + AnchorSideRight.Side = asrBottom + Left = 193 + Height = 23 + Top = 31 + Width = 60 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + TabOrder = 3 + Text = 'StdDevYEdit' + end + object Label5: TLabel[13] AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Label4 - Left = 450 + AnchorSideTop.Control = CorrEdit + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = CorrEdit + Left = 51 Height = 15 - Top = 6 - Width = 36 + Top = 82 + Width = 76 + Anchors = [akTop, akRight] BorderSpacing.Left = 8 - Caption = 'Cor.XY' + BorderSpacing.Right = 8 + Caption = 'Correlation XY' ParentColor = False end - object Label6: TLabel - AnchorSideLeft.Control = Corr + object CorrEdit: TEdit[14] + AnchorSideLeft.Control = Label3 + AnchorSideTop.Control = MeanYEdit + AnchorSideTop.Side = asrBottom + Left = 135 + Height = 23 + Top = 78 + Width = 60 + Alignment = taRightJustify + BorderSpacing.Top = 24 + TabOrder = 4 + Text = 'CorrEdit' + end + object Label6: TLabel[15] AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Label5 + AnchorSideTop.Control = NumObsEdit AnchorSideTop.Side = asrCenter - Left = 554 + AnchorSideRight.Control = NumObsEdit + Left = 92 Height = 15 - Top = 6 + Top = 113 Width = 35 + Anchors = [akTop, akRight] BorderSpacing.Left = 16 + BorderSpacing.Right = 8 Caption = 'N Size:' ParentColor = False end - object MeanX: TEdit - AnchorSideLeft.Control = Label1 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrCenter - Left = 49 - Height = 23 - Top = 2 - Width = 43 - Alignment = taRightJustify - BorderSpacing.Left = 6 - OnKeyPress = MeanXKeyPress - TabOrder = 0 - Text = 'MeanX' - end - object MeanY: TEdit - AnchorSideLeft.Control = Label2 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = MeanX - Left = 159 - Height = 23 - Top = 2 - Width = 46 - Alignment = taRightJustify - BorderSpacing.Left = 8 - OnKeyPress = MeanYKeyPress - TabOrder = 1 - Text = 'MeanY' - end - object SDX: TEdit - AnchorSideLeft.Control = Label3 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = MeanY - Left = 279 - Height = 23 - Top = 2 - Width = 50 - Alignment = taRightJustify - BorderSpacing.Left = 8 - OnKeyPress = SDXKeyPress - TabOrder = 2 - Text = 'SDX' - end - object SDY: TEdit - AnchorSideLeft.Control = Label4 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = SDX - Left = 403 - Height = 23 - Top = 2 - Width = 39 - Alignment = taRightJustify - BorderSpacing.Left = 8 - OnKeyPress = SDYKeyPress - TabOrder = 3 - Text = 'SDY' - end - object Corr: TEdit - AnchorSideLeft.Control = Label5 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = SDY - Left = 494 - Height = 23 - Top = 2 - Width = 44 - Alignment = taRightJustify - BorderSpacing.Left = 8 - OnKeyPress = CorrKeyPress - TabOrder = 4 - Text = 'Corr' - end - object ComputeBtn: TButton - AnchorSideTop.Control = CloseBtn - AnchorSideRight.Control = CloseBtn - Left = 702 - Height = 26 - Top = 0 - Width = 65 - Anchors = [akTop, akRight] - BorderSpacing.Right = 8 - Caption = 'Compute' - OnClick = ComputeBtnClick - TabOrder = 6 - end - object CloseBtn: TButton - AnchorSideTop.Control = Panel1 - AnchorSideTop.Side = asrCenter - AnchorSideRight.Control = Panel1 + object NumObsEdit: TEdit[16] + AnchorSideLeft.Control = CorrEdit + AnchorSideTop.Control = CorrEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = CorrEdit AnchorSideRight.Side = asrBottom - Left = 775 - Height = 26 - Top = 0 - Width = 66 - Anchors = [akTop, akRight] - Caption = 'Close' - ModalResult = 11 - TabOrder = 7 - end - object Nobs: TEdit - AnchorSideLeft.Control = Label6 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = Corr - Left = 597 + Left = 135 Height = 23 - Top = 2 - Width = 40 + Top = 109 + Width = 60 Alignment = taRightJustify - BorderSpacing.Left = 8 - OnKeyPress = NobsKeyPress + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 8 TabOrder = 5 - Text = 'Nobs' + Text = 'NumObsEdit' + end + object PlotOptionsGroup: TGroupBox[17] + AnchorSideLeft.Control = ParamsPanel + AnchorSideTop.Control = NumObsEdit + AnchorSideTop.Side = asrBottom + Left = 0 + Height = 90 + Top = 148 + Width = 178 + AutoSize = True + BorderSpacing.Top = 16 + Caption = 'Plot options' + ClientHeight = 70 + ClientWidth = 174 + TabOrder = 6 + object NumBinsEdit: TSpinEdit + AnchorSideLeft.Control = Label7 + AnchorSideLeft.Side = asrBottom + AnchorSideTop.Control = PlotOptionsGroup + Left = 107 + Height = 23 + Top = 8 + Width = 55 + BorderSpacing.Left = 8 + BorderSpacing.Top = 8 + BorderSpacing.Right = 12 + MinValue = 2 + OnChange = NumBinsEditChange + TabOrder = 0 + Value = 2 + end + object Label7: TLabel + AnchorSideLeft.Control = PlotOptionsGroup + AnchorSideTop.Control = NumBinsEdit + AnchorSideTop.Side = asrCenter + Left = 16 + Height = 15 + Top = 12 + Width = 83 + BorderSpacing.Left = 16 + Caption = 'Number of bins' + ParentColor = False + end + object BinCountChk: TCheckBox + AnchorSideLeft.Control = Label7 + AnchorSideTop.Control = NumBinsEdit + AnchorSideTop.Side = asrBottom + Left = 16 + Height = 19 + Top = 39 + Width = 113 + BorderSpacing.Top = 8 + BorderSpacing.Bottom = 12 + Caption = 'Show frequencies' + OnChange = BinCountChkChange + TabOrder = 1 + end end end - object Bevel1: TBevel - Left = 0 - Height = 8 - Top = 397 - Width = 857 - Align = alBottom - Shape = bsBottomLine + inherited ParamsSplitter: TSplitter + Left = 279 + Height = 447 + end + inherited PageControl: TPageControl + Left = 288 + Height = 431 + Width = 561 + TabOrder = 1 end end diff --git a/applications/lazstats/source/forms/simulations/corsimunit.pas b/applications/lazstats/source/forms/simulations/corsimunit.pas index 35bb3bead..7c451b730 100644 --- a/applications/lazstats/source/forms/simulations/corsimunit.pas +++ b/applications/lazstats/source/forms/simulations/corsimunit.pas @@ -5,462 +5,491 @@ unit CorSimUnit; interface uses - Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, - ExtCtrls, StdCtrls, Math, - Globals, OutputUnit; + Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, + ExtCtrls, StdCtrls, ComCtrls, Spin, + TAGraph, TACustomSeries, + Globals, BasicStatsReportAndChartFormUnit; type - { TCorSimFrm } + TCorSimResult = record + a, b: Double; + xMean, xStdDev: Double; + yMean, yStdDev: Double; + CorrXY: Double; + end; - TCorSimFrm = class(TForm) - Bevel1: TBevel; - Nobs: TEdit; - Image1: TImage; + { TCorSimForm } + + TCorSimForm = class(TBasicStatsReportAndChartForm) + BinCountChk: TCheckBox; + Label7: TLabel; + PlotOptionsGroup: TGroupBox; + NumObsEdit: TEdit; Label6: TLabel; - CloseBtn: TButton; - ComputeBtn: TButton; - Corr: TEdit; + CorrEdit: TEdit; Label5: TLabel; - SDY: TEdit; + NumBinsEdit: TSpinEdit; + StdDevYEdit: TEdit; Label4: TLabel; - SDX: TEdit; + StdDevXEdit: TEdit; Label3: TLabel; - MeanY: TEdit; + MeanYEdit: TEdit; Label2: TLabel; - MeanX: TEdit; + MeanXEdit: TEdit; Label1: TLabel; - Panel1: TPanel; - procedure ComputeBtnClick(Sender: TObject); - procedure CorrKeyPress(Sender: TObject; var Key: char); - procedure FormShow(Sender: TObject); - procedure MeanXKeyPress(Sender: TObject; var Key: char); - procedure MeanYKeyPress(Sender: TObject; var Key: char); - procedure NobsKeyPress(Sender: TObject; var Key: char); - procedure SDXKeyPress(Sender: TObject; var Key: char); - procedure SDYKeyPress(Sender: TObject; var Key: char); + procedure BinCountChkChange(Sender: TObject); + procedure NumBinsEditChange(Sender: TObject); private - { private declarations } - xmean, ymean, xsd, ysd, corxy, corsqr, yvariance, predvar : double; - errvariance, stderror, b, constant, newxmean, newymean : double; - newxsd, newysd, newcorr, randomerror, newb, newconstant : double; - x, y: DblDyneVec; - freqx, freqy: IntDyneVec; - N: integer; + XHistogramChart: TChart; + YHistogramChart: TChart; + XHistogramSeries: TChartSeries; + YHistogramSeries: TChartSeries; + xValues, yValues: DblDyneVec; + simResult: TCorSimResult; + + procedure ChartAfterPaint(Sender: TChart); + procedure ChartFrameResize(Sender: TObject); + + procedure GenerateData(ACount: Integer; + const XMean, YMean, XStdDev, YStdDev, CorrXY: Double; out A, B: Double); + procedure Plot; - function Validate(out AMsg: String; out AControl: TWinControl): Boolean; + + procedure WriteReport(A, B, XMean, YMean, XStdDev, YStdDev, CorrXY: Double); + + 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 - CorSimFrm: TCorSimFrm; + CorSimForm: TCorSimForm; + implementation -{ TCorSimFrm } +{$R *.lfm} -procedure TCorSimFrm.MeanXKeyPress(Sender: TObject; var Key: char); -begin - if Key = #13 then MeanY.SetFocus; -end; +uses + Math, + TAGeometry, TAChartUtils, TALegend, TASeries, + MatrixUnit, ChartFrameUnit; -procedure TCorSimFrm.CorrKeyPress(Sender: TObject; var Key: char); -begin - if Key = #13 then Nobs.SetFocus; -end; -procedure TCorSimFrm.ComputeBtnClick(Sender: TObject); -var - i: integer; - msg: String; - C: TWinControl; - lReport: TStrings; +{ TCorSimForm } + +constructor TCorSimForm.Create(AOwner: TComponent); begin - if not Validate(msg, C) then begin - C.SetFocus; - MessageDlg(msg, mtError, [mbOk], 0); - exit; + inherited; + + FChartFrame.OnResize := @ChartFrameResize; + FChartFrame.Chart.OnAfterPaint := @ChartAfterPaint; + FChartFrame.Chart.Foot.Alignment := taLeftJustify; + + XHistogramChart := TChart.Create(FChartFrame); + with XHistogramChart do + begin + Parent := FChartFrame; + AnchorSideTop.Control := FChartFrame.ChartToolBar; + AnchorSideTop.Side := asrBottom; + AnchorSideLeft.Control := FChartFrame.Chart; + AnchorSideRight.Control := FChartFrame.Chart; + AnchorSideRight.Side := asrBottom; + Anchors := [akLeft, akTop, akRight]; + Height := 200; + Color := FChartFrame.Chart.Color; + BackColor := FChartFrame.Chart.Color; + Margins.Bottom := 0; + Frame.Visible := false; + LeftAxis.Visible := false; + BottomAxis.Grid.Visible := false; + BottomAxis.Marks.Visible := false; + BottomAxis.AxisPen.Visible := true; end; - N := StrToInt(NObs.Text); - xmean := StrToFloat(MeanX.Text); - ymean := StrToFloat(MeanY.Text); - xsd := StrToFloat(SDX.Text); - ysd := StrToFloat(SDY.Text); - corxy := StrToFloat(Corr.Text); + XHistogramSeries := TBarSeries.Create(FChartFrame); + with TBarSeries(XHistogramSeries) do + begin + BarBrush.Color := DATA_COLORS[1]; + MarkPositions := lmpPositive; + end; + XHistogramChart.AddSeries(XHistogramSeries); + YHistogramChart := TChart.Create(FChartFrame); + with YHistogramChart do + begin + Parent := FChartFrame; + AnchorSideTop.Control := FChartFrame.Chart; + AnchorSideRight.Control := FChartFrame; + AnchorSideRight.Side := asrBottom; + AnchorSideBottom.Control := FChartFrame.Chart; + AnchorSideBottom.Side := asrBottom; + Anchors := [akTop, akRight, akBottom]; + Width := 200; + Color := FChartFrame.Chart.Color; + BackColor := FChartFrame.Chart.Color; + Margins.Left := 0; + Frame.Visible := false; + BottomAxis.Visible := false; + LeftAxis.Grid.Visible := false; + LeftAxis.Marks.Visible := false; + LeftAxis.AxisPen.Visible := true; + end; + + YHistogramSeries := TBarSeries.Create(FChartFrame); + with TBarSeries(YHistogramSeries) do + begin + AxisIndexX := 0; + AxisIndexY := 1; + BarBrush.Color := DATA_COLORS[2]; + MarkPositions := lmpPositive; + end; + YHistogramChart.AddSeries(YHistogramSeries); + + with FChartFrame.Chart do + begin + Align := alNone; + AnchorSideLeft.Control := FChartFrame; + AnchorSideBottom.Control := FChartFrame; + AnchorSideBottom.Side := asrBottom; + AnchorSideTop.Control := XHistogramChart; + AnchorSideTop.Side := asrBottom; + AnchorSideRight.Control := YHistogramChart; + Anchors := [akLeft, akTop, akRight, akBottom]; + end; + + PageControl.ActivePageIndex := 0; +end; + + +procedure TCorSimForm.AdjustConstraints; +begin + inherited; + + ParamsPanel.Constraints.MinWidth := Max( + 3*CloseBtn.Width + 2*CloseBtn.BorderSpacing.Left, + StdDevXEdit.Left + StdDevXEdit.Width + ); + ParamsPanel.Constraints.MinHeight := + PlotOptionsGroup.Top + PlotOptionsGroup.Height + + ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height; +end; + + +procedure TCorSimForm.BinCountChkChange(Sender: TObject); +begin + if BinCountChk.Checked then + begin + XHistogramSeries.Marks.Style := smsValue; + YHistogramSeries.Marks.Style := smsValue; + end else + begin + XHistogramSeries.Marks.Style := smsNone; + YHistogramSeries.Marks.Style := smsNone; + end; +end; + + +procedure TCorSimForm.ChartAfterPaint(Sender: TChart); +var + ext: TDoubleRect; + R: Trect; +begin + ext := FChartFrame.Chart.CurrentExtent; + R.TopLeft := FChartFrame.Chart.GraphToImage(DoublePoint(ext.a.x, ext.b.y)); + R.BottomRight := FChartFrame.Chart.GraphToImage(DoublePoint(ext.b.x, ext.a.y)); + XHistogramChart.BorderSpacing.Left := R.Left; + XHistogramChart.BorderSpacing.Right := FChartFrame.Chart.Width - R.Right; + YHistogramChart.BorderSpacing.Top := R.Top; + YHistogramChart.BorderSpacing.Bottom := FChartFrame.Chart.Height - R.Bottom; +end; + + +procedure TCorSimForm.ChartFrameResize(Sender: TObject); +begin + XHistogramChart.Height := Min(FChartFrame.Width div 5, FChartFrame.Height div 5); + YHistogramChart.Width := XHistogramChart.Height; +end; + + +procedure TCorSimForm.Compute; +var + N: Integer; + a, b: Double; + xMean, yMean, xStdDev, yStdDev, corrXY: Double; +begin + N := StrToInt(NumObsEdit.Text); + xMean := StrToFloat(MeanXEdit.Text); + yMean := StrToFloat(MeanYEdit.Text); + xStdDev := StrToFloat(StdDevXEdit.Text); + yStdDev := StrToFloat(StdDevYEdit.Text); + corrXY := StrToFloat(CorrEdit.Text); + + GenerateData(N, xMean, yMean, xStdDev, yStdDev, corrXY, a, b); + + with SimResult do + begin + VecMeanStdDev(xValues, XMean, XStdDev); + VecMeanStdDev(yValues, YMean, YStdDev); + SimResult.CorrXY := (xValues * yValues - XMean * YMean * N) / ((N-1) * XStdDev * YStdDev); + B := CorrXY * (YStdDev / XStdDev); + A := YMean - B * XMean; + end; + + WriteReport(a, b, xMean, yMean, xStdDev, yStdDev, corrXY); + Plot; +end; + + +procedure TCorSimForm.GenerateData(ACount: Integer; + const XMean, YMean, XStdDev, YStdDev, CorrXY: Double; out A, B: Double); +var + corrSqr, yVariance, predVariance, errVariance, stdError: Double; + i: Integer; +begin Randomize; - SetLength(freqx, N + 1); - SetLength(freqy, N + 1); - SetLength(x, N + 1); - SetLength(y, N + 1); + // Calculate the fitted line parameters + B := CorrXY * (YStdDev / XStdDev); + A := YMean - B * XMean; - // generate x and y data observations - corsqr := corxy * corxy; - yvariance := ysd * ysd; - predvar := corsqr * yvariance; - errvariance := yvariance - predvar; - stderror := sqrt(errvariance); - b := corxy * (ysd / xsd); - constant := ymean - b * xmean; + // Calculate the "scatter" parameters + corrSqr := sqr(CorrXY); + yVariance := YStdDev * YStdDev; + predVariance := corrSqr * yVariance; + errVariance := yVariance - predVariance; + stdError := sqrt(errVariance); - newxmean := 0.0; - newymean := 0.0; - newxsd := 0.0; - newysd := 0.0; - newcorr := 0.0; - for i := 1 to N do + // Calculate x and y values + xValues := nil; + yValues := nil; + SetLength(xValues, ACount); + SetLength(yValues, ACount); + for i := 0 to ACount-1 do begin - x[i] := RandG(xmean, xsd); - randomerror := RandG(0.0, stderror); - y[i] := b * x[i] + constant + randomerror; - newxmean := newxmean + x[i]; - newymean := newymean + y[i]; - newxsd := newxsd + sqr(x[i]); - newysd := newysd + sqr(y[i]); - newcorr := newcorr + x[i] * y[i]; - end; - - newxsd := newxsd - sqr(newxmean) / N; - newxsd := newxsd / (N - 1); - newxsd := sqrt(newxsd); - - newysd := newysd - sqr(newymean) / N; - newysd := newysd / (N - 1); - newysd := sqrt(newysd); - - newcorr := newcorr - newxmean * newymean / N; - newcorr := newcorr / (N - 1); - newcorr := newcorr / (newxsd * newysd); - - newxmean := newxmean / N; - newymean := newymean / N; - newb := newcorr * (newysd / newxsd); - newconstant := newymean - newb * newxmean; - - lReport := TStringList.Create; - try - lReport.Add('POPULATION PARAMETERS FOR THE SIMULATION'); - lReport.Add(''); - lReport.Add('Mean X: %8.3f', [xmean]); - lReport.Add('Std. Dev. X: %8.3f', [xsd]); - lReport.Add(''); - lReport.Add('Mean Y: %8.3f', [ymean]); - lReport.Add('Std. Dev. Y: %8.3f', [ysd]); - lReport.Add(''); - lReport.Add('Product-Moment Correlation: %8.3f', [corxy]); - lReport.Add('Regression line slope: %8.3f', [b]); - lReport.Add(' constant: %8.3f', [constant]); - lReport.Add(''); - lReport.Add(DIVIDER); - lReport.Add(''); - lReport.Add('SAMPLE STATISTICS FOR %d OBSERVATIONS FROM THE POPULATION', [N]); - lReport.Add(''); - lReport.Add('Mean X: %8.3f', [newxmean]); - lReport.Add('Std. Dev. X: %8.3f', [newxsd]); - lReport.Add(''); - lReport.Add('Mean Y: %8.3f', [newymean]); - lReport.Add('Std. Dev. Y: %8.3f', [newysd]); - lReport.Add(''); - lReport.Add('Product-Moment Correlation: %8.3f', [newcorr]); - lReport.Add('Regression line slope: %8.3f', [newb]); - lReport.Add(' constant: %8.3f', [newconstant]); - lReport.Add(''); - lReport.Add(DIVIDER); - lReport.Add(''); - lReport.Add('Pair No. X Y '); - lReport.Add('-------- --------- ---------'); - for i := 1 to N do - lReport.Add(' %4d %8.3f %8.3f', [i, x[i], y[i]]); - - DisplayReport(lReport); - - Plot(); - - finally - lReport.Free; - freqx := nil; - freqy := nil; - x := nil; - y := nil; + xValues[i] := RandG(xMean, xStdDev); + yValues[i] := A + B * xValues[i] + RandG(0.0, stdError); end; end; -procedure TCorSimFrm.FormShow(Sender: TObject); -begin - Image1.Canvas.Pen.Color := clBlack; - Image1.Canvas.Brush.Color := clWhite; - Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height); - MeanX.Text := '100'; - MeanY.Text := '100'; - SDX.Text := '15'; - SDY.Text := '15'; - Corr.Text := '.8'; - Nobs.Text := '100'; +procedure TCorSimForm.NumBinsEditChange(Sender: TObject); +begin + if Assigned(FChartFrame) and Assigned(xValues) and Assigned(yValues) then + Plot; end; -procedure TCorSimFrm.MeanYKeyPress(Sender: TObject; var Key: char); -begin - if Key = #13 then SDX.SetFocus; -end; -procedure TCorSimFrm.NobsKeyPress(Sender: TObject; var Key: char); -begin - if Key = #13 then ComputeBtn.SetFocus; -end; - -procedure TCorSimFrm.SDXKeyPress(Sender: TObject; var Key: char); -begin - if Key = #13 then SDY.SetFocus; -end; - -procedure TCorSimFrm.SDYKeyPress(Sender: TObject; var Key: char); -begin - if Key = #13 then Corr.SetFocus; -end; - -procedure TCorSimFrm.Plot; +procedure TCorSimForm.Plot; var - minx, maxx, miny, maxy, xincrement, yincrement: double; - predy1, predy2, lowerx, upperx, frange, prop: double; - charlabel: string; - xpos, ypos, xpos1, ypos1, xpos2, ypos2: integer; - i, winwidth, winheight, xoffset, yoffset, xaxislong, yaxislong: integer; - j, xspacing, yspacing, labelwidth, minfreq, maxfreq: integer; - flength, theight, lowery, uppery: integer; + freqData: DblDyneVec; + x, mn, mx: Double; + i, n: Integer; + xpts, ypts: DblDyneVec; begin - // get min and max of x and y points - minx := x[1]; - maxx := minx; - miny := y[1]; - maxy := miny; - for i := 1 to N do + FChartFrame.Clear; + XHistogramSeries.Clear; + YHistogramSeries.Clear; + + // Chart labelling + FChartFrame.SetXTitle('x distribution'); + FChartFrame.SetYTitle('y distribution'); + FChartFrame.SetFooter(Format( + 'Correlation: %.3f' + LineEnding + + 'X Mean: %.3f, StdDev: %.3f' + LineEnding + + 'Y Mean: %.3f, StdDev: %.3f', [ + SimResult.CorrXY, SimResult.XMean, SimResult.XStdDev, SimResult.YMean, SimResult.YStdDev + ])); + FChartFrame.Chart.Legend.Alignment := laBottomCenter; + FChartFrame.Chart.Legend.ColumnCount := 2; + + // Draw data points + FChartFrame.PlotXY(ptSymbols, xValues, yValues, nil, nil, 'Data', DATA_COLORS[0]); + + // Draw top histogram + VecMaxMin(xValues, mx, mn); + n := NumBinsEdit.Value; + freqData := VecHistogram(xValues, mn, mx, n); + for i:= 0 to n-1 do begin - if (minx > x[i]) then minx := x[i]; - if (maxx < x[i]) then maxx := x[i]; - if (miny > y[i]) then miny := y[i]; - if (maxy < y[i]) then maxy := y[i]; - end; - xincrement := (maxx - minx) / 10; - yincrement := (maxy - miny) / 10; - - winwidth := Image1.Width; - winheight := Image1.Height; - xoffset := winwidth div 5; - yoffset := winheight div 5; - xaxislong := winwidth - xoffset- winwidth div 10; - yaxislong := winheight - yoffset - winheight div 10; - xspacing := xaxislong div 10; - yspacing := yaxislong div 10; - - Image1.Canvas.Pen.Color := clBlack; - Image1.Canvas.Line(xoffset, yaxislong, winwidth, yaxislong); - Image1.canvas.Line(xoffset, yaxislong, xoffset, 0); - - // do xaxis - for i := 0 to 11 do - begin - Image1.Canvas.Line(xoffset + i * xspacing, yaxislong, xoffset + i * xspacing, yaxislong + 10); - charlabel := Format('%.3f', [minx + i * xincrement]); - labelwidth := Image1.Canvas.TextWidth(charlabel); - xpos := xoffset + i * xspacing - labelwidth div 2; - ypos := yaxislong + 12; - Image1.Canvas.TextOut(xpos, ypos, charlabel); + x := i / (n-1) * (mx - mn) + mn; + XHistogramSeries.AddXY(x, freqData[i]); end; - // do yaxis - for i := 0 to 11 do - begin - Image1.Canvas.Line(xoffset, yaxislong - i * yspacing, xoffset-10, yaxislong - i * yspacing); - charlabel := Format('%.3f', [miny + i * yincrement]); - labelwidth := Image1.Canvas.TextWidth(charlabel); - xpos := xoffset - 10 - labelwidth; - ypos := yaxislong - i * yspacing; - Image1.Canvas.TextOut(xpos, ypos, charlabel); - end; + // Draw regression line + // Regression line + SetLength(xpts, 2); + SetLength(yPts, 2); + xpts[0] := mn; ypts[0] := SimResult.a + SimResult.b * xpts[0]; + xpts[1] := mx; ypts[1] := SimResult.a + SimResult.b * xpts[1]; + FChartFrame.PlotXY(ptLines, xpts, ypts, nil, nil, 'Regression line', clBlack); - // plot points - Image1.Canvas.Pen.Color := clRed; - for i := 1 to N do + // Draw right histogram + VecMaxMin(yValues, mx, mn); + n := NumBinsEdit.Value; + freqData := VecHistogram(yValues, mn, mx, n); + for i := 0 to n-1 do begin - xpos := round(xoffset + ((x[i] - minx) / (maxx - minx) * xaxislong)); - ypos := round(yaxislong - ((y[i] - miny) / (maxy - miny) * yaxislong)); - Image1.Canvas.Ellipse(xpos, ypos, xpos+5, ypos+5); + x := i / (n-1) * (mx - mn) + mn; + YHistogramSeries.AddXY(x, freqData[i]); end; - - // draw regression line - Image1.Canvas.Pen.Color := clBlack; - predy1 := newb * minx + newconstant; - predy2 := newb * maxx + newconstant; - xpos1 := xoffset; - xpos2 := xoffset + xaxislong; - ypos1 := round(yaxislong - ((predy1 - miny) / (maxy - miny) * yaxislong)); - ypos2 := round(yaxislong - ((predy2 - miny) / (maxy - miny) * yaxislong)); - Image1.Canvas.Line(xpos1, ypos1, xpos2, ypos2); - - // do x frequency distribution - xincrement := (maxx - minx) / 50.0; - xspacing := xaxislong div 50; - for j := 1 to 51 do - freqx[j] := 0; - for i := 1 to N do - begin - for j := 1 to 51 do - begin - lowerx := minx + j * xincrement; - upperx := minx + (j+1) * xincrement; - if (x[i] >= lowerx) and (x[i] < upperx) then - freqx[j] := freqx[j] + 1; - end; - end; - - // plot the x frequencies - minfreq := N; - maxfreq := 0; - for j := 1 to 51 do - begin - if (freqx[j] > maxfreq) then - maxfreq := freqx[j]; - if (freqx[j] < minfreq) then - minfreq := freqx[j]; - end; - flength := winheight - (yaxislong + 25) - Panel1.Height; - for j := 1 to 51 do - begin - xpos := xoffset + j * xspacing; - ypos1 := round(yaxislong + 25 + (freqx[j] - minfreq)/ (maxfreq-minfreq) * flength); - ypos2 := yaxislong + 25; - Image1.Canvas.Line(xpos, ypos1, xpos, ypos2); - end; - Image1.Canvas.Line(xoffset, yaxislong+25, winwidth, yaxislong+25); - - xpos := 20; - ypos := yaxislong+30; - Image1.Canvas.TextOut(xpos, ypos, 'X DISTRIBUTION'); - - theight := Image1.Canvas.TextHeight('X'); - ypos := ypos + theight; - charlabel := Format('Correlation: %.3f', [newcorr]); - Image1.Canvas.TextOut(xpos, ypos, charlabel); - ypos := ypos + theight; - charlabel := Format('Mean X: %.3f; Mean Y: %.3f', [newxmean, newymean]); - Image1.Canvas.TextOut(xpos, ypos, charlabel); - charlabel := Format('SD X: %.3f; SD Y: %.3f', [newxsd, newysd]); - ypos := ypos + theight; - Image1.Canvas.TextOut(xpos,ypos,charlabel); - - // do y frequency distribution - yincrement := (maxy-miny) / 50.0; - yspacing := yaxislong div 50; - for j := 1 to 51 do - freqy[j] := 0; - for i := 1 to N do - begin - for j := 1 to 51 do - begin - lowery := round(miny + j * yincrement); - uppery := round(miny + ((j+1) * yincrement)); - if (y[i] >= lowery) and (y[i] < uppery) then - freqy[j] := freqy[j] + 1; - end; - end; - - // plot the y frequencies - minfreq := N; - maxfreq := 0; - for j := 1 to 51 do - begin - if (freqy[j] > maxfreq) then maxfreq := freqy[j]; - if (freqy[j] < minfreq) then minfreq := freqy[j]; - end; - flength := winwidth - (xaxislong + 150); - for j := 1 to 51 do - begin - ypos := yaxislong - j * yspacing; - frange := maxfreq - minfreq; - prop := (freqy[j] - minfreq) / frange; - xpos1 := round(xoffset - 50 - prop * flength); - xpos2 := xoffset - 50; - Image1.Canvas.Line(xpos1, ypos, xpos2, ypos); - end; - Image1.Canvas.Line(xoffset - 50, yaxislong, xoffset - 50, 0); - Image1.Canvas.TextOut(0,0,'Y DISTRIBUTION'); end; -function TCorSimFrm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; + +procedure TCorSimForm.Reset; +begin + inherited; + MeanXEdit.Text := '100'; + MeanYEdit.Text := '100'; + StdDevXEdit.Text := '15'; + StdDevYEdit.Text := '15'; + CorrEdit.Text := '0.8'; + NumObsEdit.Text := '100'; + NumBinsEdit.Value := 12; +end; + + +function TCorSimForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; +var + x: Double; + n: Integer; begin Result := false; - if (MeanX.Text = '') or (MeanY.Text = '') or - (SDX.Text = '') or (SDY.Text = '') or - (Corr.Text = '') or (NObs.Text = '') then + if (MeanXEdit.Text = '') or (MeanYEdit.Text = '') or + (StdDevXEdit.Text = '') or (StdDevYEdit.Text = '') or + (CorrEdit.Text = '') or (NumObsEdit.Text = '') then begin - if MeanX.Text = '' then - AControl := MeanX - else if MeanY.Text = '' then - AControl := MeanY - else if SDX.Text = '' then - AControl := SDX - else if SDY.Text = '' then - AControl := SDY - else if Corr.Text = '' then - AControl := Corr - else if NObs.Text = '' then - AControl := NObs; + if MeanXEdit.Text = '' then + AControl := MeanXEdit + else if MeanYEdit.Text = '' then + AControl := MeanYEdit + else if StdDevXEdit.Text = '' then + AControl := StdDevXEdit + else if StdDevYEdit.Text = '' then + AControl := StdDevYEdit + else if CorrEdit.Text = '' then + AControl := CorrEdit + else if NumObsEdit.Text = '' then + AControl := NumObsEdit; AMsg := 'Input cannot be empty.'; exit; end; - if not TryStrToFloat(MeanX.Text, xMean) then + if not TryStrToFloat(MeanXEdit.Text, x) then begin - AControl := MeanX; + AControl := MeanXEdit; AMsg := 'Mean X must be a valid number.'; exit; end; - if not TryStrToFloat(MeanY.Text, yMean) then + if not TryStrToFloat(MeanYEdit.Text, x) then begin - AControl := MeanY; + AControl := MeanYEdit; AMsg := 'Mean Y must be a valid number.'; exit; end; - if not TryStrToFloat(SDX.Text, xSD) or (xSD <= 0) then + if not TryStrToFloat(StdDevXEdit.Text, x) or (x <= 0) then begin - AControl := SDX; + AControl := StdDevXEdit; AMsg := 'Std.Dev X must be a valid positive number.'; exit; end; - if not TryStrToFloat(SDY.Text, ySD) or (ySD <= 0) then + if not TryStrToFloat(StdDevYEdit.Text, x) or (x <= 0) then begin - AControl := SDY; + AControl := StdDevYEdit; AMsg := 'Std.Dev Y must be a valid positive number.'; exit; end; - if not TryStrToFloat(Corr.Text, corXY) then + if not TryStrToFloat(CorrEdit.Text, x) then begin - AControl := Corr; + AControl := CorrEdit; AMsg := 'Correlation XY must be a valid number.'; exit; end; - if not TryStrToInt(NObs.Text, N) or (N <= 0) then + if not TryStrToInt(NumObsEdit.Text, n) or (n <= 0) then begin - AControl := NObs; + AControl := NumObsEdit; AMsg := 'Number of observations must be a valid positive integer.'; exit; end; + if NumBinsEdit.Text = '' then + begin + AControl := NumBinsEdit; + AMsg := 'Empty input not allowed here.'; + exit; + end; + + if not TryStrToInt(NumBinsEdit.Text, n) or (n < 2) then + begin + AControl := NumBinsEdit; + AMsg := 'There must be at least 2 bins.'; + exit; + end; + Result := true; end; -initialization - {$I corsimunit.lrs} + +procedure TCorSimForm.WriteReport( + A, B, XMean, YMean, XStdDev, YStdDev, CorrXY: Double); +var + lReport: TStrings; + i: Integer; +begin + lReport := TStringList.Create; + try + lReport.Add('POPULATION PARAMETERS FOR THE SIMULATION'); + lReport.Add(''); + lReport.Add('Mean X: %8.3f', [XMean]); + lReport.Add('Std. Dev. X: %8.3f', [XStdDev]); + lReport.Add(''); + lReport.Add('Mean Y: %8.3f', [YMean]); + lReport.Add('Std. Dev. Y: %8.3f', [YStdDev]); + lReport.Add(''); + lReport.Add('Product-Moment Correlation: %8.3f', [CorrXY]); + lReport.Add('Regression line slope: %8.3f', [B]); + lReport.Add(' constant: %8.3f', [A]); + lReport.Add(''); + lReport.Add(DIVIDER); + lReport.Add(''); + lReport.Add('SAMPLE STATISTICS FOR %d OBSERVATIONS FROM THE POPULATION', [Length(xValues)]); + lReport.Add(''); + lReport.Add('Mean X: %8.3f', [SimResult.XMean]); + lReport.Add('Std. Dev. X: %8.3f', [SimResult.XStdDev]); + lReport.Add(''); + lReport.Add('Mean Y: %8.3f', [SimResult.YMean]); + lReport.Add('Std. Dev. Y: %8.3f', [SimResult.YStdDev]); + lReport.Add(''); + lReport.Add('Product-Moment Correlation: %8.3f', [SimResult.CorrXY]); + lReport.Add('Regression line slope: %8.3f', [SimResult.B]); + lReport.Add(' constant: %8.3f', [SimResult.A]); + lReport.Add(''); + lReport.Add(DIVIDER); + lReport.Add(''); + lReport.Add('Pair No. X Y '); + lReport.Add('-------- --------- ---------'); + for i := 0 to High(XValues) do + lReport.Add(' %4d %8.3f %8.3f', [i, xValues[i], yValues[i]]); + + FReportFrame.DisplayReport(lReport); + + finally + lReport.Free; + end; +end; + end. diff --git a/applications/lazstats/source/units/matrixunit.pas b/applications/lazstats/source/units/matrixunit.pas index 9274ec853..78ac80745 100644 --- a/applications/lazstats/source/units/matrixunit.pas +++ b/applications/lazstats/source/units/matrixunit.pas @@ -42,19 +42,24 @@ procedure VecMeanVarStdDevSS(const AData: TDblVector; procedure VecSumSS(const AData: TDblVector; out Sum, SS: Double); +function VecHistogram(const AData: TDblVector; AMin, AMax: Double; + N: Integer): TDblVector; + function VecMedian(const AData: TDblVector): Double; + // Matrices +{ NOTE: Indices follow math convention: + - 1st index is the row index, i.e. runs vertically + - 2nd index is the col index, i.e. runs horizontally + All indices are 0-based. } + operator + (A, B: TDblMatrix): TDblMatrix; operator - (A, B: TDblMatrix): TDblMatrix; operator * (A, B: TDblMatrix): TDblMatrix; operator * (A: TDblMatrix; v: TDblVector): TDblVector; -{ NOTE: Indices follow math convention: - - 1st index is the row index, i.e. runs vertically - - 2nd index is the col index, i.e. runs horizontally - All indices are 0-based. } function MatAppendColVector(A: TDblMatrix; v: TDblVector): TDblMatrix; procedure MatCheck(A: TDblMatrix); procedure MatCheckSquare(A: TDblMatrix; out n: Integer); @@ -319,6 +324,26 @@ begin end; +function VecHistogram(const AData: TDblVector; AMin, AMax: Double; + N: Integer): TDblVector; +var + i, j: Integer; + factor: Double; +begin + SetLength(Result, N); + for j := 0 to N-1 do Result[j] := 0; + + factor := N / (AMax - AMin); + for i := 0 to High(AData) do + begin + j := trunc((AData[i] - AMin) * factor); + if j <= 0 then j := 0; + if j >= N then j := N-1; + Result[j] := Result[j] + 1; + end; +end; + + function VecMedian(const AData: TDblVector): Double; var N, midPt: integer;