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>
<Filename Value="forms\analysis\descriptive\plotxyunit.pas"/>
<IsPartOfProject Value="True"/>
<ComponentName Value="PlotXYFrm"/>
<ComponentName Value="PlotXYForm"/>
<HasResources Value="True"/>
<ResourceBaseClass Value="Form"/>
<UnitName Value="PlotXYUnit"/>

View File

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

View File

@ -8,15 +8,15 @@ interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, ComCtrls,
MainUnit, Globals, FunctionsLib, BasicStatsReportAndChartFormUnit,
StdCtrls, ExtCtrls, Buttons, ComCtrls, Grids,
MainUnit, Globals, MathUnit, BasicStatsReportAndChartFormUnit,
ReportFrameUnit, ChartFrameUnit;
type
{ TPlotXYFrm }
{ TPlotXYForm }
TPlotXYFrm = class(TBasicStatsReportAndChartForm)
TPlotXYForm = class(TBasicStatsReportAndChartForm)
ConfEdit: TEdit;
Label4: TLabel;
LineChk: TCheckBox;
@ -41,15 +41,20 @@ type
procedure YOutBtnClick(Sender: TObject);
private
procedure PlotXY(XPoints, YPoints, UpConf, LowConf: DblDyneVec;
XMean, YMean, R, Slope, Intercept: Double);
procedure PlotXY(XPoints, YPoints: DblDyneVec;
const ARegressionResults: TBivariateRegressionResults);
function PrepareData(ADataGrid: TStringGrid;
out xCol, ycol: Integer; out XData, YData: DblDyneVec;
out ColNoSelected: IntDyneVec): Boolean;
procedure WriteToReport(ARegressionResults: TBivariateRegressionResults);
protected
procedure AdjustConstraints; override;
procedure Compute; override;
procedure UpdateBtnStates; override;
function Validate(out AMsg: String; out AControl: TWinControl;
Xcol,Ycol: Integer): Boolean; reintroduce;
function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override;
public
constructor Create(AOwner: TComponent); override;
@ -57,7 +62,8 @@ type
end;
var
PlotXYFrm: TPlotXYFrm;
PlotXYForm: TPlotXYForm;
implementation
@ -65,12 +71,12 @@ implementation
uses
TAChartUtils, TAChartAxisUtils, TALegend, TASources, TACustomSeries, TASeries,
MathUnit, GridProcs, Utils;
GridProcs, Utils;
{ TPlotXYFrm }
{ TPlotXYForm }
constructor TPlotXYFrm.Create(AOwner: TComponent);
constructor TPlotXYForm.Create(AOwner: TComponent);
begin
inherited;
@ -98,7 +104,7 @@ begin
end;
procedure TPlotXYFrm.AdjustConstraints;
procedure TPlotXYForm.AdjustConstraints;
begin
ParamsPanel.Constraints.MinHeight := OptionsGroup.Top + OptionsGroup.Height +
OptionsGroup.BorderSpacing.Bottom + ButtonBevel.Height +
@ -110,26 +116,19 @@ begin
end;
procedure TPlotXYFrm.Compute;
procedure TPlotXYForm.Compute;
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;
yValues: DblDyneVec = nil;
UpConf: DblDyneVec = nil;
lowConf: DblDyneVec = nil;
ColNoSelected: IntDyneVec= nil;
xCol, yCol: Integer;
confBand: Double;
regressionRes: TBivariateRegressionResults;
C: TWinControl;
msg: String;
lReport: TStrings;
begin
xCol := OS3MainFrm.DataGrid.Rows[0].IndexOf(XEdit.Text);
yCol := OS3MainFrm.DataGrid.Rows[0].IndexOf(YEdit.Text);
// Validation
if not Validate(msg, C, Xcol, Ycol) then
// Validation: Make sure that XEdit and YEdit are not empty
if not Validate(msg, C) then
begin
C.SetFocus;
ErrorMsg(msg);
@ -137,99 +136,36 @@ begin
exit;
end;
SetLength(ColNoSelected, 2);
ColNoSelected[0] := Xcol;
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');
// Extract data from Grid
if not PrepareData(OS3MainFrm.DataGrid, xCol, yCol, xValues, yValues, ColNoSelected) then
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);
Calc_MeanVarStddevSS(yValues, yMean, yVariance, yStdDev, SYY);
SXY := 0;
for i := 0 to N-1 do
SXY := SXY + xValues[i] * yValues[i];
// Sort on x values
SortOnX(xValues, yValues);
R := (SXY - xMean * yMean * N) / ((N - 1) * xStdDev * yStdDev);
sePred := sqrt(1.0 - sqr(R)) * yStdDev * sqrt((N - 1) / (N - 2));
slope := R * yStdDev / xStdDev;
intercept := yMean - slope * xMean;
// Calculate regression
confBand := StrToFloat(ConfEdit.Text) / 100.0;
Calc_BivariateRegression(xValues, yValues, confBand, regressionRes);
// Print the descriptive statistics to the output frame
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('---------- -------- -------- --------');
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;
WriteToReport(regressionRes);
// 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;
procedure TPlotXYFrm.PlotXY(XPoints, YPoints, UpConf, LowConf: DblDyneVec;
XMean, YMean, R, Slope, Intercept: Double);
procedure TPlotXYForm.PlotXY(XPoints, YPoints: DblDyneVec;
const ARegressionResults: TBivariateRegressionResults);
var
tmpX: array of Double = nil;
tmpY: array of Double = nil;
tmpX: DblDyneVec = nil;
tmpY: DblDyneVec = nil;
conf: DblDyneVec = nil;
xmin, xmax, ymin, ymax: Double;
rightLabels: TListChartSource;
topLabels: TListChartSource;
ser: TChartSeries;
i: Integer;
begin
rightLabels := FChartFrame.Chart.AxisList[2].Marks.Source as TListChartSource;
rightLabels.Clear;
@ -239,16 +175,20 @@ begin
// Titles
FChartFrame.SetTitle('X vs. Y plot using file ' + OS3MainFrm.FileNameEdit.Text);
FChartFrame.SetFooter(Format('R(X,Y) = %.3f, Slope = %.3f, Intercept = %.3f', [
R, Slope, Intercept
]));
with ARegressionResults do
FChartFrame.SetFooter(Format('R(X,Y) = %.3f, Slope = %.3f, Intercept = %.3f', [
R, Slope, Intercept
]));
FChartFrame.SetXTitle(XEdit.Text);
FChartFrame.SetYTitle(YEdit.Text);
// Draw upper confidence band
if ConfChk.Checked then
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');
end;
@ -258,7 +198,10 @@ begin
// Draw lower confidence band
if ConfChk.Checked then
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');
end;
@ -268,19 +211,26 @@ begin
// Draw means
if MeansChk.Checked then
begin
FChartFrame.VertLine(XMean, clGreen, psDashDot, 'Mean ' + XEdit.Text);
topLabels.Add(XMean, XMean, 'Mean ' + XEdit.Text);
FChartFrame.HorLine(YMean, clGreen, psDash, 'Mean ' + YEdit.Text);
rightLabels.Add(YMean, YMean, 'Mean ' + YEdit.Text);
end;
with ARegressionResults do
begin
FChartFrame.VertLine(XMean, clGreen, psDashDot, 'Mean ' + XEdit.Text);
topLabels.Add(XMean, XMean, 'Mean ' + XEdit.Text);
FChartFrame.HorLine(YMean, clGreen, psDash, 'Mean ' + YEdit.Text);
rightLabels.Add(YMean, YMean, 'Mean ' + YEdit.Text);
end;
// Draw regression line
if LineChk.Checked then
begin
SetLength(tmpX, 2); SetLength(tmpY, 2);
tmpX[0] := xmin; tmpY[0] := tmpX[0] * slope + intercept;
tmpX[1] := xmax; tmpY[1] := tmpX[1] * slope + intercept;
SetLength(tmpX, 2);
SetLength(tmpY, 2);
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);
rightLabels.Add(tmpY[1], tmpY[1], 'Predicted');
end;
@ -289,67 +239,110 @@ begin
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
i: integer;
begin
inherited;
XEdit.Text := '';
YEdit.Text := '';
ConfEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT);
LineChk.Checked := false;
MeansChk.Checked := false;
ConfChk.Checked := false;
VarList.Items.Clear;
for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
if Assigned(FChartFrame) then
FChartFrame.Clear;
if Assigned(FReportFrame) then
FReportFrame.Clear;
UpdateBtnStates;
end;
procedure TPlotXYFrm.UpdateBtnStates;
procedure TPlotXYForm.UpdateBtnStates;
begin
inherited;
XInBtn.Enabled := (VarList.ItemIndex > -1) and (XEdit.Text = '');
XoutBtn.Enabled := (XEdit.Text <> '');
YinBtn.Enabled := (VarList.ItemIndex > -1) and (YEdit.Text = '');
YoutBtn.Enabled := (YEdit.Text <> '');
if Assigned(FReportFrame) then
FReportFrame.UpdateBtnStates;
if Assigned(FChartFrame) then
FChartFrame.UpdateBtnStates;
end;
function TPlotXYFrm.Validate(out AMsg: String; out AControl: TWinControl;
Xcol, Ycol: Integer): Boolean;
function TPlotXYForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean;
begin
Result := false;
if (Xcol < 0) then
if XEdit.Text = '' then
begin
AControl := XEdit;
AMsg := 'No case selected for X.';
AMsg := 'No variable selected for X.';
exit;
end;
if (Ycol < 0) then
if YEdit.Text = '' then
begin
AControl := YEdit;
AMsg := 'No case selected for Y.';
AMsg := 'No variable selected for Y.';
exit;
end;
Result := true;
end;
procedure TPlotXYFrm.VarListDblClick(Sender: TObject);
procedure TPlotXYForm.VarListDblClick(Sender: TObject);
var
index: integer;
begin
@ -366,13 +359,49 @@ begin
end;
procedure TPlotXYFrm.VarListSelectionChange(Sender: TObject; User: boolean);
procedure TPlotXYForm.VarListSelectionChange(Sender: TObject; User: boolean);
begin
UpdateBtnStates;
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
index: integer;
begin
@ -386,7 +415,7 @@ begin
end;
procedure TPlotXYFrm.XOutBtnClick(Sender: TObject);
procedure TPlotXYForm.XOutBtnClick(Sender: TObject);
begin
if XEdit.Text <> '' then
begin
@ -397,7 +426,7 @@ begin
end;
procedure TPlotXYFrm.YInBtnClick(Sender: TObject);
procedure TPlotXYForm.YInBtnClick(Sender: TObject);
var
index: integer;
begin
@ -411,7 +440,7 @@ begin
end;
procedure TPlotXYFrm.YOutBtnClick(Sender: TObject);
procedure TPlotXYForm.YOutBtnClick(Sender: TObject);
begin
if YEdit.Text <> '' then
begin

View File

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

View File

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