Files
lazarus-ccr/applications/lazstats/source/forms/analysis/descriptive/descriptiveunit.pas

776 lines
22 KiB
ObjectPascal
Raw Normal View History

unit DescriptiveUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, Spin, ComCtrls, Grids,
MainUnit, Globals, FunctionsLib, ReportFrameUnit, BasicStatsReportFormUnit,
DataProcs, DictionaryUnit;
type
{ TDescriptiveStats }
TDescriptiveOption = (doAlternativeQuartiles, doPercentileRanks, doCasewiseDeletion);
TDescriptiveOptions = set of TDescriptiveOption;
TQuartileMethod = 1..8;
TQuartile = 1..3;
TDescriptiveStats = class
private
FDataGrid: TStringGrid;
FColIndex: Integer;
FConfLevel: Double; // usually 0.95
FColsSelected: IntDyneVec;
FMean, FStdErrorMean, FDeltaMean: Double;
FMin, FMax: Double;
FSum: Double;
FVariance, FStdDev: Double;
FSkew, FStdErrorSkew: Double;
FKurtosis, FStdErrorKurtosis: Double;
FFirstQuartile, FMedian, FThirdQuartile: Double;
FCategoryValues, FPercentiles: DblDyneVec;
FFreqValues: IntDyneVec;
FOptions: TDescriptiveOptions;
FNumCases: Integer;
FQuartiles: array[TQuartileMethod, TQuartile] of Double;
procedure Calc_AlternativeQuartiles(const AValues: DblDyneVec);
function Calc_DeltaMean(AStdErrorOfMean: Double): Double;
procedure Calc_Moments(const AValues: DblDyneVec; AMean: Double;
out M2, M3, M4: Double);
procedure Calc_Quartiles(const AValues: DblDyneVec; out Q1, Median, Q3: Double);
procedure Calc_Skew_Kurtosis(StdDev, M2, M3, M4: Double; ANumCases: Integer;
out Skew, StdErrorSkew, Kurtosis, StdErrorKurtosis: Double);
procedure Calc_Sum_SumOfSquares_Min_Max(const AValues: DblDyneVec;
out ASum, ASumOfSquares, AMin, AMax: Double);
procedure CollectValues(out AValues: DblDyneVec);
procedure PercentileRank(const AValues: DblDyneVec;
out ACategoryValues, APercentiles: DblDyneVec; out AFreq: IntDyneVec);
public
constructor Create(ADataGrid: TStringGrid; AColsSelected: IntDyneVec; AConfLevel: Double);
procedure Analyze(AColIndex: Integer; AOptions: TDescriptiveOptions);
procedure WriteToReport(AVarName: String; ADecPlaces: Integer; AReport: TStrings);
property Mean: Double read FMean;
property StdDev: Double read FStdDev;
// more can be added...
end;
{ TDescriptiveFrm }
TDescriptiveFrm = class(TBasicStatsReportForm)
CaseChk: TCheckBox;
DecPlacesEdit: TSpinEdit;
Label4: TLabel;
PageControl: TPageControl;
ReportPage: TTabSheet;
ZScoresToGridChk: TCheckBox;
AllQuartilesChk: TCheckBox;
Label2: TLabel;
Label3: TLabel;
PercentileChk: TCheckBox;
OptionsGroup: TGroupBox;
InBtn: TBitBtn;
OutBtn: TBitBtn;
AllBtn: TBitBtn;
CIEdit: TEdit;
Label1: TLabel;
VarList: TListBox;
SelList: TListBox;
procedure AllBtnClick(Sender: TObject);
procedure InBtnClick(Sender: TObject);
procedure OutBtnClick(Sender: TObject);
procedure SelListDblClick(Sender: TObject);
procedure VarListDblClick(Sender: TObject);
procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean);
private
function GetReportFrame(APageIndex: Integer): TReportFrame;
procedure zScoresToGrid(AColIndex: Integer; const AColsSelected: IntDyneVec;
AMean, AStdDev: Double);
protected
procedure AdjustConstraints; override;
procedure Compute; override;
procedure UpdateBtnStates; override;
public
constructor Create(AOwner: TComponent); override;
procedure Reset; override;
end;
var
DescriptiveFrm: TDescriptiveFrm;
implementation
{$R *.lfm}
uses
Math,
Utils;
{===============================================================================
* TDescriptiveStats
*-------------------------------------------------------------------------------
* TDescriptiveStats is a helper class which
* - does all the required calculations (Analyze) and
* - prepares the report for each variable (WriteToReport).
*==============================================================================}
constructor TDescriptiveStats.Create(ADataGrid: TStringGrid;
AColsSelected: IntDyneVec; AConfLevel: Double);
begin
inherited Create;
FDataGrid := ADataGrid;
FColsSelected := AColsSelected;
FConfLevel := AConfLevel;
end;
procedure TDescriptiveStats.Analyze(AColIndex: Integer; AOptions: TDescriptiveOptions);
var
SS: Double;
values: DblDyneVec;
M2, M3, M4: Double;
begin
FMean := NaN;
FVariance := NaN;
FStdDev := NaN;
FStdErrorMean := NaN;
FDeltaMean := NaN;
FSkew := NaN;
FStdErrorSkew := NaN;
FColIndex := AColIndex;
FOptions := AOptions;
CollectValues(values);
FNumCases := Length(values);
SortOnX(values);
Calc_Sum_SumOfSquares_Min_Max(values, FSum, SS, FMin, FMax);
if FNumCases > 0 then begin
FMean := FSum / FNumCases;
if FNumCases > 1 then
begin
FVariance := (SS - sqr(FSum) / FNumCases) / (FNumCases - 1);
FStdDev := sqrt(FVariance);
FStdErrorMean := sqrt(FVariance / FNumCases);
FDeltaMean := Calc_DeltaMean(FStdErrorMean);
end;
Calc_Moments(values, FMean, M2, M3, M4);
Calc_Skew_Kurtosis(FStdDev, M2, M3, M4, FNumCases, FSkew, FStdErrorSkew, FKurtosis, FStdErrorKurtosis);
Calc_Quartiles(values, FFirstQuartile, FMedian, FThirdQuartile);
if (doAlternativeQuartiles in FOptions) then
Calc_AlternativeQuartiles(values);
if (doPercentileRanks in FOptions) then
PercentileRank(values, FCategoryValues, FPercentiles, FFreqValues);
end;
end;
procedure TDescriptiveStats.Calc_AlternativeQuartiles(const AValues: DblDyneVec);
var
nCases: Integer;
q: TQuartile;
m: TQuartileMethod;
begin
nCases := Length(AValues);
for m := Low(TQuartileMethod) to High(TQuartileMethod) do
for q := Low(TQuartile) to High(TQuartile) do
FQuartiles[m, q] := Quartiles(m, 0.25*q, nCases, AValues);
end;
// Tolerance around the mean
function TDescriptiveStats.Calc_DeltaMean(AStdErrorOfMean: Double): Double;
var
alpha: Double;
confLev: Double;
DOF: Integer;
begin
alpha := (1 - FConfLevel) / 2;
confLev := 1 - alpha;
if FNumCases < 120 then
begin
DOF := FNumCases - 1;
Result := AStdErrorOfMean * InverseT(confLev, DOF);
end else
Result := AStdErrorOfMean * InverseZ(confLev);
end;
procedure TDescriptiveStats.Calc_Moments(const AValues: DblDyneVec;
AMean: Double; out M2, M3, M4: Double);
var
i: Integer;
dev, devSqr: Double;
begin
M2 := 0;
M3 := 0;
M4 := 0;
for i := 0 to High(AValues) do
begin
dev := AValues[i] - AMean;
devSqr := Sqr(dev);
M2 := M2 + devSqr;
M3 := M3 + dev * devSqr;
M4 := M4 + sqr(devSqr);
end;
end;
procedure TDescriptiveStats.Calc_Quartiles(const AValues: DblDyneVec;
out Q1, Median, Q3: Double);
var
n: Integer;
begin
n := Length(AValues);
Q1 := Quartiles(2, 0.25, n, AValues);
Median := Quartiles(2, 0.5, n, AValues);
Q3 := Quartiles(2, 0.75, n, AValues);
end;
procedure TDescriptiveStats.Calc_Skew_Kurtosis(StdDev, M2, M3, M4: Double;
ANumCases: Integer; out Skew, StdErrorSkew, Kurtosis, StdErrorKurtosis: Double);
var
num, denom: Double;
stdDev3, stdDev4: Double;
begin
Skew := NaN;
StdErrorSkew := NaN;
Kurtosis := NaN;
StdErrorKurtosis := NaN;
stdDev3 := StdDev * StdDev * StdDev;
stdDev4 := StdDev3 * StdDev;
if ANumCases > 2 then
begin
Skew := ANumCases * M3 / ((ANumCases - 1) * (ANumCases - 3) * stdDev3);
num := 6.0 * ANumCases * (ANumCases - 1);
denom := (ANumCases - 2) * (ANumCases + 1) * (ANumCases + 3);
StdErrorSkew := sqrt(num / denom);
end;
if ANumCases > 3 then
begin
num := ANumCases * (ANumCases + 1) * M4 - 3 * M2 * M2 * (ANumCases - 1);
denom := (ANumCases - 1) * (ANumCases - 2) * (ANumCases - 3) * stdDev4;
Kurtosis := num / denom;
num := 4.0 * (sqr(ANumCases) - 1) * sqr(StdErrorSkew);
denom := (ANumCases - 3) * (ANumCases + 5);
StdErrorKurtosis := sqrt(num / denom);
end;
end;
procedure TDescriptiveStats.Calc_Sum_SumOfSquares_Min_Max(const AValues: DblDyneVec;
out ASum, ASumOfSquares, AMin, AMax: Double);
var
i: Integer;
begin
ASum := 0.0;
ASumOfSquares := 0;
AMin := Infinity;
AMax := -Infinity;
for i := 0 to High(AValues) do
begin
ASum := ASum + AValues[i];
ASumOfSquares := ASumOfSquares + sqr(AValues[i]);
if AValues[i] < AMin then AMin := AValues[i];
if AValues[i] > AMax then AMax := AValues[i];
end;
end;
procedure TDescriptiveStats.CollectValues(out AValues: DblDyneVec);
var
i, n: Integer;
begin
AValues := nil; // silence the compiler
SetLength(AValues, NoCases);
n := 0;
for i := 1 to NoCases do
begin
if (doCasewiseDeletion in FOptions) then
begin
// Do not consider a case when any variable is empty
if not ValidValue(i, FColIndex) then
continue;
end else
begin
// Do not consider a case when the current variable is empty
if not GoodRecord(i, Length(FColsSelected), FColsSelected) then
continue;
end;
if TryStrToFloat(FDataGrid.Cells[FColIndex, i], AValues[n]) then
inc(n)
else
raise Exception.CreateFmt('Invalid number: variable "%s", case "%s"',
[FDataGrid.cells[FColIndex, 0], FDataGrid.Cells[0, i]]);
end;
SetLength(AValues, n);
end;
// Computes the percentile ranks of values stored in the data grid at the
// loaded columns. The values are assumed to be sorted.
procedure TDescriptiveStats.PercentileRank(const AValues: DblDyneVec; out
ACategoryValues, APercentiles: DblDyneVec; out AFreq: IntDyneVec);
var
i, nCases, iCat, nCategories: Integer;
lastCategoryValue: Double;
cumFreqCentered: Double;
cumFreq: Integer;
begin
// silence the compiler
ACategoryvalues := nil;
AFreq := nil;
APercentiles := nil;
nCases := Length(AValues);
SetLength(ACategoryValues, nCases); // over-dimension; will be trimmed later
SetLength(AFreq, nCases);
// Get count of unique values and frequencies of each
lastCategoryValue := AValues[0];
ACategoryValues[0] := lastCategoryValue;
AFreq[0] := 1;
iCat := 0;
for i := 1 to nCases-1 do
begin
if (lastCategoryValue = AValues[i]) then
AFreq[iCat] := AFreq[iCat] + 1
else
begin // new value
inc(iCat);
AFreq[iCat] := 1;
lastCategoryValue := AValues[i];
ACategoryValues[iCat] := lastCategoryValue;
end;
end;
// trim arrays
nCategories := iCat + 1;
SetLength(ACategoryValues, nCategories);
SetLength(AFreq, nCategories);
// Get cumulative frequencies and percentile ranks
SetLength(APercentiles, nCategories);
APercentiles[0] := AFreq[0] * 0.5 / nCases;
cumFreq := AFreq[0];
for i := 1 to nCategories-1 do // NOTE: This loop must begin at index 1
begin
cumFreqCentered := cumFreq + AFreq[i]*0.5; // cum frequencies at mid-point
APercentiles[i] := cumFreqCentered / nCases;
cumFreq := cumFreq + AFreq[i];
end;
end;
procedure TDescriptiveStats.WriteToReport(AVarName: String; ADecPlaces: Integer;
AReport: TStrings);
var
w: Integer;
nCategories: Integer;
i: Integer;
cumFreq: Integer;
m: TQuartileMethod;
begin
w := 10 + ADecPlaces - 3;
AReport.Add('VARIABLE: %*s', [W, '"' + AVarName + '"']);
AReport.Add('');
AReport.Add('Number of cases: %*d', [W, FNumCases]);
AReport.Add('Sum: %*.*f', [W, ADecPlaces, FSum]);
AReport.Add('Mean: %*.*f', [W, ADecPlaces, FMean]);
AReport.Add('Variance: %*.*f', [W, ADecPlaces, FVariance]);
AReport.Add('Std.Dev.: %*.*f', [W, ADecPlaces, FStdDev]);
AReport.Add('Std.Error of Mean %*.*f', [W, ADecPlaces, FStdErrorMean]);
AReport.Add('%.2f%% Conf.Interval Mean: %.*f to %.*f', [
FConfLevel*100.0, ADecPlaces, FMean - FDeltaMean, ADecPlaces, FMean + FDeltaMean]);
AReport.Add('');
AReport.Add('Minimum: %*.*f', [W, ADecPlaces, FMin]);
AReport.Add('Maximum: %*.*f', [W, ADecPlaces, FMax]);
AReport.Add('Range: %*.*f', [W, ADecPlaces, FMax - FMin]);
AReport.Add('');
AReport.Add('Skewness: %*.*f', [W, ADecPlaces, FSkew]);
AReport.Add('Std.Error of Skew: %*.*f', [W, ADecPlaces, FStdErrorSkew]);
AReport.Add('Kurtosis: %*.*f', [W, ADecPlaces, FKurtosis]);
AReport.Add('Std. Error of Kurtosis: %*.*f', [W, ADecPlaces, FStdErrorKurtosis]);
AReport.Add('');
AReport.Add('First Quartile: %*.*f', [W, ADecPlaces, FFirstQuartile]);
AReport.Add('Median: %*.*f', [W, ADecPlaces, FMedian]);
AReport.Add('Third Quartile: %*.*f', [W, ADecPlaces, FThirdQuartile]);
AReport.Add('Interquartile range: %*.*f', [W, ADecPlaces, FThirdQuartile - FFirstQuartile]);
if (doAlternativeQuartiles in FOptions) then
begin
AReport.Add('');
AReport.Add('');
AReport.Add('ALTERNATIVE METHODS FOR OBTAINING QUARTILES');
AReport.Add('');
AReport.Add('Method First Quartile Median Third Quartile');
AReport.Add('------ -------------- ---------- --------------');
for m := Low(TQuartileMethod) to High(TQuartileMethod) do
AReport.Add(' %d %12.3f %12.3f %12.3f', [m, FQuartiles[m, 1], FQuartiles[m, 2], FQuartiles[m, 3]]);
AReport.Add('');
AReport.Add('NOTES:');
AReport.Add('Method 1 is the weighted average at X[np] where ');
AReport.Add(' n is no. of cases, p is percentile / 100');
AReport.Add('Method 2 is the weighted average at X[(n+1)p] This is used in this program.');
AReport.Add('Method 3 is the empirical distribution function.');
AReport.Add('Method 4 is called the empirical distribution function - averaging.');
AReport.Add('Method 5 is called the empirical distribution function = Interpolation.');
AReport.Add('Method 6 is the closest observation method.');
AReport.Add('Method 7 is from the TrueBasic Statistics Graphics Toolkit.');
AReport.Add('Method 8 was used in an older Microsoft Excel version.');
AReport.Add('See the internet site http://www.xycoon.com/ for the above.');
end;
if (doPercentileRanks in FOptions) then
begin
nCategories := Length(FCategoryValues);
cumFreq := 0;
AReport.Add('');
AReport.Add('');
AReport.Add('PERCENTILE RANKS');
AReport.Add('');
AReport.Add('Score Value Frequency Cum.Freq. Percentile Rank');
AReport.Add('----------- --------- --------- ---------------');
for i := 0 to nCategories-1 do
begin
cumFreq := cumFreq + FFreqValues[i];
AReport.Add(' %8.3f %8d %8d %12.2f%%', [
FCategoryValues[i], FFreqValues[i], cumFreq, FPercentiles[i]*100.0
]);
end;
end;
end;
{ TDescriptiveFrm }
constructor TDescriptiveFrm.Create(AOwner: TComponent);
begin
inherited;
FReportFrame.Parent := ReportPage;
FReportFrame.BorderSpacing.Left := 0;
FReportFrame.BorderSpacing.Top := 0;
FReportFrame.BorderSpacing.Bottom := 0;
FReportFrame.BorderSpacing.Right := 0;
end;
procedure TDescriptiveFrm.AdjustConstraints;
begin
ParamsPanel.Constraints.MinHeight := AllBtn.Top + AllBtn.Height + OptionsGroup.Height +
CIEdit.Height + ButtonBevel.Height + CloseBtn.Height + VarList.BorderSpacing.Bottom +
OptionsGroup.BorderSpacing.Bottom + CloseBtn.BorderSpacing.Top +
DecPlacesEdit.Height + DecPlacesEdit.BorderSpacing.Top;
ParamsPanel.Constraints.MinWidth := Math.Max(
4*CloseBtn.Width + 3*HelpBtn.BorderSpacing.Right,
OptionsGroup.Width
);
end;
procedure TDescriptiveFrm.AllBtnClick(Sender: TObject);
var
i : integer;
begin
for i := 0 to VarList.Items.Count-1 do
SelList.Items.Add(VarList.Items.Strings[i]);
VarList.Clear;
UpdateBtnStates;
end;
procedure TDescriptiveFrm.Compute;
var
cellString: String;
i, j: Integer;
noSelected: Integer;
selected: IntDyneVec = nil;
page: TTabSheet;
reportFrame: TReportFrame;
lReport: TStrings;
lDescrStats: TDescriptiveStats;
options: TDescriptiveOptions;
begin
noSelected := SelList.Items.Count;
if noSelected = 0 then
begin
MessageDlg('No variables selected.', mtError, [mbOK], 0);
exit;
end;
SetLength(selected, noSelected);
// Find column index of selected variables
for i := 0 to noSelected - 1 do
begin
cellstring := SelList.Items[i];
for j := 1 to NoVariables do
if cellstring = OS3MainFrm.DataGrid.Cells[j,0] then selected[i] := j;
end;
// Create a tabsheet with ReportFrame for each selected variable (in addition to the built-in one)
if noSelected > PageControl.PageCount then
begin
for i := 1 to noSelected-1 do // we do not create a tab for the first variable - it exists by default
begin
page := TTabSheet.Create(PageControl);
page.Parent := PageControl;
reportFrame := TReportFrame.Create(page);
reportFrame.Parent := page;
reportFrame.Align := alClient;
InitToolBar(reportFrame.ReportToolbar, tpRight);
end;
end;
// Remove excess pages from previous session
while PageControl.PageCount > noSelected do
PageControl.Pages[PageControl.PageCount-1].Free;
// Every tab gets the name of the corresponding variable.
for i := 0 to NoSelected-1 do
PageControl.Pages[i].Caption := OS3MainFrm.DataGrid.Cells[selected[i], 0];
// Prepare options
options := [];
if PercentileChk.Checked then Include(options, doPercentileRanks);
if AllQuartilesChk.Checked then Include(options, doAlternativeQuartiles);
if CaseChk.Checked then Include(options, doCasewiseDeletion);
lReport := TStringList.Create;
lDescrStats := TDescriptiveStats.Create(OS3MainFrm.DataGrid, selected, StrToFloat(CIEdit.Text)/100);
try
for i := 0 to noSelected-1 do
begin
// Analyze the data and get descriptive stats
lDescrStats.Analyze(selected[i], options);
// Store z values, (value - mean) / stdDev, to grid, if needed
zScoresToGrid(selected[i], selected, lDescrStats.Mean, lDescrStats.StdDev);
// Write descriptive stats to report
lReport.Clear;
lReport.Add('DISTRIBUTION PARAMETER ESTIMATES');
lReport.Add('');
lDescrStats.WriteToReport(trim(OS3MainFrm.DataGrid.Cells[selected[i], 0]),
DecPlacesEdit.Value, lReport);
// Display report in the page of the variable
reportFrame := GetReportFrame(i);
reportFrame.DisplayReport(lReport);
end;
finally
// Clean up
lDescrStats.Free;
lReport.Free;
end;
end;
function TDescriptiveFrm.GetReportFrame(APageIndex: Integer): TReportFrame;
var
page: TTabSheet;
begin
Result := nil;
if (APageIndex >=0) and (APageIndex < PageControl.PageCount) then
begin
page := PageControl.Pages[APageIndex];
if (page.ControlCount > 0) and (page.Controls[0] is TReportFrame) then
Result := TReportFrame(page.Controls[0]);
end;
end;
procedure TDescriptiveFrm.InBtnClick(Sender: TObject);
var
i: integer;
begin
i := 0;
while i < VarList.Items.Count do
begin
if VarList.Selected[i] then
begin
SelList.Items.Add(VarList.Items[i]);
VarList.Items.Delete(i);
i := 0;
end else
inc(i);
end;
UpdateBtnStates;
end;
procedure TDescriptiveFrm.OutBtnClick(Sender: TObject);
var
i: integer;
begin
i := 0;
while i < SelList.Items.Count do
begin
if SelList.Selected[i] then
begin
VarList.Items.Add(SelList.Items[i]);
SelList.Items.Delete(i);
i := 0;
end else
inc(i);
end;
UpdateBtnStates;
end;
procedure TDescriptiveFrm.Reset;
var
i: integer;
begin
inherited;
for i := PageControl.PageCount-1 downto 1 do
PageControl.Pages[i].Free;
PageControl.Pages[0].Caption := 'Report';
CIEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT);
VarList.Clear;
SelList.Clear;
for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
UpdateBtnStates;
end;
procedure TDescriptiveFrm.SelListDblClick(Sender: TObject);
var
index: integer;
begin
index := SelList.ItemIndex;
if index > -1 then
begin
VarList.Items.Add(SelList.Items[index]);
SelList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TDescriptiveFrm.VarListDblClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if index > -1 then
begin
SelList.Items.Add(VarList.Items[index]);
VarList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TDescriptiveFrm.UpdateBtnStates;
var
lSelected: Boolean;
i: Integer;
F: TReportFrame;
begin
inherited;
for i := 0 to PageControl.PageCount-1 do
begin
F := GetReportFrame(i);
if Assigned(F) then F.Clear;
end;
lSelected := false;
for i := 0 to VarList.Items.Count-1 do
if VarList.Selected[i] then
begin
lSelected := true;
break;
end;
InBtn.Enabled := lSelected;
lSelected := false;
for i := 0 to SelList.Items.Count-1 do
if SelList.Selected[i] then
begin
lSelected := true;
break;
end;
OutBtn.Enabled := lSelected;
AllBtn.Enabled := VarList.Count > 0;
end;
procedure TDescriptiveFrm.VarListSelectionChange(Sender: TObject; User: boolean);
begin
UpdateBtnStates;
end;
procedure TDescriptiveFrm.zScoresToGrid(AColIndex: Integer;
const AColsSelected: IntDyneVec; AMean, AStdDev: Double);
var
i, idx: Integer;
value, zValue: Double;
varName: String;
begin
if AStdDev = 0 then begin
ErrorMsg('Cannot store z values to grid because StdDev is zero.');
exit;
end;
varName := OS3MainFrm.DataGrid.Cells[AColIndex, 0] + '_z';
idx := OS3MainFrm.DataGrid.Rows[0].IndexOf(varName);
if idx = -1 then
begin
DictionaryFrm.NewVar(NoVariables + 1);
DictionaryFrm.DictGrid.Cells[1, NoVariables] := varName;
OS3MainFrm.DataGrid.Cells[NoVariables, 0] := varName;
idx := NoVariables;
end;
for i := 1 to NoCases do
begin
if CaseChk.Checked then
begin
if not ValidValue(i, AColsSelected[AColIndex]) then continue;
end
else
if not GoodRecord(i, Length(AColsSelected), AColsSelected) then continue;
value := StrToFloat(OS3MainFrm.DataGrid.Cells[AColIndex, i]);
zValue := (value - AMean) / AStdDev;
OS3MainFrm.DataGrid.Cells[idx, i] := Format('%8.5f', [zValue]);
// to do: read number of decimal places from Dictionary and use in Format().
end;
end;
end.