unit GridProcs; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Grids, Globals, DictionaryUnit; function CollectVecValues(AGrid: TStringGrid; AColIndex: Integer; AColCheck: IntDyneVec = nil): DblDyneVec; function CollectMatValues(AGrid: TStringGrid; AColIndices: IntDyneVec): DblDyneMat; procedure GetMinMax(AGrid: TStringGrid; AColIndex: Integer; const AColCheck: IntDyneVec; out AMin, AMax: Double); function GetVariableIndex(AGrid: TStringGrid; const AVarName: String): Integer; 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 IsNumericCol(AColIndex: 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 CollectVecValues(AGrid: TStringGrid; AColIndex: Integer; AColCheck: IntDyneVec): DblDyneVec; var row, n: Integer; val: Double; 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; if TryStrToFloat(trim(AGrid.Cells[AColIndex, row]), val) then Result[n] := val else raise ELazStats.CreateFmt('Non-numeric string "%s" in column %d, row %d', [AGrid.Cells[AColIndex, row], AColIndex, row]); inc(n); end; SetLength(Result, n); end; { Extracts the grid values from the columns with indices given by AColIndices and puts them into the columns of the result matrix. This means: The result matrix contains the variables as columns and the cases as rows. "Bad" records (filtered, empty) are skipped. } function CollectMatValues(AGrid: TStringGrid; AColIndices: IntDyneVec): DblDyneMat; var r, c, i, j: Integer; val: Double; begin SetLength(Result, AGrid.RowCount, Length(AColIndices)); i := 0; for r:= 1 to AGrid.RowCount-1 do begin if not GoodRecord(AGrid, r, AColIndices) then Continue; for j := 0 to High(AColIndices) do begin c := AColIndices[j]; if TryStrToFloat(trim(AGrid.Cells[c, r]), val) then Result[i, j] := val else Result[i, j] := NaN; end; inc(i); end; SetLength(Result, i); 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; { Finds the index of the variable with the specified name among the columns of the grid. } function GetVariableIndex(AGrid: TStringGrid; const AVarName: String): Integer; begin if AVarName <> '' then Result := AGrid.Rows[0].IndexOf(AVarName) else Result := -1; 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 if ACol < DictionaryFrm.DictGrid.RowCount then begin missingCode := Trim(DictionaryFrm.DictGrid.Cells[6, ACol]); value := Trim(AGrid.Cells[ACol, ARow]); Result := (value = missingCode); end else Result := false; end; { Checks in the dictionary whether the variable in the specified grid column has either data type float or integer. } function IsNumericCol(AColIndex: Integer): Boolean; var typeCode: String; begin if AColIndex < DictionaryFrm.DictGrid.RowCount then begin typeCode := Trim(DictionaryFrm.DictGrid.Cells[4, AColIndex]); Result := (typeCode = 'F') or (typeCode = 'I'); end else Result := false; 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.