LazStats: Refactor PlotXYUnit to use Calc_BivariateRegression()

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7758 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2020-10-10 14:10:57 +00:00
parent 3cf0e45644
commit 7f6ad8982a
5 changed files with 178 additions and 149 deletions

View File

@ -264,7 +264,7 @@
<Unit20> <Unit20>
<Filename Value="forms\analysis\descriptive\plotxyunit.pas"/> <Filename Value="forms\analysis\descriptive\plotxyunit.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<ComponentName Value="PlotXYFrm"/> <ComponentName Value="PlotXYForm"/>
<HasResources Value="True"/> <HasResources Value="True"/>
<ResourceBaseClass Value="Form"/> <ResourceBaseClass Value="Form"/>
<UnitName Value="PlotXYUnit"/> <UnitName Value="PlotXYUnit"/>

View File

@ -1,4 +1,4 @@
inherited PlotXYFrm: TPlotXYFrm inherited PlotXYForm: TPlotXYForm
Left = 427 Left = 427
Height = 500 Height = 500
Top = 175 Top = 175

View File

@ -8,15 +8,15 @@ interface
uses uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, ComCtrls, StdCtrls, ExtCtrls, Buttons, ComCtrls, Grids,
MainUnit, Globals, FunctionsLib, BasicStatsReportAndChartFormUnit, MainUnit, Globals, MathUnit, BasicStatsReportAndChartFormUnit,
ReportFrameUnit, ChartFrameUnit; ReportFrameUnit, ChartFrameUnit;
type type
{ TPlotXYFrm } { TPlotXYForm }
TPlotXYFrm = class(TBasicStatsReportAndChartForm) TPlotXYForm = class(TBasicStatsReportAndChartForm)
ConfEdit: TEdit; ConfEdit: TEdit;
Label4: TLabel; Label4: TLabel;
LineChk: TCheckBox; LineChk: TCheckBox;
@ -41,15 +41,20 @@ type
procedure YOutBtnClick(Sender: TObject); procedure YOutBtnClick(Sender: TObject);
private private
procedure PlotXY(XPoints, YPoints, UpConf, LowConf: DblDyneVec; procedure PlotXY(XPoints, YPoints: DblDyneVec;
XMean, YMean, R, Slope, Intercept: Double); const ARegressionResults: TBivariateRegressionResults);
function PrepareData(ADataGrid: TStringGrid;
out xCol, ycol: Integer; out XData, YData: DblDyneVec;
out ColNoSelected: IntDyneVec): Boolean;
procedure WriteToReport(ARegressionResults: TBivariateRegressionResults);
protected protected
procedure AdjustConstraints; override; procedure AdjustConstraints; override;
procedure Compute; override; procedure Compute; override;
procedure UpdateBtnStates; override; procedure UpdateBtnStates; override;
function Validate(out AMsg: String; out AControl: TWinControl; function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override;
Xcol,Ycol: Integer): Boolean; reintroduce;
public public
constructor Create(AOwner: TComponent); override; constructor Create(AOwner: TComponent); override;
@ -57,7 +62,8 @@ type
end; end;
var var
PlotXYFrm: TPlotXYFrm; PlotXYForm: TPlotXYForm;
implementation implementation
@ -65,12 +71,12 @@ implementation
uses uses
TAChartUtils, TAChartAxisUtils, TALegend, TASources, TACustomSeries, TASeries, TAChartUtils, TAChartAxisUtils, TALegend, TASources, TACustomSeries, TASeries,
MathUnit, GridProcs, Utils; GridProcs, Utils;
{ TPlotXYFrm } { TPlotXYForm }
constructor TPlotXYFrm.Create(AOwner: TComponent); constructor TPlotXYForm.Create(AOwner: TComponent);
begin begin
inherited; inherited;
@ -98,7 +104,7 @@ begin
end; end;
procedure TPlotXYFrm.AdjustConstraints; procedure TPlotXYForm.AdjustConstraints;
begin begin
ParamsPanel.Constraints.MinHeight := OptionsGroup.Top + OptionsGroup.Height + ParamsPanel.Constraints.MinHeight := OptionsGroup.Top + OptionsGroup.Height +
OptionsGroup.BorderSpacing.Bottom + ButtonBevel.Height + OptionsGroup.BorderSpacing.Bottom + ButtonBevel.Height +
@ -110,26 +116,19 @@ begin
end; end;
procedure TPlotXYFrm.Compute; procedure TPlotXYForm.Compute;
var var
xMean, yMean, xVariance, yVariance, xStddev, yStddev: double;
SXX, SXY, SYY, R, slope, intercept, t, confBand: Double;
sePred, predicted, sedata: double;
i, xCol, yCol, N, DF: integer;
xValues: DblDyneVec = nil; xValues: DblDyneVec = nil;
yValues: DblDyneVec = nil; yValues: DblDyneVec = nil;
UpConf: DblDyneVec = nil;
lowConf: DblDyneVec = nil;
ColNoSelected: IntDyneVec= nil; ColNoSelected: IntDyneVec= nil;
xCol, yCol: Integer;
confBand: Double;
regressionRes: TBivariateRegressionResults;
C: TWinControl; C: TWinControl;
msg: String; msg: String;
lReport: TStrings;
begin begin
xCol := OS3MainFrm.DataGrid.Rows[0].IndexOf(XEdit.Text); // Validation: Make sure that XEdit and YEdit are not empty
yCol := OS3MainFrm.DataGrid.Rows[0].IndexOf(YEdit.Text); if not Validate(msg, C) then
// Validation
if not Validate(msg, C, Xcol, Ycol) then
begin begin
C.SetFocus; C.SetFocus;
ErrorMsg(msg); ErrorMsg(msg);
@ -137,99 +136,36 @@ begin
exit; exit;
end; end;
SetLength(ColNoSelected, 2); // Extract data from Grid
ColNoSelected[0] := Xcol; if not PrepareData(OS3MainFrm.DataGrid, xCol, yCol, xValues, yValues, ColNoSelected) then
ColNoSelected[1] := Ycol;
xValues := CollectValues(OS3MainFrm.DataGrid, xCol, ColNoSelected);
yValues := CollectValues(OS3MainFrm.DataGrid, yCol, ColNoSelected);
if (Length(yValues) = 0) then
begin
ErrorMsg('No y data');
exit; exit;
end;
if (Length(xValues) <> Length(yValues)) then
begin
ErrorMsg('Different count of x and y values.');
exit;
end;
N := Length(yValues);
Calc_MeanVarStddevSS(xValues, xMean, xVariance, xStdDev, SXX); // Sort on x values
Calc_MeanVarStddevSS(yValues, yMean, yVariance, yStdDev, SYY); SortOnX(xValues, yValues);
SXY := 0;
for i := 0 to N-1 do
SXY := SXY + xValues[i] * yValues[i];
R := (SXY - xMean * yMean * N) / ((N - 1) * xStdDev * yStdDev); // Calculate regression
sePred := sqrt(1.0 - sqr(R)) * yStdDev * sqrt((N - 1) / (N - 2)); confBand := StrToFloat(ConfEdit.Text) / 100.0;
slope := R * yStdDev / xStdDev; Calc_BivariateRegression(xValues, yValues, confBand, regressionRes);
intercept := yMean - slope * xMean;
// Print the descriptive statistics to the output frame // Print the descriptive statistics to the output frame
lReport := TStringList.Create; WriteToReport(regressionRes);
try
lReport.Add('X vs. Y PLOT');
lReport.Add('');
lReport.Add('Data file: %s', [OS3MainFrm.FileNameEdit.Text]);
lReport.Add('');
lReport.Add('Variables:');
lReport.Add(' X: %s', [XEdit.Text]);
lReport.Add(' Y: %s', [YEdit.Text]);
lReport.Add('');
lReport.Add('Variable Mean Variance Std.Dev.');
lReport.Add('---------- -------- -------- --------');
lReport.Add('%-10s %8.2f %8.2f %8.2f', [XEdit.Text, XMean, XVariance, XStdDev]);
lReport.Add('%-10s %8.2f %8.2f %8.2f', [YEdit.Text, YMean, YVariance, YStdDev]);
lReport.Add('');
lReport.Add('Regression:');
lReport.Add(' Correlation: %8.3f', [R]);
lReport.Add(' Slope: %8.3f', [slope]);
lReport.Add(' Intercept: %8.3f', [intercept]);
lReport.Add(' Standard Error of Estimate: %8.3f', [sePred]);
lReport.Add(' Number of good cases: %8d', [N]);
FReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
// Get upper and lower confidence points for each X value
if ConfChk.Checked then
begin
SortOnX(xValues, yValues);
SetLength(UpConf, N);
SetLength(lowConf, N);
confBand := StrToFloat(ConfEdit.Text) / 100.0;
DF := N - 2;
t := InverseT(confBand, DF);
for i := 0 to N-1 do
begin
predicted := intercept + slope * xValues[i];
seData := sePred * sqrt(1.0 + 1/N + sqr(xValues[i] - XMean) / SXX);
upConf[i] := predicted + t * seData;
lowConf[i] := predicted - t * seData;
end;
end
else
confBand := 0.0;
// Plot the values (and optional line and confidence band if elected) // Plot the values (and optional line and confidence band if elected)
PlotXY(xValues, yValues, upConf, lowConf, xMean, yMean, R, slope, intercept); PlotXY(xValues, yValues, regressionRes);
end; end;
procedure TPlotXYFrm.PlotXY(XPoints, YPoints, UpConf, LowConf: DblDyneVec; procedure TPlotXYForm.PlotXY(XPoints, YPoints: DblDyneVec;
XMean, YMean, R, Slope, Intercept: Double); const ARegressionResults: TBivariateRegressionResults);
var var
tmpX: array of Double = nil; tmpX: DblDyneVec = nil;
tmpY: array of Double = nil; tmpY: DblDyneVec = nil;
conf: DblDyneVec = nil;
xmin, xmax, ymin, ymax: Double; xmin, xmax, ymin, ymax: Double;
rightLabels: TListChartSource; rightLabels: TListChartSource;
topLabels: TListChartSource; topLabels: TListChartSource;
ser: TChartSeries; ser: TChartSeries;
i: Integer;
begin begin
rightLabels := FChartFrame.Chart.AxisList[2].Marks.Source as TListChartSource; rightLabels := FChartFrame.Chart.AxisList[2].Marks.Source as TListChartSource;
rightLabels.Clear; rightLabels.Clear;
@ -239,16 +175,20 @@ begin
// Titles // Titles
FChartFrame.SetTitle('X vs. Y plot using file ' + OS3MainFrm.FileNameEdit.Text); FChartFrame.SetTitle('X vs. Y plot using file ' + OS3MainFrm.FileNameEdit.Text);
FChartFrame.SetFooter(Format('R(X,Y) = %.3f, Slope = %.3f, Intercept = %.3f', [ with ARegressionResults do
R, Slope, Intercept FChartFrame.SetFooter(Format('R(X,Y) = %.3f, Slope = %.3f, Intercept = %.3f', [
])); R, Slope, Intercept
]));
FChartFrame.SetXTitle(XEdit.Text); FChartFrame.SetXTitle(XEdit.Text);
FChartFrame.SetYTitle(YEdit.Text); FChartFrame.SetYTitle(YEdit.Text);
// Draw upper confidence band // Draw upper confidence band
if ConfChk.Checked then if ConfChk.Checked then
begin begin
ser := FChartFrame.PlotXY(ptLines, XPoints, UpConf, nil, nil, 'Upper confidence band', clRed); SetLength(conf, ARegressionResults.Count);
for i := 0 to High(conf) do
conf[i] := ARegressionResults.ConfidenceLimits(XPoints[i], true);
ser := FChartFrame.PlotXY(ptLines, XPoints, conf, nil, nil, 'Upper confidence band', clRed);
rightLabels.Add(ser.yValue[ser.Count-1], ser.YValue[ser.Count-1], 'UCL'); rightLabels.Add(ser.yValue[ser.Count-1], ser.YValue[ser.Count-1], 'UCL');
end; end;
@ -258,7 +198,10 @@ begin
// Draw lower confidence band // Draw lower confidence band
if ConfChk.Checked then if ConfChk.Checked then
begin begin
ser := FChartFrame.PlotXY(ptLines, XPoints, LowConf, nil, nil, 'Lower confidence band', clRed); SetLength(conf, ARegressionResults.Count);
for i := 0 to High(conf) do
conf[i] := ARegressionResults.ConfidenceLimits(XPoints[i], false);
ser := FChartFrame.PlotXY(ptLines, XPoints, conf, nil, nil, 'Lower confidence band', clRed);
rightLabels.Add(ser.yValue[ser.Count-1], ser.YValue[ser.Count-1], 'LCL'); rightLabels.Add(ser.yValue[ser.Count-1], ser.YValue[ser.Count-1], 'LCL');
end; end;
@ -268,19 +211,26 @@ begin
// Draw means // Draw means
if MeansChk.Checked then if MeansChk.Checked then
begin with ARegressionResults do
FChartFrame.VertLine(XMean, clGreen, psDashDot, 'Mean ' + XEdit.Text); begin
topLabels.Add(XMean, XMean, 'Mean ' + XEdit.Text); FChartFrame.VertLine(XMean, clGreen, psDashDot, 'Mean ' + XEdit.Text);
FChartFrame.HorLine(YMean, clGreen, psDash, 'Mean ' + YEdit.Text); topLabels.Add(XMean, XMean, 'Mean ' + XEdit.Text);
rightLabels.Add(YMean, YMean, 'Mean ' + YEdit.Text); FChartFrame.HorLine(YMean, clGreen, psDash, 'Mean ' + YEdit.Text);
end; rightLabels.Add(YMean, YMean, 'Mean ' + YEdit.Text);
end;
// Draw regression line // Draw regression line
if LineChk.Checked then if LineChk.Checked then
begin begin
SetLength(tmpX, 2); SetLength(tmpY, 2); SetLength(tmpX, 2);
tmpX[0] := xmin; tmpY[0] := tmpX[0] * slope + intercept; SetLength(tmpY, 2);
tmpX[1] := xmax; tmpY[1] := tmpX[1] * slope + intercept; tmpX[0] := xmin;
tmpX[1] := xmax;
with ARegressionResults do
begin
tmpY[1] := tmpX[1] * Slope + Intercept;
tmpY[0] := tmpX[0] * Slope + Intercept;
end;
ser := FChartFrame.PlotXY(ptLines, tmpX, tmpY, nil, nil, 'Predicted', clBlack); ser := FChartFrame.PlotXY(ptLines, tmpX, tmpY, nil, nil, 'Predicted', clBlack);
rightLabels.Add(tmpY[1], tmpY[1], 'Predicted'); rightLabels.Add(tmpY[1], tmpY[1], 'Predicted');
end; end;
@ -289,67 +239,110 @@ begin
end; end;
procedure TPlotXYfrm.Reset; function TPlotXYForm.PrepareData(ADataGrid: TStringGrid;
out xCol, ycol: Integer; out XData, YData: DblDyneVec;
out ColNoSelected: IntDyneVec): Boolean;
var
N: Integer;
begin
Result := false;
ColNoSelected := nil;
XData := nil;
YData := nil;
xCol := GetVariableIndex(ADataGrid, XEdit.Text);
yCol := GetVariableIndex(ADataGrid, YEdit.Text);
if xCol = -1 then
begin
ErrorMsg('X variable not found.');
exit;
end;
if yCol = -1 then
begin
ErrorMsg('Y variable not found.');
exit;
end;
SetLength(ColNoSelected, 2);
ColNoSelected[0] := xCol;
ColNoSelected[1] := yCol;
XData := CollectValues(ADataGrid, xCol, colNoSelected);
YData := CollectValues(ADataGrid, ycol, colNoSelected);
N := Length(XData);
if N < 3 then
begin
ErrorMsg('At least three data points required.');
exit;
end;
if N <> Length(YData) then
begin
ErrorMsg('Equal count of cases required for x and y variables.');
exit;
end;
Result := true;
end;
procedure TPlotXYForm.Reset;
var var
i: integer; i: integer;
begin begin
inherited;
XEdit.Text := ''; XEdit.Text := '';
YEdit.Text := ''; YEdit.Text := '';
ConfEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT); ConfEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT);
LineChk.Checked := false; LineChk.Checked := false;
MeansChk.Checked := false; MeansChk.Checked := false;
ConfChk.Checked := false; ConfChk.Checked := false;
VarList.Items.Clear; VarList.Items.Clear;
for i := 1 to NoVariables do for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
if Assigned(FChartFrame) then
FChartFrame.Clear;
if Assigned(FReportFrame) then
FReportFrame.Clear;
UpdateBtnStates; UpdateBtnStates;
end; end;
procedure TPlotXYFrm.UpdateBtnStates; procedure TPlotXYForm.UpdateBtnStates;
begin begin
inherited;
XInBtn.Enabled := (VarList.ItemIndex > -1) and (XEdit.Text = ''); XInBtn.Enabled := (VarList.ItemIndex > -1) and (XEdit.Text = '');
XoutBtn.Enabled := (XEdit.Text <> ''); XoutBtn.Enabled := (XEdit.Text <> '');
YinBtn.Enabled := (VarList.ItemIndex > -1) and (YEdit.Text = ''); YinBtn.Enabled := (VarList.ItemIndex > -1) and (YEdit.Text = '');
YoutBtn.Enabled := (YEdit.Text <> ''); YoutBtn.Enabled := (YEdit.Text <> '');
if Assigned(FReportFrame) then
FReportFrame.UpdateBtnStates;
if Assigned(FChartFrame) then
FChartFrame.UpdateBtnStates;
end; end;
function TPlotXYFrm.Validate(out AMsg: String; out AControl: TWinControl; function TPlotXYForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean;
Xcol, Ycol: Integer): Boolean;
begin begin
Result := false; Result := false;
if (Xcol < 0) then if XEdit.Text = '' then
begin begin
AControl := XEdit; AMsg := 'No variable selected for X.';
AMsg := 'No case selected for X.';
exit; exit;
end; end;
if (Ycol < 0) then if YEdit.Text = '' then
begin begin
AControl := YEdit; AControl := YEdit;
AMsg := 'No case selected for Y.'; AMsg := 'No variable selected for Y.';
exit; exit;
end; end;
Result := true; Result := true;
end; end;
procedure TPlotXYFrm.VarListDblClick(Sender: TObject); procedure TPlotXYForm.VarListDblClick(Sender: TObject);
var var
index: integer; index: integer;
begin begin
@ -366,13 +359,49 @@ begin
end; end;
procedure TPlotXYFrm.VarListSelectionChange(Sender: TObject; User: boolean); procedure TPlotXYForm.VarListSelectionChange(Sender: TObject; User: boolean);
begin begin
UpdateBtnStates; UpdateBtnStates;
end; end;
procedure TPlotXYFrm.XInBtnClick(Sender: TObject); procedure TPlotXYForm.WriteToReport(ARegressionResults: TBivariateRegressionResults);
var
lReport: TStrings;
begin
lReport := TStringList.Create;
try
lReport.Add('X vs. Y PLOT');
lReport.Add('');
lReport.Add('Data file: %s', [OS3MainFrm.FileNameEdit.Text]);
lReport.Add('');
lReport.Add('Variables:');
lReport.Add(' X: %s', [XEdit.Text]);
lReport.Add(' Y: %s', [YEdit.Text]);
lReport.Add('');
lReport.Add('Variable Mean Variance Std.Dev.');
lReport.Add('---------- -------- -------- --------');
with ARegressionResults do
begin
lReport.Add('%-10s %8.2f %8.2f %8.2f', [XEdit.Text, XMean, XVariance, XStdDev]);
lReport.Add('%-10s %8.2f %8.2f %8.2f', [YEdit.Text, YMean, YVariance, YStdDev]);
lReport.Add('');
lReport.Add('Regression:');
lReport.Add(' Correlation: %8.3f', [R]);
lReport.Add(' Slope: %8.3f', [Slope]);
lReport.Add(' Intercept: %8.3f', [Intercept]);
lReport.Add(' Standard Error of Estimate: %8.3f', [StdErrorPredicted]);
lReport.Add(' Number of good cases: %8d', [Count]);
end;
FReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
end;
procedure TPlotXYForm.XInBtnClick(Sender: TObject);
var var
index: integer; index: integer;
begin begin
@ -386,7 +415,7 @@ begin
end; end;
procedure TPlotXYFrm.XOutBtnClick(Sender: TObject); procedure TPlotXYForm.XOutBtnClick(Sender: TObject);
begin begin
if XEdit.Text <> '' then if XEdit.Text <> '' then
begin begin
@ -397,7 +426,7 @@ begin
end; end;
procedure TPlotXYFrm.YInBtnClick(Sender: TObject); procedure TPlotXYForm.YInBtnClick(Sender: TObject);
var var
index: integer; index: integer;
begin begin
@ -411,7 +440,7 @@ begin
end; end;
procedure TPlotXYFrm.YOutBtnClick(Sender: TObject); procedure TPlotXYForm.YOutBtnClick(Sender: TObject);
begin begin
if YEdit.Text <> '' then if YEdit.Text <> '' then
begin begin

