unit TTestUnit; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, Buttons, MainUnit, Globals, BasicStatsReportFormUnit; type { TTtestForm } TTtestForm = class(TBasicStatsReportForm) Bevel3: TBevel; Bevel4: TBevel; Bevel5: TBevel; TailCombo: TComboBox; GroupCodeGroup: TGroupBox; GroupCodeChk: TCheckBox; Grp1CodeEdit: TEdit; Grp2CodeEdit: TEdit; GrpCodeLabel1: TLabel; GrpCodeLabel2: TLabel; GrpIn: TBitBtn; GrpOut: TBitBtn; Label2: TLabel; Notebook: TNotebook; Page1: TPage; Page2: TPage; Cor12Label: TLabel; Cor12Edit: TEdit; CIntervalEdit: TEdit; GrpEdit: TEdit; Label1: TLabel; Var1In: TBitBtn; Var1Out: TBitBtn; Var2Edit: TEdit; Var1Edit: TEdit; Var1Label: TLabel; GrpLabel: TLabel; Var2Label: TLabel; VarList: TListBox; SelVarLabel: TLabel; N2Edit: TEdit; N1Edit: TEdit; SampSize2Label: TLabel; SampSize1Label: TLabel; SD2Edit: TEdit; SD1Edit: TEdit; SD2Label: TLabel; SD1Label: TLabel; Mean2Edit: TEdit; Mean1Edit: TEdit; Mean2Label: TLabel; Mean1Label: TLabel; DataEntryGroup: TRadioGroup; DepIndepGroup: TRadioGroup; Var2In: TBitBtn; Var2Out: TBitBtn; procedure GroupCodeChkChange(Sender: TObject); procedure DataEntryGroupClick(Sender: TObject); procedure DepIndepGroupClick(Sender: TObject); procedure GrpInClick(Sender: TObject); procedure GrpOutClick(Sender: TObject); procedure Var1InClick(Sender: TObject); procedure Var1OutClick(Sender: TObject); procedure Var2InClick(Sender: TObject); procedure Var2OutClick(Sender: TObject); procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); private independent: boolean; griddata: boolean; function Eval_DependentGridData(out AMean1, AMean2, AVariance1, AVariance2, AStdDev1, AStdDev2, ACovar12, r12: Double; out ANumCases1, ANumCases2: Integer): Boolean; function Eval_IndependentGridData(out AMean1, AMean2, AVariance1, AVariance2, AStdDev1, AStdDev2: Double; out ANumCases1, ANumCases2: Integer; out ALabel1Str, ALabel2Str: String): Boolean; protected procedure AdjustConstraints; override; procedure Compute; override; procedure UpdateBtnStates; override; function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override; public constructor Create(AOwner: TComponent); override; procedure Reset; override; end; var TtestForm: TTtestForm; implementation {$R *.lfm} uses Math, Utils, MathUnit, MatrixUnit, GridProcs; { TTtestForm } constructor TTtestForm.Create(AOwner: TComponent); begin inherited; CIntervalEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT); end; procedure TTtestForm.AdjustConstraints; begin inherited; ParamsPanel.Constraints.MinWidth := Max( DataEntryGroup.Width + DepIndepGroup.BorderSpacing.Left + DepIndepGroup.Width, 4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left ); ParamsPanel.Constraints.MinHeight := DataEntryGroup.Height + TailCombo.BorderSpacing.Top + TailCombo.Height + CIntervalEdit.BorderSpacing.Top + CIntervalEdit.Height + CIntervalEdit.BorderSpacing.Bottom + Bevel3.Height + Bevel3.BorderSpacing.Bottom + GrpIn.Top + GroupCodeGroup.Height + ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height; end; procedure TTtestForm.Compute; var mean1, mean2, Dif, stddev1, stddev2, r12, stderr1, stderr2: double; tequal, tunequal, cov12, lowci, hici, F, Fp, df1, df2: double; tprobability: double; variance1, variance2, pooled, sedif, df, ConfInt, tconfint: double; nCases1, nCases2: integer; label1Str, label2Str: string; lReport: TStrings; begin ConfInt := (100.0 - StrToFloat(CIntervalEdit.Text)) / 2.0 ; ConfInt := (100.0 - ConfInt) / 100.0; // one tail if griddata then begin // Read and analyze data from grid if independent then begin if not Eval_IndependentGridData( mean1, mean2, variance1, variance2, stddev1, stddev2, nCases1, nCases2, Label1Str, Label2Str) then exit; end else begin if not Eval_DependentGridData( mean1, mean2, variance1, variance2, stddev1, stddev2, cov12, r12, nCases1, nCases2) then exit; end; end else begin // Read data from form mean1 := StrToFloat(mean1Edit.Text); mean2 := StrToFloat(mean2Edit.Text); stddev1 := StrToFloat(SD1Edit.Text); stddev2 := StrToFloat(SD2Edit.Text); ncases1 := round(StrToFloat(N1Edit.Text)); ncases2 := round(StrToFloat(N2Edit.Text)); variance1 := stddev1 * stddev1; variance2 := stddev2 * stddev2; Label1Str := 'Group 1'; Label2Str := 'Group 2'; if not independent then begin r12 := StrToFloat(Cor12Edit.Text); cov12 := r12 * stddev1 * stddev2; end; end; dif := mean1 - mean2; // Initialize output form lReport := TStringList.Create; try lReport.Add('COMPARISON OF TWO MEANS'); lReport.Add(''); // Calculate pooled and independent t and z values and test statistic if independent then begin stderr1 := sqrt(variance1 / ncases1); Stderr2 := sqrt(variance2 / ncases2); lReport.Add('Variable Mean Variance Std.Dev. S.E.Mean N'); lReport.Add('%-10s%8.2f %8.2f %8.2f %8.2f %d', [Label1Str, mean1, variance1, stddev1, stderr1, ncases1]); lReport.Add('%-10s%8.2f %8.2f %8.2f %8.2f %d', [Label2Str, mean2, variance2, stddev2, stderr2, ncases2]); lReport.Add(''); pooled := ((ncases1-1) * variance1) + ((ncases2-1) * variance2); pooled := pooled / (ncases1 + ncases2 - 2); pooled := pooled * ( 1.0 / ncases1 + 1.0 / ncases2); sedif := sqrt(pooled); tequal := dif / sedif; df := ncases1 + ncases2 - 2; tprobability := ProbT(tequal, df); if TailCombo.ItemIndex = 1 then tprobability := 0.5 * tprobability; lReport.Add('Assuming equal variances, t = %.3f with probability = %.4f and %.0f degrees of freedom', [ tequal, tprobability, df ]); lReport.Add('Difference = %.2f and Standard Error of difference = %.2f', [dif, sedif]); tconfint := inverset(ConfInt,df); lowci := dif - tconfint * sedif; hici := dif + tconfint * sedif; lReport.Add('Confidence interval = (%.2f ... %.2f)', [lowci, hici]); lReport.Add(''); // now for unequal variances sedif := sqrt((variance1 / ncases1) + (variance2 / ncases2)); tunequal := dif / sedif; df := sqr((variance1 / ncases1) + (variance2 / ncases2)); df := df / (sqr(variance1 / ncases1) / (ncases1 - 1) + sqr(variance2 / ncases2) / (ncases2 - 1) ); tprobability := ProbT(tequal, df); if TailCombo.ItemIndex = 1 then tprobability := 0.5 * tprobability; lReport.Add('Assuming unequal variances, t = %.3f with probability = %.4f and %.0f degrees of freedom', [ tunequal, tprobability, df ]); lReport.Add('Difference = %.2f and Standard Error of difference = %.2f', [dif, sedif]); tconfint := inverset(ConfInt,df); lowci := dif - tconfint * sedif; hici := dif + tconfint * sedif; lReport.Add('Confidence interval = (%.2f ... %.2f)', [lowci, hici]); lReport.Add(''); df1 := ncases1 - 1; df2 := ncases2 - 1; if variance1 > variance2 then begin F := variance1 / variance2; Fp := ProbF(F, df1, df2); end else begin F := variance2 / variance1; Fp := ProbF(F, df2, df1); end; lReport.Add('F test for equal variances = %.3f, Probability = %.4f', [F, fp]); end else // dependent t test begin stderr1 := sqrt(variance1 / ncases1); Stderr2 := sqrt(variance2 / ncases2); lReport.Add('Variable Mean Variance Std.Dev. S.E.Mean N'); lReport.Add('%-10s%8.2f %8.2f %8.2f %8.2f %d', [Label1Str, mean1, variance1, stddev1, stderr1, ncases1]); lReport.Add('%-10s%8.2f %8.2f %8.2f %8.2f %d', [Label2Str, mean2, variance2, stddev2, stderr2, ncases2]); lReport.Add(''); sedif := variance1 + variance2 - (2.0 * cov12); sedif := sqrt(sedif / ncases1); tequal := Dif / sedif; df := ncases1 - 1; tprobability := probt(tequal,df); lReport.Add('Assuming dependent samples, t = %.3f with probability = %.4f and %.0f degrees of freedom', [ tequal, tprobability, df ]); lReport.Add('Correlation between %s and %s = %.3f', [Label1Str, Label2Str, r12]); lReport.Add('Difference = %.2f and Standard Error of difference = %.2f', [dif, sedif]); tconfint := inverset(ConfInt,df); lowci := dif - tconfint * sedif; hici := dif + tconfint * sedif; lReport.Add('Confidence interval = (%.2f ... %.2f)', [lowci, hici]); tequal := variance1 - variance2; tequal := tequal / sqrt( (4 * variance1 * variance2)/(ncases1 - 2) * (1.0 - sqr(r12)) ); df := ncases1 - 2; tprobability := probt(tequal,df); lReport.Add('t for test of equal variances = %.3f with probability = %.4f', [tequal, tprobability]); end; FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TTtestForm.DataEntryGroupClick(Sender: TObject); var index: integer; begin index := DataEntryGroup.ItemIndex; Notebook.PageIndex := index; gridData := (index = 1); if index = 1 then begin if DepIndepGroup.ItemIndex = 1 then Var2Edit.Visible := true else Var2Edit.Visible := false; end; GroupCodeChk.Visible := independent; GroupCodeGroup.Visible := independent; Var2Label.Visible := Var2Edit.Visible; Var2In.Visible := Var2Edit.Visible; Var2Out.Visible := Var2Edit.Visible; GrpEdit.Visible := not Var2Edit.Visible; GrpLabel.Visible := GrpEdit.Visible; GrpIn.Visible := GrpEdit.Visible; GrpOut.Visible := GrpEdit.Visible; end; procedure TTtestForm.DepIndepGroupClick(Sender: TObject); begin independent := (DepIndepGroup.ItemIndex = 0); GrpEdit.Visible := independent; GrpLabel.Visible := independent; GrpIn.Visible := independent; GrpOut.Visible := independent; GroupCodeChk.Visible := independent; GroupCodeGroup.Visible := independent; Var2Label.Visible := not independent; Var2Edit.Visible := not independent; Var2In.Visible := not independent; Var2Out.Visible := not independent; Cor12Edit.Visible := not independent; Cor12Label.Visible := not independent; end; function TtTestForm.Eval_DependentGridData(out AMean1, AMean2, AVariance1, AVariance2, AStdDev1, AStdDev2, ACovar12, r12: Double; out ANumCases1, ANumCases2: Integer): Boolean; var colNoSelected: IntDyneVec = nil; values1: DblDyneVec = nil; values2: DblDyneVec = nil; begin SetLength(colNoSelected, 2); colNoSelected[0] := GetVariableIndex(OS3MainFrm.DataGrid, Var1Edit.Text); colNoSelected[1] := GetVariableIndex(OS3MainFrm.DataGrid, Var2Edit.Text); values1 := CollectVecValues(OS3MainFrm.DataGrid, colNoSelected[0], colNoSelected); values2 := CollectVecValues(OS3MainFrm.DataGrid, colNoSelected[1], colNoSelected); ANumCases1 := Length(values1); ANumCases2 := Length(values2); if ANumCases1 <> ANumCases2 then begin ErrorMsg('Both variables must have the same count of cases.'); Result := false; exit; end; if ANumCases1 < 2 then begin ErrorMsg('There must be at least two cases.'); Result := false; exit; end; VecMeanVarStdDev(values1, AMean1, AVariance1, AStdDev1); VecMeanVarStdDev(values2, AMean2, AVariance2, AStdDev2); ACovar12 := (values1 - AMean1) * (values2 - AMean2) / (ANumCases1-1); r12 := ACovar12 / (AStdDev1 * AStdDev2); Result := true; end; function TtTestForm.Eval_IndependentGridData(out AMean1, AMean2, AVariance1, AVariance2, AStdDev1, AStdDev2: Double; out ANumCases1, ANumCases2: Integer; out ALabel1Str, ALabel2Str: String): Boolean; var colNoSelected: IntDyneVec = nil; values1: DblDyneVec = nil; values2: DblDyneVec = nil; grp1, grp2: Integer; minf, maxf: Double; begin SetLength(colNoSelected, 2); colNoSelected[0] := GetVariableIndex(OS3MainFrm.DataGrid, Var1Edit.Text); colNoSelected[1] := GetVariableIndex(OS3MainFrm.DataGrid, GrpEdit.Text); if GroupCodeChk.Checked then begin grp1 := StrToInt(Grp1CodeEdit.Text); grp2 := StrToInt(Grp2CodeEdit.Text); end else begin GetColMinMax(OS3MainFrm.DataGrid, colNoSelected[1], colNoSelected, minf, maxf); grp1 := round(minf); grp2 := round(maxf); end; // Values in group with code given by grp1 values1 := CollectFilteredVecValues(OS3MainFrm.DataGrid, colNoSelected[0], colNoSelected[1], grp1, colNoSelected); // Values in group with code given by grp2 values2 := CollectFilteredVecValues(OS3MainFrm.DataGrid, colNoSelected[0], colNoSelected[1], grp2, colNoSelected); ANumCases1 := Length(values1); ANumCases2 := Length(values2); if ANumCases1 <> ANumCases2 then begin ErrorMsg('Both variables must have the same count of cases.'); Result := false; exit; end; if ANumCases1 < 2 then begin ErrorMsg('There must be at least 2 cases.'); Result := false; exit; end; VecMeanVarStdDev(values1, AMean1, AVariance1, AStdDev1); VecMeanVarStdDev(values2, AMean2, AVariance2, AStdDev2); ALabel1Str := format('Group %d', [grp1]); ALabel2Str := format('Group %d', [grp2]); Result := true; end; procedure TTtestForm.GroupCodeChkChange(Sender: TObject); begin Grp1CodeEdit.Enabled := GroupCodeChk.Checked; Grp2CodeEdit.Enabled := GroupCodeChk.Checked; GrpCodeLabel1.Enabled := GroupCodeChk.Checked; GrpCodeLabel2.Enabled := GroupCodeChk.Checked; end; procedure TTtestForm.GrpInClick(Sender: TObject); var index: Integer; begin index := VarList.ItemIndex; if (index > -1) and (GrpEdit.Text = '') then begin GrpEdit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TTtestForm.GrpOutClick(Sender: TObject); begin if GrpEdit.Text <> '' then begin VarList.Items.Add(GrpEdit.Text); GrpEdit.Text := ''; UpdateBtnStates; end; end; procedure TTtestForm.Reset; var i: integer; begin inherited; DataEntryGroup.ItemIndex := 0; DepIndepGroup.ItemIndex := 0; Notebook.PageIndex := DataEntryGroup.ItemIndex; independent := true; griddata := false; VarList.Clear; for i := 1 to NoVariables do VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); Var1Edit.Clear; Var2Edit.Clear; Mean1Edit.Clear; Mean2Edit.Clear; SD1Edit.Clear; SD2Edit.Clear; N1Edit.Clear; N2Edit.Clear; Cor12Edit.Clear; GroupCodeChk.Checked := false; GrpEdit.Clear; Grp1CodeEdit.Clear; Grp2CodeEdit.Clear; DepIndepGroupClick(nil); UpdateBtnStates; end; procedure TTtestForm.UpdateBtnStates; begin inherited UpdateBtnStates; Var1In.Enabled := (Var1Edit.Text = '') and (VarList.ItemIndex > -1); Var2In.Enabled := (Var2Edit.Text = '') and (VarList.ItemIndex > -1); GrpIn.Enabled := (GrpEdit.Text = '') and (VarList.ItemIndex > -1); Var1Out.Enabled := Var1Edit.Text <> ''; Var2Out.Enabled := Var2Edit.Text <> ''; GrpOut.Enabled := GrpEdit.Text <> ''; end; function TTtestForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; var n: Integer; x: Double; begin Result := false; AControl := nil; AMsg := ''; if Notebook.PageIndex = 0 then begin if (Mean1Edit.Text = '') or not TryStrToFloat(Mean1Edit.Text, x) then begin AControl := Mean1Edit; AMsg := 'Invalid input for the mean of sample 1'; exit; end; if (SD1Edit.Text = '') or not TryStrToFloat(SD1Edit.Text, x) or (x <= 0) then begin AControl := SD1Edit; AMsg := 'Invald input for the standard deviation of sample 1'; exit; end; if (N1Edit.Text = '') or not TryStrToInt(N1Edit.Text, n) or (n <= 0) then begin AControl := N1Edit; AMsg := 'Invald input for the size of sample 1'; exit; end; if (Mean2Edit.Text = '') or not TryStrToFloat(Mean2Edit.Text, x) then begin AControl := Mean2Edit; AMsg := 'Invalid input for the mean of sample 2'; exit; end; if (SD2Edit.Text = '') or not TryStrToFloat(SD2Edit.Text, x) or (x <= 0) then begin AControl := SD2Edit; AMsg := 'Invald input for the standard deviation of sample 2'; exit; end; if (N2Edit.Text = '') or not TryStrToInt(N2Edit.Text, n) or (n <= 0) then begin AControl := N2Edit; AMsg := 'Invald input for the size of sample 2'; exit; end; end else if Notebook.PageIndex = 1 then begin if (Var1Edit.Text = '') then begin AControl := Var1Edit; AMsg := 'Variable 1 not specified.'; exit; end; if Var2Edit.Visible and (Var2Edit.Text = '') then begin AControl := Var2Edit; AMsg := 'Variable 2 not specified.'; exit; end; if GrpEdit.Visible and (GrpEdit.Text = '') then begin AControl := GrpEdit; AMsg := 'Group variable not specified.'; exit; end; if GroupCodeChk.Checked then begin if Grp1CodeEdit.Visible and ((Grp1CodeEdit.Text = '') or not TryStrToInt(Grp1CodeEdit.Text, n)) then begin AControl := Grp1CodeEdit; AMsg := 'Code for group 1 missing.'; exit; end; if Grp2CodeEdit.Visible and ((Grp2CodeEdit.Text = '') or not TryStrToInt(Grp2CodeEdit.Text, n))then begin AControl := Grp2CodeEdit; AMsg := 'Code for group 2 missing.'; exit; end; end; end; Result := true; end; procedure TTtestForm.Var1InClick(Sender: TObject); var index: Integer; begin index := VarList.ItemIndex; if (index > -1) and (Var1Edit.Text = '') then begin Var1Edit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TTtestForm.Var1OutClick(Sender: TObject); begin if Var1Edit.Text <> '' then begin VarList.Items.Add(Var1Edit.Text); Var1Edit.Text := ''; UpdateBtnStates; end; end; procedure TTtestForm.Var2InClick(Sender: TObject); var index: Integer; begin index := VarList.ItemIndex; if (index > -1) and (Var2Edit.Text = '') then begin Var2Edit.Text := VarList.Items[index]; VarList.Items.Delete(index); UpdateBtnStates; end; end; procedure TTtestForm.Var2OutClick(Sender: TObject); begin if Var2Edit.Text <> '' then begin VarList.Items.Add(Var2Edit.Text); Var2Edit.Text := ''; UpdateBtnStates; end; end; procedure TTtestForm.VarListDblClick(Sender: TObject); var index: integer; s: String; begin index := VarList.ItemIndex; if index > -1 then begin s := VarList.Items[index]; if Var1Edit.Text = '' then begin Var1Edit.Text := s; VarList.Items.Delete(index); end else if independent then begin if GrpEdit.Text = '' then begin GrpEdit.Text := s; VarList.Items.Delete(index); end; end else begin if Var2Edit.Text = '' then begin Var2Edit.Text := s; VarList.Items.Delete(index); end; end; UpdateBtnStates; end; end; procedure TTtestForm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; end.