diff --git a/applications/lazstats/source/LazStats.lpi b/applications/lazstats/source/LazStats.lpi index f262e789d..f6570482f 100644 --- a/applications/lazstats/source/LazStats.lpi +++ b/applications/lazstats/source/LazStats.lpi @@ -122,7 +122,7 @@ - + @@ -1534,6 +1534,11 @@ + + + + + diff --git a/applications/lazstats/source/LazStats.lpr b/applications/lazstats/source/LazStats.lpr index f02b58669..42339e2dc 100644 --- a/applications/lazstats/source/LazStats.lpr +++ b/applications/lazstats/source/LazStats.lpr @@ -8,7 +8,7 @@ uses {$ENDIF}{$ENDIF} Interfaces, // this includes the LCL widgetset Forms, tachartlazaruspkg, tachartprint, lhelpcontrolpkg, Globals, LicenseUnit, - OptionsUnit, MainDM, MainUnit; + OptionsUnit, MainDM, MainUnit, GridProcs; {$R LazStats.res} diff --git a/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas b/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas index 4b3a686f0..59038ecf3 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas +++ b/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas @@ -65,7 +65,7 @@ implementation uses Math, Grids, - Utils, DictionaryUnit; + Utils, DictionaryUnit, GridProcs; { TCrossTabCalculator } @@ -232,13 +232,13 @@ begin for i := 1 to no_in_list do begin j := FVarList[i-1]; - if not GoodRecord(FDataGrid, 1, NoSelected, FColNoSelected) then continue; + if not GoodRecord(FDataGrid, 1, FColNoSelected) then continue; value := StrToFloat(FDataGrid.Cells[j, 1]); min_value[i-1] := round(value); max_value[i-1] := round(value); for k := 2 to NoCases do begin - if not GoodRecord(FDataGrid, k, NoSelected, FColNoSelected) then continue; + if not GoodRecord(FDataGrid, k, FColNoSelected) then continue; value := StrToFloat(FDataGrid.Cells[j, k]); if value < min_value[i-1] then min_value[i-1] := round(value); @@ -292,11 +292,11 @@ begin freq[i] := 0; for i := 1 to NoCases do begin - if IsFiltered(i) then + if IsFiltered(FDataGrid, i) then continue; for j := 1 to no_in_list do begin - if not GoodRecord(i, NoSelected, FColNoSelected) then continue; + if not GoodRecord(FDataGrid, i, FColNoSelected) then continue; k := FVarList[j-1]; value := StrToFloat(FDataGrid.Cells[k,i]); x := round(value); @@ -453,6 +453,9 @@ var noSelected: Integer; cellValue: String; begin + AVarList := nil; // Silence the compiler + AColNoSelected := nil; + SetLength(AVarList, SelList.Count); SetLength(AColNoSelected, SelList.Count); diff --git a/applications/lazstats/source/units/dataprocs.pas b/applications/lazstats/source/units/dataprocs.pas index 3ba850874..51be08b59 100644 --- a/applications/lazstats/source/units/dataprocs.pas +++ b/applications/lazstats/source/units/dataprocs.pas @@ -7,10 +7,11 @@ interface uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, Clipbrd, Grids, - Globals, OptionsUnit, DictionaryUnit; + Globals, OptionsUnit, DictionaryUnit, GridProcs; -Function GoodRecord(Row, NoVars: integer; const GridPos: IntDyneVec): boolean; overload; -Function GoodRecord(AGrid: TStringGrid; Row, NoVars: integer; const GridPos: IntDyneVec): boolean; overload; +function GoodRecord(Row, NoVars: integer; const GridPos: IntDyneVec): boolean; +function IsFiltered(GridRow: integer): boolean; overload; +function ValidValue(row, col: integer): boolean; procedure FormatCell(Col, Row : integer); procedure FormatGrid; @@ -29,10 +30,6 @@ procedure CopyRow; procedure PasteRow; procedure PrintDict(AReport: TStrings); procedure PrintData(AReport: TStrings); -function ValidValue(row, col: integer): boolean; overload; -function ValidValue(AGrid: TStringGrid; row, col: integer): boolean; overload; -function IsFiltered(GridRow: integer): boolean; overload; -function IsFiltered(AGrid: TStringGrid; GridRow: integer): boolean; overload; procedure MatRead(const a: DblDyneMat; out NoRows, NoCols: integer; const Means, StdDevs: DblDyneVec; out NCases: integer; @@ -66,25 +63,21 @@ implementation uses Utils, MainUnit; -function GoodRecord(Row, NoVars: Integer; const GridPos: IntDyneVec): boolean; -begin - Result := GoodRecord(OS3MainFrm.DataGrid, Row, NoVars, GridPos); -end; -function GoodRecord(AGrid: TStringGrid; Row, NoVars: integer; - const GridPos: IntDyneVec): boolean; +// NOTE: Do not call GridProcs.GoodRecord here because this old function may +// use an over-dimensioned GridPos array. +function GoodRecord(Row, NoVars: Integer; const GridPos: IntDyneVec): boolean; var i, j: integer; begin Result := true; - for i := 0 to NoVars-1 do + for i := 0 to NoVars - 1 do begin j := GridPos[i]; - if not ValidValue(AGrid, Row, j) then + if not ValidValue(Row, j) then Result := false; end; end; -//------------------------------------------------------------------- procedure FormatCell(Col, Row: integer); var @@ -802,33 +795,7 @@ end; function ValidValue(row, col: Integer): Boolean; begin - Result := ValidValue(OS3MainFrm.DataGrid, row, col); -end; - -function ValidValue(AGrid: TStringGrid; row, col: integer): boolean; -var - valid: boolean; - xvalue: string; - cellstring: string; -begin - valid := true; - if FilterOn then - begin - cellstring := Trim(AGrid.Cells[FilterCol, row]); - if cellstring = 'NO' then valid := false; - Result := valid; - exit; - end; - - xvalue := Trim(AGrid.Cells[col,row]); - if (xvalue = '') and (DictionaryFrm.DictGrid.Cells[4, col] <> 'S') then - valid := false; - if valid then // check for user-defined missing value - begin - if Trim(DictionaryFrm.DictGrid.Cells[6, col]) = xvalue then - valid := false; - end; - Result := valid; + Result := GridProcs.ValidValue(OS3MainFrm.DataGrid, row, col); end; function IsFiltered(GridRow: Integer): Boolean; @@ -836,12 +803,6 @@ begin Result := IsFiltered(OS3MainFrm.DataGrid, GridRow); end; -function IsFiltered(AGrid: TStringGrid; GridRow: integer): boolean; -begin - Result := FilterOn and (Trim(AGrid.Cells[FilterCol,GridRow]) = 'NO'); -end; - - procedure MatRead(const a: DblDyneMat; out NoRows, NoCols: integer; const means, stddevs: DblDyneVec; out NCases: integer; const RowLabels, ColLabels: StrDyneVec; const AFileName: string); diff --git a/applications/lazstats/source/units/gridprocs.pas b/applications/lazstats/source/units/gridprocs.pas new file mode 100644 index 000000000..97a97b2f1 --- /dev/null +++ b/applications/lazstats/source/units/gridprocs.pas @@ -0,0 +1,157 @@ +unit GridProcs; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, Grids, + Globals, DictionaryUnit; + +function CollectValues(AGrid: TStringGrid; AColIndex: Integer; + AColCheck: IntDyneVec): DblDyneVec; + +procedure GetMinMax(AGrid: TStringGrid; AColIndex: Integer; + const AColCheck: IntDyneVec; out AMin, AMax: Double); + +function GoodRecord(AGrid: TStringGrid; ARow: integer; + const AColCheck: IntDyneVec): boolean; + +function IsEmptyNumericValue(AGrid: TStringGrid; ARow, ACol: Integer): Boolean; + +function IsFiltered(AGrid: TStringGrid; ARow: integer): boolean; + +function IsMissingValueCode(AGrid: TStringGrid; ARow, ACol: Integer): Boolean; + +function ValidValue(AGrid: TStringGrid; ARow, ACol: integer): boolean; + + +implementation + +uses + Math; + +{ Extracts the values in the given column from the grid and returns them as an + array. + Cells which are filtered or empty are not considered. This check is extended + over all columns specified by the column indices in AColCheck; AColCheck + should be empty to consider only the current column. + Non-numeric values in the considered cell will raise an exception. + + NOTE: AColCheck must not be overdimensioned! } +function CollectValues(AGrid: TStringGrid; AColIndex: Integer; AColCheck: IntDyneVec): DblDyneVec; +var + row, n: Integer; +begin + SetLength(Result, AGrid.RowCount); + n := 0; + for row := 1 to AGrid.RowCount-1 do + begin + if Length(AColCheck) = 0 then + begin + if not ValidValue(AGrid, row, AColIndex) then continue; + end else + begin + if not GoodRecord(AGrid, row, AColCheck) then continue; + end; + Result[n] := StrToFloat(trim(AGrid.Cells[AColIndex, row])); + inc(n); + end; + SetLength(Result, n); +end; + + +{ Determines the minimum and maximum of the values in the specified column of + the grid. Rows with "invalid" data are ignored. If AColCheck contains other + column indices these cells must be "valid", too. } +procedure GetMinMax(AGrid: TStringGrid; AColIndex: Integer; + const AColCheck: IntDyneVec; out AMin, AMax: Double); +var + row: Integer; + value: Double; +begin + AMin := Infinity; + AMax := -Infinity; + for row := 1 to AGrid.RowCount-1 do + begin + if Length(AColCheck) = 0 then + begin + if not ValidValue(AGrid, row, AColIndex) then continue; + end else + begin + if not GoodRecord(AGrid, row, AColCheck) then continue; + end; + value := StrToFloat(trim(AGrid.Cells[AColIndex, row])); + if value < AMin then AMin := value; + if value > AMax then AMax := value; + end; +end; + + +{ Checks whether all cells specified for the given row in the columns listed in + the GridPos array are "valid": not filtered and not empty } +function GoodRecord(AGrid: TStringGrid; ARow: integer; + const AColCheck: IntDyneVec): boolean; +var + i, j: integer; +begin + Result := true; + for i := 0 to High(AColCheck) do + begin + j := AColCheck[i]; + if not ValidValue(AGrid, ARow, j) then + Result := false; + end; +end; + + +{ Checks whether the cell in the given row in the given numeric column is empty. } +function IsEmptyNumericValue(AGrid: TStringGrid; ARow, ACol: Integer): Boolean; +var + value: String; + isStringField: Boolean; +begin + value := Trim(AGrid.Cells[ACol, ARow]); + isStringField := DictionaryFrm.DictGrid.Cells[4, ACol] = 'S'; + Result := not IsStringField and (value = ''); +end; + + +{ Checks whether the specified row is "filtered". Two criteria are needed for + a row to be filtered: + - The cell in column FilterCol (global value) must contain the text 'NO'. + - The global variable "FilterOn" must be TRUE. } +function IsFiltered(AGrid: TStringGrid; ARow: integer): boolean; +begin + Result := FilterOn and (Trim(AGrid.Cells[FilterCol, ARow]) = 'NO'); +end; + + +{ Checks whether specified cell contains the "missing value code" defined by + the Dictionary } +function IsMissingValueCode(AGrid: TStringGrid; ARow, ACol: Integer): Boolean; +var + missingCode: String; + value: String; +begin + missingCode := Trim(DictionaryFrm.DictGrid.Cells[6, ACol]); + value := Trim(AGrid.Cells[ACol, ARow]); + Result := (value = missingCode); +end; + + +{ Checks wheter the value in cell at the given column and row is a not-filtered, + non-empty number. + NOTE: non-numeric characters in a numeric field are not taken into account! } +function ValidValue(AGrid: TStringGrid; ARow, ACol: integer): boolean; +begin + Result := not ( + IsFiltered(AGrid, ARow) or // filtering is active and row is marked to be excluded + IsEmptyNumericValue(AGrid, ARow, aCol) or // column is numeric, but cell is empty + IsMissingValueCode(AGrid, ARow, ACol) // cell contains the "missing value code" + ); +end; + + +end. +