View File

@ -19,7 +19,7 @@ object OS3MainFrm: TOS3MainFrm
OnCreate = FormCreate OnCreate = FormCreate
OnDestroy = FormDestroy OnDestroy = FormDestroy
OnShow = FormShow OnShow = FormShow
LCLVersion = '2.0.10.0' LCLVersion = '2.1.0.0'
object Panel1: TPanel object Panel1: TPanel
Left = 0 Left = 0
Height = 35 Height = 35

View File

@ -2071,9 +2071,9 @@ end;
// Menu "Analysis" > "Descriptive" > "Plot X vs Y" // Menu "Analysis" > "Descriptive" > "Plot X vs Y"
procedure TOS3MainFrm.mnuAnalysisDescr_PlotXvsYClick(Sender: TObject); procedure TOS3MainFrm.mnuAnalysisDescr_PlotXvsYClick(Sender: TObject);
begin begin
if PlotXYFrm = nil then if PlotXYForm = nil then
Application.CreateForm(TPlotXYFrm, PlotXYFrm); Application.CreateForm(TPlotXYForm, PlotXYForm);
PlotXYFrm.Show; PlotXYForm.Show;
end; end;
procedure TOS3MainFrm.mnuAnalysisDescr_ResistanceLineClick(Sender: TObject); procedure TOS3MainFrm.mnuAnalysisDescr_ResistanceLineClick(Sender: TObject);