Files
lazarus-ccr/applications/lazstats/source/forms/analysis/nonparametric/binomialunit.pas
2020-10-27 18:06:08 +00:00

216 lines
5.2 KiB
ObjectPascal

unit BinomialUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
FunctionsLib, GraphLib, BasicStatsReportAndChartFormUnit;
type
{ TBinomialForm }
TBinomialForm = class(TBasicStatsReportAndChartForm)
FreqAEdit: TEdit;
FreqBEdit: TEdit;
PropAEdit: TEdit;
FreqALabel: TLabel;
FreqBLabel: TLabel;
PropALabel: TLabel;
private
protected
procedure AdjustConstraints; override;
procedure Compute; override;
function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override;
public
constructor Create(AOwner: TComponent); override;
procedure Reset; override;
end;
var
BinomialForm: TBinomialForm;
implementation
{$R *.lfm}
uses
Math,
TACustomSeries,
Globals, MathUnit, ChartFrameUnit;
{ TBinomialForm }
constructor TBinomialForm.Create(AOwner: TComponent);
begin
inherited;
FChartFrame.Chart.Margins.Bottom := 0;
FChartFrame.Chart.BottomAxis.AxisPen.Visible := true;
FChartFrame.Chart.BottomAxis.ZPosition := 1;
end;
procedure TBinomialForm.AdjustConstraints;
begin
inherited;
ParamsPanel.Constraints.MinWidth := Max(
PropALabel.Width + PropAEdit.Width + PropAEdit.BorderSpacing.Left,
4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left
);
ParamsPanel.Constraints.MinHeight := PropAEdit.Top + PropAEdit.Height
end;
procedure TBinomialForm.Reset;
begin
inherited;
FreqAEdit.Clear;
FreqBEdit.Clear;
PropAEdit.Clear;
end;
procedure TBinomialForm.Compute;
const
N_MAX = 35;
var
P, Q, probability, z, correctedA, SumProb, mean, sigma: double;
A, B, N, i: integer;
xPts: DblDyneVec = nil;
yPts: DblDyneVec = nil;
lReport: TStrings;
ser: TChartSeries;
begin
A := round(StrToFloat(FreqAEdit.Text));
B := round(StrToFloat(FreqBEdit.Text));
P := StrToFloat(PropAEdit.Text);
N := A + B;
Q := 1.0 - P;
lReport := TStringList.Create;
try
lReport.Add('BINOMIAL PROBABILITY TEST');
lReport.Add('');
lReport.Add('Frequency of %d out of %d observed', [A, N]);
lReport.Add('The theoretical proportion expected in category A is %.3f', [P]);
lReport.Add('');
lReport.Add('The test is for the probability of a value in category A as small or smaller');
lReport.Add('than that observed given the expected proportion.');
lReport.Add('');
if (N > N_MAX) then
begin
// Use normal distribution approximation
correctedA := A;
if A < N * P then correctedA := A + 0.5;
if A > N * P then correctedA := A - 0.5;
z := (correctedA - N * P) / sqrt(N * P * Q);
lReport.Add('Z value for normal nistribution approximation: %.3f', [z]);
Probability := NormalDist(z);
lReport.Add('Probability: %6.4f', [Probability]);
end else
begin
// Use binomial fomula
sumProb := 0;
for i := 0 to A do
begin
Probability := combos(i, N) * IntPower(P, i) * IntPower(Q, N-i);
lReport.Add('Probability of %d: %6.4f', [i, Probability]);
sumProb := sumProb + Probability;
end;
lReport.Add('');
lReport.Add('Binomial Probability of %d or less out of %d: %.4f', [A, N, sumProb]);
end;
FReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
// Plot the distribution
FChartFrame.Clear;
FChartFrame.SetXTitle('Values');
FChartFrame.SetYTitle('Probability');
if N > N_MAX then
begin
FChartFrame.SetTitle('Binomial Distribution' + LineEnding + '(approximated by Normal Distribution)');
mean := N*P;
sigma := sqrt(N * P * Q);
SetLength(xPts, N+1);
SetLength(yPts, N+1);
for i := 0 to N do
begin
xPts[i] := i;
yPts[i] := NormalDistDensity(i, mean, sigma);
end;
end else
begin
FChartFrame.SetTitle('Binomial Distribution');
SetLength(xPts, N+1);
SetLength(yPts, N+1);
for i := 0 to N do
begin
xPts[i] := i;
yPts[i] := Combos(i, N) * IntPower(p, i) * IntPower(Q, N-i);
end;
end;
if N < 50 then
ser := FChartFrame.PlotXY(ptBars, xPts, yPts, nil, nil, '', DATA_COLORS[0])
else
ser := FChartFrame.PlotXY(ptArea, xPts, yPts, nil, nil, '', DATA_COLORS[0]);
ser.ZPosition := 2;
FChartFrame.Chart.Legend.Visible := false;
end;
function TBinomialForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean;
var
x: Double;
n: Integer;
begin
Result := false;
if (FreqAEdit.Text = '') or (FreqBEdit.Text = '') or (PropAEdit.Text = '') then
begin
AMsg := 'Value not specified.';
if FreqAEdit.Text = '' then AControl := FreqAEdit;
if FreqBEdit.Text = '' then AControl := FreqBEdit;
if PropAEdit.Text = '' then AControl := PropAEdit;
exit;
end;
if not TryStrToInt(FreqAEdit.Text, n) then
begin
AMsg := 'No valid integer.';
AControl := FreqAEdit;
exit;
end;
if not TryStrToInt(FreqBEdit.Text, n) then
begin
AMsg := 'No valid integer.';
AControl := FreqBEdit;
exit;
end;
if not TryStrToFloat(PropAEdit.Text, x) then
begin
AMsg := 'No valid number.';
AControl := PropAEdit;
exit;
end;
if (x < 0) or (x > 1) then
begin
AMsg := 'Number between 0 and 1 expected.';
AControl := PropAEdit;
exit;
end;
Result := true;
end;
end.