Files
lazarus-ccr/applications/lazstats/source/forms/analysis/measurement_programs/guttmanunit.pas

634 lines
19 KiB
ObjectPascal
Raw Normal View History

unit GuttmanUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
StdCtrls, Buttons, ExtCtrls,
MainUnit, OutputUnit, Globals, DataProcs;
type
{ TGuttmanFrm }
TGuttmanFrm = class(TForm)
Bevel1: TBevel;
InBtn: TBitBtn;
OutBtn: TBitBtn;
AllBtn: TBitBtn;
ResetBtn: TButton;
ComputeBtn: TButton;
CloseBtn: TButton;
Label1: TLabel;
Label2: TLabel;
ItemList: TListBox;
VarList: TListBox;
procedure AllBtnClick(Sender: TObject);
procedure ComputeBtnClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure InBtnClick(Sender: TObject);
procedure OutBtnClick(Sender: TObject);
procedure ResetBtnClick(Sender: TObject);
procedure VarListSelectionChange(Sender: TObject; User: boolean);
private
{ private declarations }
FAutoSized: Boolean;
procedure UpdateBtnStates;
public
{ public declarations }
end;
var
GuttmanFrm: TGuttmanFrm;
implementation
uses
Math, Utils;
{ TGuttmanFrm }
procedure TGuttmanFrm.ResetBtnClick(Sender: TObject);
var
i: integer;
begin
VarList.Clear;
ItemList.Clear;
for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
UpdateBtnStates;
end;
procedure TGuttmanFrm.VarListSelectionChange(Sender: TObject; User: boolean);
begin
UpdateBtnStates;
end;
procedure TGuttmanFrm.FormActivate(Sender: TObject);
var
w: Integer;
begin
if FAutoSized then
exit;
w := MaxValue([ResetBtn.Width, ComputeBtn.Width, CloseBtn.Width]);
ResetBtn.Constraints.MinWidth := w;
ComputeBtn.Constraints.MinWidth := w;
CloseBtn.Constraints.MinWidth := w;
Constraints.MinWidth := Width + w; // make form a bit wider...
Constraints.MinHeight := Height;
FAutoSized := true;
end;
procedure TGuttmanFrm.FormCreate(Sender: TObject);
begin
Assert(OS3MainFrm <> nil);
end;
procedure TGuttmanFrm.FormShow(Sender: TObject);
begin
ResetBtnClick(self);
end;
procedure TGuttmanFrm.AllBtnClick(Sender: TObject);
var
i: integer;
begin
for i := 0 to VarList.Items.Count - 1 do
ItemList.Items.Add(VarList.Items[i-1]);
VarList.Clear;
UpdateBtnStates;
end;
procedure TGuttmanFrm.ComputeBtnClick(Sender: TObject);
var
i, j, k, col, X, e0, e1, e2, e3, first, last, errors : integer;
totalerrors, rowno : integer;
FreqMat0 : IntDyneMat; // Pointer to array of 0 responses for each item by score group
FreqMat1 : IntDyneMat; // Pointer to array of 1 responses for each item by score group
RowTots : IntDyneVec; // Pointer to vector of total score frequencies for items
ColTots : IntDyneMat; // Pointer to array of 0 and 1 column totals
ColProps : DblDyneVec; // Pointer to array of proportions correct in columns
ColNoSelected : IntDyneVec; // Pointer to vector of item Grid columns
CaseVector : IntDyneVec; // Pointer to vector of subject's item responses
TotalScore : integer; // Total score of a subject
temp : integer; // temporary variable used in sorting
CutScore : IntDyneVec; // Optimal cut scores for each item
ErrorMat : IntDyneMat; // matrix of errors above and below cut scores
sequence : IntDyneVec; // original and sorted sequence no. of items
CaseNo : IntDyneVec; // ID number for each case
ModalArray : IntDyneMat; // Array of modal item responses
NoSelected : integer;
VarLabels : StrDyneVec; // variable labels
outline, astring : string;
done : boolean;
CoefRepro : double;
Min_Coeff : double;
lReport: TStrings;
begin
if ItemList.Count = 0 then
begin
MessageDlg('No variable(s) selected.', mtError, [mbOK], 0);
exit;
end;
// allocate heap space for arrays
SetLength(ColNoSelected,NoVariables);
SetLength(FreqMat0,NoCases,NoVariables);
SetLength(FreqMat1,NoCases,NoVariables);
SetLength(RowTots,NoCases);
SetLength(ColTots,NoVariables,2);
SetLength(ColProps,NoVariables);
SetLength(CaseVector,NoCases);
SetLength(CutScore,NoCases);
SetLength(ErrorMat,NoVariables,2);
SetLength(sequence,NoVariables);
SetLength(CaseNo,NoCases);
SetLength(ModalArray,NoVariables+1,NoVariables+1);
SetLength(VarLabels,NoVariables);
// get variables used for the analysis
NoSelected := ItemList.Items.Count;
for i := 1 to NoVariables do
begin
for j := 1 to NoSelected do
begin
if OS3MainFrm.DataGrid.Cells[i,0] = ItemList.Items.Strings[j-1] then
begin
ColNoSelected[j-1] := i;
VarLabels[j-1] := OS3MainFrm.DataGrid.Cells[i,0];
end;
end;
end;
// Initialize sequence
for i := 1 to NoSelected do sequence[i-1] := i;
// Initialize arrays
for i := 0 to NoSelected-1 do
begin
ColTots[i,0] := 0;
ColTots[i,1] := 0;
ColProps[i] := 0.0;
ErrorMat[i,0] := 0;
ErrorMat[i,1] := 0;
end;
for i := 0 to NoCases-1 do
begin
RowTots[i] := 0;
CutScore[i] := 0;
CaseNo[i] := i+1;
for j := 0 to NoSelected-1 do
begin
FreqMat0[i,j] := 0;
FreqMat1[i,j] := 0;
end;
end;
if (NoCases > NoSelected) then
begin
for i := 1 to NoCases do CaseVector[i-1] := 0;
end
else begin
for i := 1 to NoSelected do CaseVector[i-1] := 0;
end;
// Get data into the frequency matrices of 0 and 1 responses
for i := 1 to NoCases do
begin
if (not GoodRecord(i,NoSelected,ColNoSelected)) then continue;
TotalScore := 0;
for j := 1 to NoSelected do
begin
col := ColNoSelected[j-1];
X := round(StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[col,i])));
CaseVector[j-1] := X;
TotalScore := TotalScore + X;
end;
for j := 1 to NoSelected do
begin
if (CaseVector[j-1] = 0) then FreqMat0[i-1,j-1] := 1
else FreqMat1[i-1,j-1] := 1;
end;
end;
// Get Row Totals for each score group (rows of FreqMat1)
for i := 1 to NoCases do
begin
if (not GoodRecord(i,NoSelected,ColNoSelected)) then continue;
for j := 1 to NoSelected do
begin
RowTots[i-1] := RowTots[i-1] + FreqMat1[i-1,j-1];
end;
end;
// Get Column Totals for item scores of 1 and 0
for i := 1 to NoSelected do //columns
begin
for j := 1 to NoCases do // rows
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
ColTots[i-1,0] := ColTots[i-1,0] + FreqMat0[j-1,i-1];
ColTots[i-1,1] := ColTots[i-1,1] + FreqMat1[j-1,i-1];
end;
end;
//Sort frequency matrices into descending order
for i := 1 to NoCases - 1 do
begin
if (not GoodRecord(i,NoSelected,ColNoSelected)) then continue;
for j := i + 1 to NoCases do
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
if (RowTots[i-1] < RowTots[j-1]) then //swap
begin
for k := 1 to NoSelected do
begin // carry all columns in the swap
temp := FreqMat0[i-1,k-1];
FreqMat0[i-1,k-1] := FreqMat0[j-1,k-1];
FreqMat0[j-1,k-1] := temp;
temp := FreqMat1[i-1,k-1];
FreqMat1[i-1,k-1] := FreqMat1[j-1,k-1];
FreqMat1[j-1,k-1] := temp;
end;
// Also swap row totals
temp := RowTots[i-1];
RowTots[i-1] := RowTots[j-1];
RowTots[j-1] := temp;
// And case number
temp := CaseNo[i-1];
CaseNo[i-1] := CaseNo[j-1];
CaseNo[j-1] := temp;
end; // end if
end; // Next j
end; // next i
// Now sort the columns into ascending order of number right
for i := 1 to NoSelected - 1 do
begin
for j := i + 1 to NoSelected do
begin
if (ColTots[i-1,1] > ColTots[j-1,1]) then //swap
begin
for k := 1 to NoCases do
begin
if (not GoodRecord(k,NoSelected,ColNoSelected)) then continue;
temp := FreqMat0[k-1,i-1];
FreqMat0[k-1,i-1] := FreqMat0[k-1,j-1];
FreqMat0[k-1,j-1] := temp;
temp := FreqMat1[k-1,i-1];
FreqMat1[k-1,i-1] := FreqMat1[k-1,j-1];
FreqMat1[k-1,j-1] := temp;
end; // next k
// swap column totals also
temp := ColTots[i-1,0];
ColTots[i-1,0] := ColTots[j-1,0];
ColTots[j-1,0] := temp;
temp := ColTots[i-1,1];
ColTots[i-1,1] := ColTots[j-1,1];
ColTots[j-1,1] := temp;
// swap label pointers
temp := sequence[i-1];
sequence[i-1] := sequence[j-1];
sequence[j-1] := temp;
end; // end if
end; // next j
end; // next i
//For each item (column), find the optimal cutting value
for i := 1 to NoSelected do
begin
CutScore[i-1] := 0;
for j := 1 to NoCases do // j is the trial cut point
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
e0 := 0;
e1 := 0;
//Get errors prior to the cut point
for k := 1 to j do
begin
if (not GoodRecord(k,NoSelected,ColNoSelected)) then continue;
if (FreqMat0[k-1,i-1] = 1) then e0 := e0 + 1;
end;
//Get errors following the cut point
for k := j + 1 to NoCases do
begin
if (not GoodRecord(k,NoSelected,ColNoSelected)) then continue;
if (FreqMat1[k-1,i-1] = 1) then e1 := e1 + 1;
end;
//Save errors for each cut
CaseVector[j-1] := e0 + e1;
end; // next j
// Save minimum cut score index
e2 := 32000;
e3 := 0;
for j := 1 to NoCases do
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
if (CaseVector[j-1] < e2) then
begin
e2 := CaseVector[j-1];
e3 := j;
end;
end;
CutScore[i-1] := e3; //Position of optimal cut for item i
end;
// Get error counts;
for i := 1 to NoSelected do
begin
for j := 1 to CutScore[i-1] do
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
if ((FreqMat0[j-1,i-1] > 0) or (FreqMat1[j-1,i-1] > 0)) then
ErrorMat[i-1,0] := ErrorMat[i-1,0] + FreqMat0[j-1,i-1];
end;
for j := CutScore[i-1] + 1 to NoCases do
begin
if (not GoodRecord(j,NoSelected,ColNoSelected)) then continue;
if ((FreqMat0[j-1,i-1] > 0) or (FreqMat1[j-1,i-1] > 0)) then
ErrorMat[i-1,1] := ErrorMat[i-1,1] + FreqMat1[j-1,i-1];
end;
end;
// Print results
lReport := TStringList.Create;
try
lReport.Add(' GUTTMAN SCALOGRAM ANALYSIS');
lReport.Add(' Cornell Method');
lReport.Add('');
lReport.Add('No. of Cases: %5d', [NoCases]);
lReport.Add('No. of items: %5d', [NoSelected]);
lReport.Add('');
lReport.Add('RESPONSE MATRIX');
lReport.Add('');
first := 1;
last := first + 5; // column (item) index
if (last > NoSelected) then last := NoSelected;
done := false;
while (not done) do //loop through all of the score groups
begin
lReport.Add('Subject Row Item Number');
outline := 'Label Sum';
for i := first to last do
outline := outline + Format('%10s', [VarLabels[sequence[i-1]-1]]);
lReport.Add(outline);
outline := ' ';
for i := first to last do
outline := outline + ' 0 1 ';
lReport.Add(outline);
lReport.Add('');
for i := 1 to NoCases do // rows
begin
if (not GoodRecord(i,NoSelected,ColNoSelected)) then continue;
outline := Format(' %3d %3d ', [CaseNo[i-1], RowTots[i-1]]);
for j := first to last do
outline := outline + Format(' %3d %3d ', [FreqMat0[i-1,j-1], FreqMat1[i-1,j-1]]);
lReport.Add(outline);
// check for optimal cut point for this score
outline := ' ';
for j := first to last do
if (CutScore[j-1] = i) then
outline := outline + ' -cut- '
else
outline := outline + ' ';
lReport.Add(outline);
end; // Next row (score group)
lReport.Add('');
outline := 'TOTALS ';
for j := first to last do
outline := outline + Format(' %3d %3d ', [ColTots[j-1,0], ColTots[j-1,1]]);
lReport.Add(outline);
outline := 'ERRORS ';
for j := first to last do
outline := outline + Format(' %3d %3d ', [ErrorMat[j-1,0], ErrorMat[j-1,1]]);
lReport.Add(outline);
if (last < NoSelected) then
begin
first := last + 1;
last := first + 5; // column (item) index
if (last > NoSelected) then last := NoSelected;
end
else done := true;
lReport.Add('');
end;
lReport.Add('');
CoefRepro := 0.0;
for j := 1 to NoSelected do
CoefRepro := CoefRepro + ErrorMat[j-1,0] + ErrorMat[j-1,1];
CoefRepro := 1.0 - (CoefRepro / (NoCases * NoSelected));
lReport.Add('Coefficient of Reproducibility := %6.3f',[CoefRepro]);
lReport.Add('');
//-----------------------------GOODENOUGH----------------------------------
// Complete Goodenough method and print results
lReport.Add('');
lReport.Add(' GUTTMAN SCALOGRAM ANALYSIS');
lReport.Add(' Goodenough Modification Using Modal Responses');
lReport.Add('');
totalerrors := 0;
Min_Coeff := 0.0;
for i := 1 to NoSelected + 1 do
for j := 1 to NoSelected do ModalArray[i-1,j-1] := 0;
for i := 1 to NoSelected do // column
begin
ColProps[i-1] := ColTots[i-1,1] / NoCases;
ErrorMat[i-1,0] := 0;
ErrorMat[i-1,1] := 0;
end;
// Get the cut scores for each score row based on rounded proportions
for i := 1 to NoSelected do
begin
CutScore[i-1] := Trunc(ColProps[i-1] * (NoSelected+1));
end;
// Build modal response array for the total scores by items
lReport.Add('');
lReport.Add(' MODAL ITEM RESPONSES');
lReport.Add('');
lReport.Add('TOTAL ITEMS');
outline := ' ';
for i := 1 to NoSelected do
begin
astring := format('%10s',[VarLabels[sequence[i-1]-1]]);
outline := outline + astring;
end;
lReport.Add(outline);
for i := 0 to NoSelected do
begin
for j := 1 to NoSelected do
if (CutScore[j-1] > i) then
ModalArray[i,j-1] := 1
else
ModalArray[i,j-1] := 0;
astring := format(' %3d ',[NoSelected - i]);
outline := astring;
for j := 1 to NoSelected do
begin
astring := format(' %3d ',[ModalArray[i,j-1]]);
outline := outline + astring;
end;
lReport.Add(outline);
end;
lReport.Add('');
lReport.Add('No. of Cases: %3d', [NoCases]);
lReport.Add('No. of items: %3d', [NoSelected]);
lReport.Add('');
lReport.Add('RESPONSE MATRIX');
lReport.Add('');
first := 1;
last := first + 5; // column (item) index
if (last > NoSelected) then last := NoSelected;
done := false;
while (not done) do //loop through all of the score groups
begin
lReport.Add('Subject Row Error Item Number');
outline := 'Label Sum Count';
for i := first to last do
outline := outline + Format('%10s', [VarLabels[sequence[i-1]-1]]);
lReport.Add(outline);
outline := ' ';
for i := first to last do
outline := outline + ' 0 1 ';
lReport.Add(outline);
lReport.Add('');
for i := 1 to NoCases do // rows
begin
if (not GoodRecord(i,NoSelected,ColNoSelected)) then continue;
errors := 0;
for j := first to last do
begin
rowno := NoSelected - RowTots[i-1] + 1;
if (FreqMat1[i-1,j-1] <> ModalArray[rowno-1,j-1]) then errors := errors + 1;
end;
outline := format(' %3d %3d %3d ',[CaseNo[i-1],RowTots[i-1],errors]);
for j := first to last do
begin
astring := format(' %3d %3d ',[FreqMat0[i-1,j-1],FreqMat1[i-1,j-1]]);
outline := outline + astring;
end;
lReport.Add(outline);
totalerrors := totalerrors + errors;
end; // Next row (score group)
lReport.Add('');
outline :='TOTALS ';
for j := first to last do
outline := outline + Format(' %3d %3d ',[ColTots[j-1,0], ColTots[j-1,1]]);
lReport.Add(outline);
outline := 'PROPORTIONS ';
for j := first to last do
outline := outline + Format('%4.2f %4.2f ',[(1.0-ColProps[j-1]), ColProps[j-1]]);
lReport.Add(outline);
if (last < NoSelected) then
begin
first := last + 1;
last := first + 5; // column (item) index
if (last > NoSelected) then last := NoSelected;
end
else
done := true;
lReport.Add('');
end;
lReport.Add('');
CoefRepro := 1.0 - (totalerrors / (NoCases * NoSelected));
lReport.Add('Coefficient of Reproducibility: %6.3f', [CoefRepro]);
for j := 1 to NoSelected do
if (ColProps[j-1] > (1.0 - ColProps[j-1])) then
Min_Coeff := Min_Coeff + ColProps[j-1]
else
Min_Coeff := Min_Coeff + (1.0 - ColProps[j-1]);
Min_Coeff := Min_coeff / NoSelected;
lReport.Add('Minimal Marginal Reproducibility: %6.3f', [Min_Coeff]);
DisplayReport(lReport);
finally
lReport.Free;
// Clean up the heap
VarLabels := nil;
ModalArray := nil;
CaseNo := nil;
sequence := nil;
ErrorMat := nil;
CutScore := nil;
CaseVector := nil;
ColProps := nil;
ColTots := nil;
RowTots := nil;
FreqMat1 := nil;
FreqMat0 := nil;
ColNoSelected := nil;
end;
end;
procedure TGuttmanFrm.InBtnClick(Sender: TObject);
var
i: integer;
begin
i := 0;
while i < VarList.Items.Count do
begin
if VarList.Selected[i] then
begin
ItemList.Items.Add(VarList.Items[i]);
VarList.Items.Delete(i);
i := 0;
end
else
inc(i);
end;
UpdateBtnStates;
end;
procedure TGuttmanFrm.OutBtnClick(Sender: TObject);
var
i: integer;
begin
i := 0;
while i < ItemList.Items.Count do
begin
if ItemList.Selected[i] then
begin
VarList.Items.Add(ItemList.Items[i]);
ItemList.Items.Delete(i);
i := 0;
end
else
inc(i);
end;
UpdateBtnStates;
end;
procedure TGuttmanFrm.UpdateBtnStates;
begin
InBtn.Enabled := AnySelected(Varlist);
OutBtn.Enabled := AnySelected(Itemlist);
AllBtn.Enabled := VarList.Items.Count > 0;
end;
initialization
{$I guttmanunit.lrs}
end.