// Use file "twoway.laz" for testing unit CrossTabUnit; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Buttons, ExtCtrls, ComCtrls, Globals, MainUnit, MatrixLib, ReportFrameUnit, BasicStatsReportFormUnit; type { TCrossTabForm } TCrossTabForm = class(TBasicStatsReportForm) VertCenterBevel: TBevel; InBtn: TBitBtn; OutBtn: TBitBtn; Label1: TLabel; Label2: TLabel; VarList: TListBox; SelList: TListBox; procedure InBtnClick(Sender: TObject); procedure OutBtnClick(Sender: TObject); procedure SelListDblClick(Sender: TObject); procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); private procedure Prepare(out AVarList, AColNoSelected: IntDyneVec); protected procedure AdjustConstraints; override; procedure Compute; override; procedure UpdateBtnStates; override; function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override; public procedure Reset; override; end; var CrossTabForm: TCrossTabForm; implementation {$R *.lfm} uses Math, Grids, Utils, GridProcs; { TCrossTabCalculator } type TCrossTabCalculator = class private FDataGrid: TStringGrid; FColNoSelected: IntDyneVec; FVarList: IntDyneVec; FReport: TStrings; grandsum, sum, index: integer; no_in_list, length_array: integer; min_value, levels, displace, subscript: IntDyneVec; freq: IntDyneVec; outgrid: DblDyneMat; NoSelected: integer; protected procedure BreakDown; procedure GetLevels; function IndexPosition(x: IntDyneVec): integer; procedure Tabulate; public procedure Execute(AReport: TStrings; ADataGrid: TStringGrid; AVarList, AColNoSelected: IntDyneVec); end; procedure TCrossTabCalculator.BreakDown; var i, j, row, col, bigmax: integer; ptr1, ptr2: Integer; outline: string; value: string; title: String; rowLabels: StrDyneVec = nil; colLabels: StrDyneVec = nil; begin bigmax := -1; for i := 0 to no_in_list-1 do if Levels[i] > bigmax then bigmax := Levels[i]; SetLength(colLabels, bigmax); SetLength(outgrid, length_array, bigmax); SetLength(rowlabels, length_array); outline := FDataGrid.Cells[FVarList[no_in_list-1], 0]; for col := 1 to Levels[no_in_list-1] do collabels[col-1] := outline + Format(':%3d', [min_value[no_in_list-1] + col - 1]); for row := 1 to length_array do rowlabels[row-1] := ''; ptr1 := no_in_list - 1; ptr2 := no_in_list; for i := 1 to no_in_list do subscript[i-1] := 1; FReport.Add(''); FReport.Add('FREQUENCIES BY LEVEL:'); FReport.Add(''); sum := 0; col := 1; row := 1; while true do begin index := IndexPosition(subscript); outline := 'For cell levels: '; for i := 1 to no_in_list do begin j := FVarList[i-1]; value := Format('%s:%3d ',[FDataGrid.Cells[j,0], min_value[i-1] + subscript[i-1] - 1]); outline := outline + value; end; sum := sum + freq[index]; outgrid[row-1,col-1] := freq[index]; outline := outline + Format(' Frequency: %3d', [freq[index]]); FReport.Add(outline); subscript[ptr2-1] := subscript[ptr2-1] + 1; col := col + 1; if subscript[ptr2-1] <= levels[ptr2-1] then continue; FReport.Add('Sum across levels: %3d', [sum]); FReport.Add(''); grandsum := grandsum + sum; sum := 0; row := row + 1; if ptr1 < 1 then break; subscript[ptr1-1] := subscript[ptr1-1] + 1; if subscript[ptr1-1] > levels[ptr1-1] then begin repeat ptr1 := ptr1 - 1; if ptr1 < 1 then break; until subscript[ptr1-1] < levels[ptr1-1]; if ptr1 < 1 then break; subscript[ptr1-1] := subscript[ptr1-1] + 1; end; for i := ptr1 + 1 to no_in_list do subscript[i-1] := 1; ptr1 := no_in_list - 1; col := 1; end; title := 'CELL FREQUENCIES BY LEVELS'; for i := 1 to row - 1 do begin value := Format('Block %d',[i]); rowlabels[i-1] := value; end; FReport.Add(''); MatPrint(outgrid, row-1, Levels[no_in_list-1], title, rowlabels, collabels, NoCases, FReport); end; procedure TCrossTabCalculator.Execute(AReport: TStrings; ADataGrid: TStringGrid; AVarList, AColNoSelected: IntDyneVec); begin FReport := AReport; FDataGrid := ADataGrid; FVarList := AVarList; no_in_list := Length(FVarList); SetLength(min_value, no_in_list); SetLength(levels, no_in_list); SetLength(displace, no_in_list); SetLength(subscript, no_in_list); FColNoSelected := AColNoSelected; NoSelected := Length(FColNoSelected); GetLevels; Tabulate; BreakDown; FReport.Add(''); FReport.Add('Grand sum across all categories: %d', [grandsum]); end; procedure TCrossTabCalculator.GetLevels; var i, j, k: integer; value: double; max_value: IntDyneVec = nil; begin SetLength(max_value, no_in_list); for i := 1 to no_in_list do begin j := FVarList[i-1]; 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, FColNoSelected) then continue; value := StrToFloat(FDataGrid.Cells[j, k]); if value < min_value[i-1] then min_value[i-1] := round(value); if value > max_value[i-1] then max_value[i-1] := round(value); end; end; for i := 1 to no_in_list do begin j := FVarList[i-1]; levels[i-1] := max_value[i-1] - min_value[i-1] + 1; FReport.Add('%-10s min %3d, max %3d, number of levels %3d', [ FDataGrid.Cells[j,0]+':', min_value[i-1], max_value[i-1], levels[i-1] ]); end; FReport.Add(''); displace[no_in_list-1] := 1; if no_in_list > 1 then for i := (no_in_list - 1) downto 1 do displace[i-1] := levels[i] * displace[i]; end; function TCrossTabCalculator.IndexPosition(x: IntDyneVec): integer; var i: integer; begin Result := x[no_in_list-1]; if no_in_list > 1 then begin for i := 1 to no_in_list - 1 do Result := Result + (x[i-1] -1) * displace[i-1]; end; end; procedure TCrossTabCalculator.Tabulate; var i, j, k: integer; value: double; x: integer; begin length_array := 1; for i := 1 to no_in_list do length_array := length_array * levels[i-1]; SetLength(freq, length_array+1); for i := 0 to length_array do freq[i] := 0; for i := 1 to NoCases do begin if IsFiltered(FDataGrid, i) then continue; for j := 1 to no_in_list do begin if not GoodRecord(FDataGrid, i, FColNoSelected) then continue; k := FVarList[j-1]; value := StrToFloat(FDataGrid.Cells[k,i]); x := round(value); x := x - min_value[j-1] + 1; subscript[j-1] := x; end; j := IndexPosition(subscript); if (j < 1) or (j > length_array) then continue else freq[j] := freq[j] + 1; end; end; { TCrossTabForm } procedure TCrossTabForm.AdjustConstraints; begin inherited; ParamsPanel.Constraints.MinWidth := Max( 4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left, Max(Label1.Width, Label2.Width) + InBtn.Width + VarList.BorderSpacing.Right * 2 ); ParamsPanel.Constraints.MinHeight := 200; end; procedure TCrossTabForm.Compute; var lReport: TStringList; crossTab: TCrossTabCalculator; var_list: IntDyneVec = nil; colNoSelected: IntDyneVec = nil; begin inherited; lReport := TStringList.Create; try lReport.Add('CROSSTAB RESULTS'); lReport.Add(''); lReport.Add('Analyzed data is from file ' + OS3MainFrm.FileNameEdit.Text); lReport.Add(''); crossTab := TCrossTabCalculator.Create; try Prepare(var_list, colNoSelected); crossTab.Execute(lReport, OS3MainFrm.DataGrid, var_list, colNoSelected); FReportFrame.DisplayReport(lReport); finally crossTab.Free; end; finally lReport.Free; end; end; procedure TCrossTabForm.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 TCrossTabForm.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 TCrossTabForm.Prepare(out AVarlist, AColNoSelected: IntDyneVec); var i, j: Integer; noSelected: Integer; cellValue: String; begin AVarList := nil; // Silence the compiler AColNoSelected := nil; SetLength(AVarList, SelList.Count); SetLength(AColNoSelected, SelList.Count); noSelected := 0; for i := 0 to SelList.Items.Count-1 do begin for j := 1 to NoVariables do begin cellvalue := OS3MainFrm.DataGrid.Cells[j, 0]; if cellvalue = SelList.Items[i] then begin AVarList[i] := j; AColNoSelected[i] := j; inc(noSelected); break; end; end; end; SetLength(AVarList, noSelected); Setlength(AColNoSelected, noSelected); end; procedure TCrossTabForm.Reset; begin inherited; CollectVariableNames(OS3MainFrm.DataGrid, VarList.Items); SelList.Clear; UpdateBtnStates; end; procedure TCrossTabForm.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 TCrossTabForm.UpdateBtnStates; var lSelected: Boolean; i: Integer; begin inherited; 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; end; function TCrossTabForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; begin Result := false; if SelList.Items.Count = 0 then begin AMsg := 'No variables selected for analysis.'; AControl := SelList; exit; end; Result := true; end; procedure TCrossTabForm.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 TCrossTabForm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; end.