You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7846 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1041 lines
30 KiB
ObjectPascal
1041 lines
30 KiB
ObjectPascal
unit KaplanMeierUnit;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
|
|
StdCtrls, ExtCtrls, Buttons, Clipbrd,
|
|
TASources, TAChartAxis,
|
|
MainUnit, Globals, FunctionsLib, BasicStatsReportAndChartFormUnit;
|
|
|
|
type
|
|
|
|
{ TKaplanMeierForm }
|
|
|
|
TKaplanMeierForm = class(TBasicStatsReportAndChartForm)
|
|
TimeInBtn: TBitBtn;
|
|
TimeOutBtn: TBitBtn;
|
|
EventInBtn: TBitBtn;
|
|
EventOutBtn: TBitBtn;
|
|
GroupInBtn: TBitBtn;
|
|
GroupOutBtn: TBitBtn;
|
|
TimeVarEdit: TEdit;
|
|
Label1: TLabel;
|
|
TimeVarLabel: TLabel;
|
|
EventVarLabel: TLabel;
|
|
GroupVarLabel: TLabel;
|
|
EventVarEdit: TEdit;
|
|
GroupVarEdit: TEdit;
|
|
VarList: TListBox;
|
|
procedure EventInBtnClick(Sender: TObject);
|
|
procedure EventOutBtnClick(Sender: TObject);
|
|
procedure GroupInBtnClick(Sender: TObject);
|
|
procedure GroupOutBtnClick(Sender: TObject);
|
|
procedure TimeInBtnClick(Sender: TObject);
|
|
procedure TimeOutBtnClick(Sender: TObject);
|
|
procedure VarListDblClick(Sender: TObject);
|
|
procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean);
|
|
|
|
private
|
|
FExperimentalAxis: TChartAxis;
|
|
FControlAxis: TChartAxis;
|
|
FExperimentalSource: TListChartSource;
|
|
FControlSource: TListChartSource;
|
|
procedure PlotXY(const XPoints: IntDyneVec; const Y1Points, Y2Points: DblDyneVec;
|
|
const Dropped, Dropped2: IntDyneVec; N: Integer);
|
|
|
|
protected
|
|
procedure AdjustConstraints; override;
|
|
procedure Compute; override;
|
|
procedure UpdateBtnStates; override;
|
|
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure Reset; override;
|
|
end;
|
|
|
|
var
|
|
KaplanMeierForm: TKaplanMeierForm;
|
|
|
|
|
|
implementation
|
|
|
|
{$R *.lfm}
|
|
|
|
uses
|
|
Math,
|
|
TAChartUtils, TAChartAxisUtils, TACustomSeries,
|
|
GridProcs, {BlankFrmUnit, } MatrixUnit, ChartFrameUnit;
|
|
|
|
const
|
|
EXPERIMENTAL_CAPTION = 'Experimental';
|
|
CONTROL_CAPTION = 'Control';
|
|
|
|
|
|
{ TKaplanMeierForm }
|
|
|
|
constructor TKaplanMeierForm.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
{
|
|
if BlankFrm = nil then
|
|
Application.CreateForm(TBlankFrm, BlankFrm);
|
|
}
|
|
|
|
FChartFrame.SetTitle('SURVIVAL CURVE');
|
|
FChartFrame.SetXTitle('Time');
|
|
FChartFrame.SetYTitle('Probability');
|
|
FChartFrame.Chart.BottomAxis.Margin := 20;
|
|
|
|
FControlSource := TListChartSource.Create(FChartFrame.Chart);
|
|
FControlAxis := FChartFrame.Chart.AxisList.Add;
|
|
with FControlAxis do
|
|
begin
|
|
Alignment := calBottom;
|
|
Marks.Source := FControlSource;
|
|
Marks.Style := smsValue;
|
|
Marks.LabelFont.Color := DATA_COLORS[1];
|
|
Title.Caption := CONTROL_CAPTION;
|
|
Title.Visible := true;
|
|
Title.LabelFont.Color := DATA_COLORS[1];
|
|
AxisPen.Color := DATA_COLORS[1];
|
|
AxisPen.Visible := true;
|
|
Grid.Visible := false;
|
|
TickLength := 0;
|
|
Index := 0;
|
|
end;
|
|
|
|
FExperimentalSource := TListChartSource.Create(FChartFrame.Chart);
|
|
FExperimentalAxis := FChartFrame.Chart.AxisList.Add;
|
|
with FExperimentalAxis do
|
|
begin
|
|
Alignment := calBottom;
|
|
Marks.Source := FExperimentalSource;
|
|
Marks.Style := smsValue;
|
|
Marks.LabelFont.Color := DATA_COLORS[0];
|
|
Title.Caption := EXPERIMENTAL_CAPTION;
|
|
Title.Visible := true;
|
|
Title.LabelFont.Color := DATA_COLORS[0];
|
|
AxisPen.Color := DATA_COLORS[0];
|
|
AxisPen.Visible := true;
|
|
Grid.Visible := false;
|
|
TickLength := 0;
|
|
Margin := 20;
|
|
Index := 1;
|
|
end;
|
|
|
|
PageControl.ActivePageIndex := 0;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.AdjustConstraints;
|
|
begin
|
|
inherited;
|
|
ParamsPanel.Constraints.MinWidth := Max(
|
|
4*CloseBtn.Width + 3*CloseBtn.BorderSpacing.Left,
|
|
GroupVarLabel.Width*2 + GroupInBtn.Width + 2*VarList.BorderSpacing.Right
|
|
);
|
|
ParamsPanel.Constraints.MinHeight := EventOutBtn.Top + EventOutBtn.Height +
|
|
VarList.BorderSpacing.Bottom +
|
|
ButtonBevel.Height + CloseBtn.BorderSpacing.Top + CloseBtn.Height;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.GroupInBtnClick(Sender: TObject);
|
|
var
|
|
i: integer;
|
|
begin
|
|
i := VarList.ItemIndex;
|
|
if (i > -1) and (GroupVarEdit.Text = '') then
|
|
begin
|
|
GroupVarEdit.Text := VarList.Items[i];
|
|
VarList.Items.Delete(i);
|
|
end;
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.GroupOutBtnClick(Sender: TObject);
|
|
begin
|
|
if GroupVarEdit.Text <> '' then
|
|
begin
|
|
VarList.Items.Add(GroupVarEdit.Text);
|
|
GroupVarEdit.Text := '';
|
|
end;
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.EventInBtnClick(Sender: TObject);
|
|
var
|
|
i: integer;
|
|
begin
|
|
i := VarList.ItemIndex;
|
|
if (i > -1) and (EventVarEdit.Text = '') then
|
|
begin
|
|
EventVarEdit.Text := VarList.Items[i];
|
|
VarList.Items.Delete(i);
|
|
end;
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
procedure TKaplanMeierForm.EventOutBtnClick(Sender: TObject);
|
|
begin
|
|
if EventVarEdit.Text <> '' then
|
|
begin
|
|
VarList.Items.Add(EventVarEdit.Text);
|
|
EventVarEdit.Text := '';
|
|
end;
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.Compute;
|
|
var
|
|
TwoGroups : boolean;
|
|
Size1, Size2, TotalSize, NoDeaths, ThisTime: integer;
|
|
minTime, maxTime, tempInt, noPoints, tempValue: integer;
|
|
NoCensored, noCats, i, j, k, icase, oldtime, pos, first, last : integer;
|
|
noinexp, noincntrl, count, TimeCol, DeathsCol: integer;
|
|
GroupCol : integer;
|
|
cumprop, proportion, term1, term2, term3 : double;
|
|
E1, E2, O1, O2, Chisquare, ProbChi, Risk, LogRisk, SELogRisk : double;
|
|
HiConf, LowConf, HiLogLevel, LowLogLevel, lastexp, lastctr : double;
|
|
TimePlot: IntDyneVec = nil;
|
|
Dropped: IntDyneVec = nil;
|
|
Dropped2: IntDyneVec = nil;
|
|
Time: IntDyneVec = nil;
|
|
AtRisk: IntDyneVec = nil;
|
|
Dead: IntDyneVec = nil;
|
|
SurvivalTimes: IntDyneVec = nil;
|
|
ExpCnt: IntDyneVec = nil;
|
|
CntrlCnt: IntDyneVec = nil;
|
|
TotalatRisk: IntDyneVec = nil;
|
|
ExpatRisk: IntDyneVec = nil;
|
|
CntrlatRisk: IntDyneVec = nil;
|
|
Deaths: IntDyneVec = nil;
|
|
Group: IntDyneVec = nil;
|
|
Censored: IntDyneVec = nil;
|
|
ProbPlot: DblDyneVec = nil;
|
|
ProbPlot2: DblDyneVec = nil;
|
|
CondProb: DblDyneVec = nil;
|
|
ExpProp: DblDyneVec = nil;
|
|
CntrlProp: DblDyneVec = nil;
|
|
CumPropExp: DblDyneVec = nil;
|
|
CumPropCntrl: DblDyneVec = nil;
|
|
lReport: TStrings;
|
|
begin
|
|
// Get variable columns
|
|
TimeCol := GetVariableIndex(OS3MainFrm.DataGrid, TimeVarEdit.Text);
|
|
DeathsCol := GetVariableIndex(OS3MainFrm.DataGrid, EventVarEdit.Text);
|
|
GroupCol := GetVariableIndex(OS3MainFrm.DataGrid, GroupVarEdit.Text);
|
|
|
|
if (TimeCol = -1) or (DeathsCol = -1) then
|
|
begin
|
|
MessageDlg('One or more variables not selected.', mtError, [mbOK], 0);
|
|
exit;
|
|
end;
|
|
|
|
if (GroupVarEdit.Text = '') then
|
|
begin
|
|
TwoGroups := false;
|
|
Size1 := NoCases;
|
|
Size2 := 0;
|
|
end else
|
|
begin
|
|
Size1 := 0;
|
|
Size2 := 0;
|
|
TwoGroups := true;
|
|
for i := 1 to NoCases do
|
|
begin
|
|
if (StrToInt(OS3MainFrm.DataGrid.Cells[GroupCol,i]) = 1) then
|
|
Size1 := Size1 + 1
|
|
else
|
|
Size2 := Size2 + 1;
|
|
end;
|
|
end;
|
|
|
|
// allocate space for the data
|
|
SetLength(SurvivalTimes, NoCases+2);
|
|
SetLength(ExpCnt, NoCases+2);
|
|
SetLength(CntrlCnt, NoCases+2);
|
|
SetLength(TotalatRisk, NoCases+2);
|
|
SetLength(ExpatRisk, NoCases+2);
|
|
SetLength(CntrlatRisk, NoCases+2);
|
|
SetLength(ExpProp, NoCases+2);
|
|
SetLength(CntrlProp, NoCases+2);
|
|
SetLength(Deaths, NoCases+2);
|
|
SetLength(Group, NoCases+2);
|
|
SetLength(Censored, NoCases+2);
|
|
SetLength(CumPropExp, NoCases+2);
|
|
SetLength(CumPropCntrl, NoCases+2);
|
|
|
|
// initialize arrays
|
|
for i := 0 to NoCases+1 do
|
|
begin
|
|
SurvivalTimes[i] := 0;
|
|
ExpCnt[i] := 0;
|
|
CntrlCnt[i] := 0;
|
|
TotalatRisk[i] := 0;
|
|
ExpatRisk[i] := 0;
|
|
CntrlatRisk[i] := 0;
|
|
ExpProp[i] := 0.0;
|
|
CntrlProp[i] := 0.0;
|
|
Deaths[i] := 0;
|
|
Group[i] := 0;
|
|
Censored[i] := 0;
|
|
CumPropExp[i] := 0.0;
|
|
CumPropCntrl[i] := 0.0;
|
|
end;
|
|
|
|
// Get Data
|
|
mintime := 0;
|
|
maxtime := 0;
|
|
|
|
if not TwoGroups then
|
|
begin
|
|
for i := 1 to NoCases do
|
|
begin
|
|
SurvivalTimes[i] := StrToInt(OS3MainFrm.DataGrid.Cells[TimeCol,i]);
|
|
if (SurvivalTimes[i] > maxtime) then
|
|
maxtime := SurvivalTimes[i];
|
|
tempvalue := StrToInt(OS3MainFrm.DataGrid.Cells[DeathsCol,i]);
|
|
if (tempvalue = 1) then
|
|
Deaths[i] := 1
|
|
else
|
|
Deaths[i] := 0;
|
|
if (tempvalue = 2) then
|
|
Censored[i] := 1
|
|
else
|
|
Censored[i] := 0;
|
|
end;
|
|
|
|
// sort cases by time
|
|
for i := 0 to NoCases - 1 do
|
|
begin
|
|
for j := i + 1 to NoCases do
|
|
begin
|
|
if (SurvivalTimes[i] > SurvivalTimes[j]) then
|
|
begin
|
|
Exchange(SurvivalTimes[i], SurvivalTimes[j]);
|
|
Exchange(Censored[i], Censored[j]);
|
|
Exchange(Deaths[i], Deaths[j]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// get number censored in each time slot
|
|
nopoints := maxtime + 1;
|
|
SetLength(Dropped, nopoints+2);
|
|
SetLength(Dropped2, nopoints+2);
|
|
for j := 0 to nopoints do
|
|
begin
|
|
Dropped[j] := 0;
|
|
Dropped2[j] := 0;
|
|
end;
|
|
ThisTime := SurvivalTimes[0];
|
|
for i := 0 to NoCases do
|
|
begin
|
|
if (ThisTime = SurvivalTimes[i]) then
|
|
begin
|
|
if(Censored[i] > 0) then
|
|
begin
|
|
tempint := SurvivalTimes[i] - mintime;
|
|
Dropped[tempint] := Dropped[tempint] + Censored[i];
|
|
end;
|
|
end
|
|
else // new time
|
|
begin
|
|
ThisTime := SurvivalTimes[i];
|
|
if(Censored[i] > 0) then
|
|
begin
|
|
tempint := SurvivalTimes[i] - mintime;
|
|
Dropped[tempint] := Dropped[tempint] + Censored[i];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// calculate expected proportions and adjust survival counts
|
|
cumprop := 1.0;
|
|
ExpCnt[0] := NoCases;
|
|
ExpProp[0] := 1.0;
|
|
CumPropExp[0] := 1.0;
|
|
|
|
// collapse deaths and censored into first time occurance
|
|
icase := 0;
|
|
oldtime := SurvivalTimes[0];
|
|
for i := 1 to NoCases do
|
|
begin
|
|
if (SurvivalTimes[i] <> oldtime) then
|
|
begin
|
|
oldtime := SurvivalTimes[i];
|
|
icase := i;
|
|
end;
|
|
|
|
// find no. of deaths at this time
|
|
NoDeaths := Deaths[i];
|
|
for j := i+1 to NoCases do
|
|
begin
|
|
ThisTime := SurvivalTimes[j];
|
|
if ((Deaths[j] > 0) and (oldtime = ThisTime)) then
|
|
begin
|
|
NoDeaths := NoDeaths + Deaths[j];
|
|
Deaths[icase] := Deaths[icase] + Deaths[j];
|
|
Deaths[j] := 0;
|
|
end;
|
|
end;
|
|
// find no. of censored at this time
|
|
NoCensored := Censored[i];
|
|
for j := i+1 to NoCases do
|
|
begin
|
|
ThisTime := SurvivalTimes[j];
|
|
if((Censored[j] > 0) and (oldtime = ThisTime)) then
|
|
begin
|
|
NoCensored := NoCensored + Censored[j];
|
|
Censored[icase] := Censored[icase] + Censored[j];
|
|
Censored[j] := 0;
|
|
end;
|
|
end;
|
|
end;
|
|
{
|
|
// debug check
|
|
FrmOutPut.RichOutPut.Clear();
|
|
for (int i := 0; i <= NoCases; i++)
|
|
begin
|
|
sprintf(outline,'case %d Day %d Deaths %d Censored %d',
|
|
i,SurvivalTimes[i], Deaths[i],Censored[i]);
|
|
FrmOutPut.RichOutPut.Lines.Add(outline);
|
|
end;
|
|
FrmOutPut.ShowModal();
|
|
}
|
|
// get no. of categories
|
|
nocats := 0;
|
|
for i := 0 to NoCases do
|
|
if (Deaths[i] > 0) or (Censored[i] > 0) then
|
|
nocats := nocats + 1;
|
|
SetLength(Time, nocats + 2);
|
|
SetLength(AtRisk, nocats + 2);
|
|
SetLength(Dead, nocats + 2);
|
|
SetLength(CondProb, nocats + 2);
|
|
for i := 0 to nocats do
|
|
begin
|
|
Time[i] := 0;
|
|
AtRisk[i] := 0;
|
|
Dead[i] := 0;
|
|
CondProb[i] := 0.0;
|
|
end;
|
|
pos := 0;
|
|
for i := 0 to NoCases do
|
|
begin
|
|
if (Deaths[i] > 0) or (Censored[i] > 0) then
|
|
begin
|
|
pos := pos + 1;
|
|
Time[pos] := SurvivalTimes[i];
|
|
Dead[pos] := Deaths[i];
|
|
Dropped[pos] := Censored[i];
|
|
end;
|
|
end;
|
|
|
|
Time[0] := 0;
|
|
AtRisk[0] := NoCases;
|
|
Dead[0] := 0;
|
|
Dropped[0] := 0;
|
|
CondProb[0] := 0.0;
|
|
|
|
for i := 1 to nocats do
|
|
begin
|
|
AtRisk[i] := AtRisk[i-1] - Dead[i-1] - Dropped[i-1];
|
|
CondProb[i-1] := 1.0 - Dead[i-1] / AtRisk[i-1];
|
|
end;
|
|
|
|
(*
|
|
lReport := TStringList.Create;
|
|
try
|
|
lReport.Add(' Time Censored Dead At Risk Probability');
|
|
for i := 1 to nocats do
|
|
begin
|
|
AtRisk[i] := AtRisk[i-1] - Dead[i-1] - Dropped[i-1];
|
|
CondProb[i-1] := 1.0 - Dead[i-1] / AtRisk[i-1];
|
|
end;
|
|
for i := 0 to nocats do
|
|
lReport.Add(' %3d %3d %3d %3d %6.3f', [Time[i], Dropped[i], Dead[i], AtRisk[i], CondProb[i]]);
|
|
DisplayReport(lReport);
|
|
finally
|
|
lReport.Free;
|
|
end;
|
|
*)
|
|
|
|
// Get cumulative proportions
|
|
for i := 0 to nocats do
|
|
begin
|
|
if (AtRisk[i] > 0) then
|
|
begin
|
|
CumPropExp[i] := cumprop * CondProb[i];
|
|
cumprop := CumPropExp[i];
|
|
end;
|
|
end;
|
|
cumprop := 1.0;
|
|
|
|
lReport := TStringList.Create;
|
|
try
|
|
lReport.Add('KAPLAN-MEIER SURVIVAL TEST');
|
|
lReport.Add('');
|
|
lReport.Add('No Control Group Method');
|
|
lReport.Add('');
|
|
lReport.Add('TIME NO.ALIVE CENSORED DEATHS COND. PROB. CUM.PROP.SURVIVING');
|
|
for i := 0 to nocats do
|
|
lReport.Add(' %4d %4d %4d %4d %7.4f %7.4f', [
|
|
Time[i], AtRisk[i], Dropped[i], Deaths[i], CondProb[i], CumPropExp[i]
|
|
]);
|
|
|
|
lReport.Add('');
|
|
lReport.Add(DIVIDER_AUTO);
|
|
lReport.Add('');
|
|
lReport.Add(' Time Censored Dead At Risk Probability');
|
|
for i := 0 to nocats do
|
|
lReport.Add(' %3d %3d %3d %3d %6.3f', [Time[i], Dropped[i], Dead[i], AtRisk[i], CondProb[i]]);
|
|
|
|
FReportFrame.DisplayReport(lReport);
|
|
|
|
finally
|
|
lReport.Free;
|
|
end;
|
|
|
|
// Plot X = Time, Y = cumulative proportion surviving
|
|
// Get points to plot
|
|
nopoints := maxtime + 1;
|
|
SetLength(TimePlot,nocats+2);
|
|
SetLength(ProbPlot,nocats+2);
|
|
ProbPlot[0] := 1.0;
|
|
for j := 0 to nocats do
|
|
begin
|
|
TimePlot[j] := Time[j];
|
|
ProbPlot[j] := CumPropExp[j];
|
|
end;
|
|
PlotXY(TimePlot, ProbPlot, nil, Dropped, Dropped, NoCats);
|
|
// BlankFrm.Show;
|
|
// PlotXY(TimePlot, ProbPlot, Dropped, Dropped2, maxtime, 0, 1.0, 0.0, nocats, 'TIME', 'PROBABILITY', 1);
|
|
|
|
end // end if not two groups
|
|
//============================================================================//
|
|
else // Experimental and control groups
|
|
begin
|
|
// obtain no. in experimental and control groups
|
|
ExpCnt[0] := Size1;
|
|
CntrlCnt[0] := Size2;
|
|
TotalSize := Size1 + Size2;
|
|
CumPropExp[0] := 1.0;
|
|
CumPropCntrl[0] := 1.0;
|
|
TotalatRisk[0] := TotalSize;
|
|
O1 := 0;
|
|
O2 := 0;
|
|
{
|
|
ShowMessage(Format('Total Group 1 = %d, Total Group 2 = %d, Grand Total = %d',
|
|
[ ExpCnt[0], CntrlCnt[0], TotalSize ]));
|
|
}
|
|
// Now read values. Note storage starts in 1, not 0!
|
|
for i := 1 to NoCases do
|
|
begin
|
|
SurvivalTimes[i] := StrToInt(OS3MainFrm.DataGrid.Cells[TimeCol,i]);
|
|
if (SurvivalTimes[i] > maxtime) then
|
|
maxtime := SurvivalTimes[i];
|
|
tempvalue := StrToInt(OS3MainFrm.DataGrid.Cells[DeathsCol,i]);
|
|
if (tempvalue = 1) then
|
|
Deaths[i] := 1
|
|
else
|
|
Deaths[i] := 0;
|
|
if (tempvalue = 2) then
|
|
Censored[i] := 1
|
|
else
|
|
Censored[i] := 0;
|
|
Group[i] := StrToInt(OS3MainFrm.DataGrid.Cells[GroupCol,i]);
|
|
end;
|
|
|
|
// sort cases by time
|
|
for i := 1 to NoCases - 1 do
|
|
begin
|
|
for j := i + 1 to NoCases do
|
|
begin
|
|
if (SurvivalTimes[i] > SurvivalTimes[j]) then
|
|
begin
|
|
Exchange(SurvivalTimes[i], SurvivalTimes[j]);
|
|
Exchange(Censored[i], Censored[j]);
|
|
Exchange(Deaths[i], Deaths[j]);
|
|
Exchange(Group[i], Group[j]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// sort cases within each time slot by deaths first then censored
|
|
ThisTime := SurvivalTimes[1];
|
|
first := 1;
|
|
last := 1;
|
|
for i := 1 to NoCases do
|
|
begin
|
|
if (ThisTime = SurvivalTimes[i]) then
|
|
begin
|
|
last := i;
|
|
continue;
|
|
end
|
|
else // sort the cases from first to last on event (descending)
|
|
begin
|
|
if (last > first) then // more than 1 to sort
|
|
begin
|
|
for j := first to last - 1 do
|
|
begin
|
|
for k := j + 1 to last do
|
|
begin
|
|
if (Deaths[j] < Deaths[k] ) then // swap
|
|
begin
|
|
Exchange(Censored[j], Censored[k]);
|
|
Exchange(Deaths[j], Deaths[k]);
|
|
Exchange(Group[j], Group[k]);
|
|
end;
|
|
end; // next k
|
|
end; // next j
|
|
end; // if last > first
|
|
end; // end else sort
|
|
|
|
first := last + 1;
|
|
ThisTime := SurvivalTimes[first];
|
|
last := first;
|
|
end; // next i
|
|
|
|
// get number censored in each time slot
|
|
nopoints := maxtime + 1;
|
|
SetLength(Dropped,nopoints+2);
|
|
SetLength(Dropped2,nopoints+2);
|
|
for j := 0 to nopoints do
|
|
begin
|
|
Dropped[j] := 0;
|
|
Dropped2[j] := 0;
|
|
end;
|
|
ThisTime := SurvivalTimes[1];
|
|
for i := 1 to NoCases do
|
|
begin
|
|
if (ThisTime = SurvivalTimes[i]) then
|
|
begin
|
|
if(Censored[i] > 0) then
|
|
begin
|
|
tempint := SurvivalTimes[i] - mintime;
|
|
if (Group[i] = 1) then
|
|
Dropped[tempint] := Dropped[tempint] + Censored[i]
|
|
else
|
|
Dropped2[tempint] := Dropped2[tempint] + Censored[i];
|
|
end;
|
|
end
|
|
else // new time
|
|
begin
|
|
ThisTime := SurvivalTimes[i];
|
|
if (Censored[i] > 0) then
|
|
begin
|
|
tempint := SurvivalTimes[i] - mintime;
|
|
if (Group[i] = 1) then
|
|
Dropped[tempint] := Dropped[tempint] + Censored[i]
|
|
else Dropped2[tempint] := Dropped2[tempint] + Censored[i];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
for i := 0 to NoCases do
|
|
begin
|
|
noinexp := 0;
|
|
noincntrl := 0;
|
|
if (Deaths[i] > 0) then
|
|
begin
|
|
// find no. of deaths at this time
|
|
NoDeaths := Deaths[i];
|
|
ThisTime := SurvivalTimes[i];
|
|
for j := i+1 to NoCases do
|
|
begin
|
|
if ((Deaths[j] > 0) and (SurvivalTimes[j] = ThisTime)) then
|
|
begin
|
|
NoDeaths := NoDeaths + Deaths[j];
|
|
Deaths[i] := Deaths[i] + Deaths[j];
|
|
Deaths[j] := 0;
|
|
end;
|
|
end;
|
|
if (TotalatRisk[i] > 0) then
|
|
begin
|
|
term1 := ExpCnt[i];
|
|
term2 := TotalatRisk[i];
|
|
term3 := NoDeaths;
|
|
ExpatRisk[i] := ceil((term1 / term2) * term3);
|
|
// ExpatRisk[i] := (ExpCnt[i]) / TotalatRisk[i]) * NoDeaths;
|
|
term1 := CntrlCnt[i];
|
|
CntrlatRisk[i] := ceil((term1 / term2) * term3);
|
|
// CntrlatRisk[i] := (CntrlCnt[i] / TotalatRisk[i]) * NoDeaths;
|
|
end;
|
|
if (i < NoCases-1) then
|
|
TotalAtRisk[i+1] := TotalAtRisk[i] - Deaths[i];
|
|
// find no. in exp. or control groups and decrement their counts
|
|
for j := 1 to NoCases do
|
|
begin
|
|
if (ThisTime = SurvivalTimes[j]) and (Censored[j] = 0) then
|
|
begin
|
|
if (Group[j] = 1) then
|
|
begin
|
|
noinexp := noinexp + 1;
|
|
O1 := O1 + 1;
|
|
end;
|
|
if (Group[j] = 2) then
|
|
begin
|
|
noincntrl := noincntrl + 1;
|
|
O2 := O2 + 1;
|
|
end;
|
|
end;
|
|
end;
|
|
if (i < NoCases) and (noinexp > 0) then
|
|
begin
|
|
term1 := ExpCnt[i];
|
|
term2 := noinexp;
|
|
term3 := ExpCnt[i];
|
|
ExpProp[i] := (term1 - term2) / term3;
|
|
// ExpProp[i] := (ExpCnt[i] - noinexp) / ExpCnt[i];
|
|
if (i > 0) then
|
|
CumPropExp[i] := CumPropExp[i-1] * ExpProp[i];
|
|
ExpCnt[i+1] := ExpCnt[i] - noinexp;
|
|
CumPropExp[i+1] := CumPropExp[i];
|
|
end;
|
|
if (i < NoCases) and (noinexp = 0) then
|
|
begin
|
|
ExpCnt[i+1] := ExpCnt[i];
|
|
CumPropExp[i+1] := CumPropExp[i];
|
|
end;
|
|
if (i < NoCases) and (noincntrl > 0) then
|
|
begin
|
|
term1 := CntrlCnt[i];
|
|
term2 := noincntrl;
|
|
term3 := CntrlCnt[i];
|
|
CntrlProp[i] := (term1 - term2) / term3;
|
|
// CntrlProp[i] := (CntrlCnt[i] - noincntrl) / CntrlCnt[i];
|
|
if (i > 0) then
|
|
CumPropCntrl[i] := CumPropCntrl[i-1] * CntrlProp[i];
|
|
CntrlCnt[i+1] := CntrlCnt[i] - noincntrl;
|
|
CumPropCntrl[i+1] := CumPropCntrl[i];
|
|
end;
|
|
if ( (i < NoCases) and (noincntrl = 0) ) then
|
|
begin
|
|
CntrlCnt[i+1] := CntrlCnt[i];
|
|
CumPropCntrl[i+1] := CumPropCntrl[i];
|
|
end;
|
|
end; // end if deaths[i] > 0
|
|
|
|
if ( (Censored[i] > 0) and (i < NoCases) ) then
|
|
begin
|
|
if (Group[i] = 1) then
|
|
begin
|
|
ExpCnt[i+1] := ExpCnt[i] - 1;
|
|
CntrlCnt[i+1] := CntrlCnt[i];
|
|
ExpProp[i+1] := ExpProp[i];
|
|
CumPropExp[i+1] := CumPropExp[i];
|
|
CumPropCntrl[i+1] := CumPropCntrl[i];
|
|
end;
|
|
if (Group[i] = 2) then
|
|
begin
|
|
CntrlCnt[i+1] := CntrlCnt[i] - 1;
|
|
ExpCnt[i+1] := ExpCnt[i];
|
|
CntrlProp[i+1] := CntrlProp[i];
|
|
CumPropCntrl[i+1] := CumPropCntrl[i];
|
|
CumPropExp[i+1] := CumPropExp[i];
|
|
end;
|
|
TotalatRisk[i+1] := TotalatRisk[i] - 1;
|
|
end;
|
|
|
|
if (Deaths[i] = 0) and (Censored[i] = 0) and (i < NoCases) then
|
|
begin
|
|
ExpCnt[i+1] := ExpCnt[i];
|
|
CntrlCnt[i+1] := CntrlCnt[i];
|
|
CumPropExp[i+1] := CumPropExp[i];
|
|
CumPropCntrl[i+1] := CumPropCntrl[i];
|
|
TotalatRisk[i+1] := TotalatRisk[i];
|
|
end;
|
|
end; // next case i
|
|
|
|
// Now calculate chisquare, relative risk (r), logr, and S.E. of log risk
|
|
E1 := 0.0;
|
|
for i := 0 to NoCases do E1 := E1 + ExpatRisk[i];
|
|
E2 := (O1 + O2) - E1;
|
|
Chisquare := ((O1 - E1) * (O1 - E1)) / E1 + ((O2 - E2) * (O2 - E2)) / E2;
|
|
ProbChi := chisquaredprob(Chisquare,1);
|
|
Risk := (O1 / E1) / (O2 / E2);
|
|
LogRisk := ln(Risk);
|
|
SELogRisk := sqrt(1.0/E1 + 1.0/E2);
|
|
HiConf := LogRisk + (inversez(0.975) * SELogRisk);
|
|
LowConf := LogRisk - (inversez(0.975) * SELogRisk);
|
|
HiLogLevel := exp(HiConf);
|
|
LowLogLevel := exp(LowConf);
|
|
end;
|
|
|
|
// Print Results
|
|
if TwoGroups then // both experimental and control groups
|
|
begin
|
|
lReport := TStringList.Create;
|
|
try
|
|
lReport.Add('KAPLAN-MEIER SURVIVAL TEST');
|
|
lReport.Add('');
|
|
lReport.Add('Comparison of Two Groups Methd');
|
|
lReport.Add('');
|
|
lReport.Add('TIME GROUP CENSORED TOTAL AT EVENTS AT RISK IN EXPECTED NO. AT RISK IN EXPECTED NO.');
|
|
lReport.Add(' RISK GROUP 1 EVENTS IN 1 GROUP 2 EVENTS IN 2');
|
|
|
|
for i := 1 to NoCases+1 do
|
|
lReport.Add('%4d %4d %4d %4d %4d %4d %7d %4d %7d', [
|
|
SurvivalTimes[i-1], Group[i-1], Censored[i-1], TotalAtRisk[i-1],
|
|
Deaths[i-1], ExpCnt[i-1], ExpAtRisk[i-1], CntrlCnt[i-1], CntrlAtRisk[i-1]
|
|
]);
|
|
|
|
lReport.Add('');
|
|
lReport.Add('');
|
|
lReport.Add('TIME DEATHS GROUP AT RISK PROPORTION CUMULATIVE');
|
|
lReport.Add(' SURVIVING PROP.SURVIVING');
|
|
|
|
for i := 1 to NoCases do
|
|
begin
|
|
if (Group[i] = 1) then
|
|
begin
|
|
count := ExpCnt[i];
|
|
proportion := ExpProp[i];
|
|
cumprop := CumPropExp[i];
|
|
end else
|
|
begin
|
|
count := CntrlCnt[i];
|
|
proportion := CntrlProp[i];
|
|
cumprop := CumPropCntrl[i];
|
|
end;
|
|
lReport.Add('%4d %4d %4d %4d %7.4f %7.4f', [
|
|
SurvivalTimes[i], Deaths[i], Group[i], count, proportion, cumprop
|
|
]);
|
|
end;
|
|
|
|
lReport.Add('');
|
|
lReport.Add('Total Expected Events for Experimental Group: %8.3f', [E1]);
|
|
lReport.Add('Observed Events for Experimental Group: %8.3f', [O1]);
|
|
lReport.Add('Total Expected Events for Control Group: %8.3f', [E2]);
|
|
lReport.Add('Observed Events for Control Group: %8.3f', [O2]);
|
|
lReport.Add('Chisquare: %8.3f', [ChiSquare]);
|
|
lReport.Add(' with probability: %8.3f', [ProbChi]);
|
|
lReport.Add('Risk: %8.3f', [Risk]);
|
|
lReport.Add('Log Risk: %8.3f', [LogRisk]);
|
|
lReport.Add('Std.Err. Log Risk: %8.3f', [SELogRisk]);
|
|
lReport.Add('95 Percent Confidence interval for Log Risk: (%.3f ... %.3f)', [LowConf, HiConf]);
|
|
lReport.Add('95 Percent Confidence interval for Risk: (%.3f ... %.3f)', [LowLogLevel, HiLogLevel]);
|
|
|
|
// Plot data output
|
|
lReport.Add('');
|
|
lReport.Add(DIVIDER_AUTO);
|
|
lReport.Add('');
|
|
lReport.Add('EXPERIMENTAL GROUP CUMULATIVE PROBABILITY');
|
|
lReport.Add('');
|
|
lReport.Add('CASE TIME DEATHS CENSORED CUM.PROB.');
|
|
for i := 1 to NoCases do
|
|
if (Group[i] = 1) then
|
|
lReport.Add('%3d %3d %3d %3d %5.3f',[
|
|
i, SurvivalTimes[i], Deaths[i], Censored[i], CumPropExp[i]
|
|
]);
|
|
lReport.Add('');
|
|
lReport.Add(DIVIDER_AUTO);
|
|
lReport.Add('');
|
|
lReport.Add('CONTROL GROUP CUMULATIVE PROBABILITY');
|
|
lReport.Add('');
|
|
lReport.Add('CASE TIME DEATHS CENSORED CUM.PROB.');
|
|
for i := 1 to NoCases do
|
|
if (Group[i] = 2) then
|
|
lReport.Add('%3d %3d %3d %3d %5.3f', [
|
|
i, SurvivalTimes[i], Deaths[i], Censored[i], CumPropCntrl[i]
|
|
]);
|
|
lReport.Add('');
|
|
|
|
FReportFrame.DisplayReport(lReport);
|
|
finally
|
|
lReport.Free;
|
|
end;
|
|
end; // if 2 groups and printit
|
|
|
|
// Plot cumulative proportion surviving (Y) against time (X)
|
|
nopoints := maxtime + 1;
|
|
SetLength(TimePlot, nopoints+2);
|
|
SetLength(ProbPlot, nopoints+2);
|
|
SetLength(ProbPlot2, nopoints+2);
|
|
ProbPlot[0] := 1.0;
|
|
ProbPlot2[0] := 1.0;
|
|
lastexp := 1.0;
|
|
lastctr := 1.0;
|
|
for i := 0 to nopoints do
|
|
begin
|
|
TimePlot[i] := 0;
|
|
ProbPlot[i] := 1.0;
|
|
ProbPlot2[i] := 1.0;
|
|
end;
|
|
TimePlot[0] := 0;
|
|
mintime := 0;
|
|
for i := 1 to nopoints do
|
|
begin
|
|
TimePlot[i] := i;
|
|
for j := 1 to NoCases do
|
|
begin
|
|
if (SurvivalTimes[j] = i) then
|
|
begin
|
|
if (Group[j] = 1) then
|
|
begin
|
|
ProbPlot[i] := CumPropExp[j]; // ExpProp[j];
|
|
lastexp := CumPropExp[j]; // ExpProp[j];
|
|
end;
|
|
if (Group[j] = 2) then
|
|
begin
|
|
ProbPlot2[i] := CumPropCntrl[j]; //CntrlProp[j];
|
|
lastctr := CumPropCntrl[j]; // CntrlProp[j];
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
if (Group[j] = 1) then ProbPlot[i] := lastexp;
|
|
if (Group[j] = 2) then ProbPlot2[i] := lastctr;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
PlotXY(TimePlot, ProbPlot, ProbPlot2, Dropped, Dropped2, NoPoints);
|
|
// BlankFrm.Image1.Canvas.Clear;
|
|
// BlankFrm.Show;
|
|
// PlotXY(TimePlot, ProbPlot, Dropped, Dropped2, maxtime, 0, 1.0, 0.0, nopoints, 'TIME', 'PROBABILITY', 1);
|
|
// PlotXY(TimePlot, ProbPlot2, Dropped, Dropped2, maxtime, 0, 1.0, 0.0, nopoints, 'TIME', 'PROBABILITY', 2);
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.PlotXY(const XPoints: IntDyneVec;
|
|
const Y1Points, Y2Points: DblDyneVec; const Dropped, Dropped2: IntDyneVec; N: Integer);
|
|
var
|
|
i: Integer;
|
|
ser: TChartSeries;
|
|
begin
|
|
// do not call FChartFrame.Clear which will delete the axis titles
|
|
FChartFrame.Chart.ClearSeries;
|
|
FExperimentalSource.Clear;
|
|
FControlSource.Clear;
|
|
|
|
if Y1Points <> nil then
|
|
begin
|
|
ser := FChartFrame.PlotXY(ptStairsYX, nil, nil, nil, nil, EXPERIMENTAL_CAPTION, DATA_COLORS[0]);
|
|
// add points manually due to overdimensioned data arrays...
|
|
for i := 0 to N-1 do
|
|
ser.AddXY(XPoints[i], Y1Points[i]);
|
|
end;
|
|
|
|
if Y2Points <> nil then
|
|
begin
|
|
ser := FChartFrame.PlotXY(ptStairsYX, nil, nil, nil, nil, CONTROL_CAPTION, DATA_COLORS[1]);
|
|
for i := 0 to N-1 do
|
|
ser.AddXY(XPoints[i], Y2Points[i]);
|
|
end;
|
|
|
|
if Dropped <> nil then
|
|
for i := 0 to N do
|
|
if Dropped[i] <> 0 then
|
|
FExperimentalSource.Add(XPoints[i], Dropped[i]);
|
|
|
|
if Dropped2 <> nil then
|
|
for i := 0 to N do
|
|
if Dropped2[i] <> 0 then
|
|
FControlSource.Add(XPoints[i], Dropped2[i]);
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.Reset;
|
|
var
|
|
i: integer;
|
|
begin
|
|
inherited;
|
|
|
|
VarList.Clear;
|
|
for i := 1 to NoVariables do
|
|
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
|
|
TimeVarEdit.Clear;
|
|
EventVarEdit.Clear;
|
|
GroupVarEdit.Clear;
|
|
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.TimeInBtnClick(Sender: TObject);
|
|
var
|
|
i: integer;
|
|
begin
|
|
i := VarList.ItemIndex;
|
|
if (i > -1) and (TimeVarEdit.Text = '') then
|
|
begin
|
|
TimeVarEdit.Text := VarList.Items[i];
|
|
VarList.Items.Delete(i);
|
|
end;
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.TimeOutBtnClick(Sender: TObject);
|
|
begin
|
|
if TimeVarEdit.Text <> '' then
|
|
begin
|
|
VarList.Items.Add(TimeVarEdit.Text);
|
|
TimeVarEdit.Text := '';
|
|
end;
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.UpdateBtnStates;
|
|
var
|
|
lSelected: Boolean;
|
|
i: Integer;
|
|
begin
|
|
inherited;
|
|
|
|
lSelected := false;
|
|
for i := 0 to VarList.Count-1 do
|
|
if VarList.Selected[i] then
|
|
begin
|
|
lSelected := true;
|
|
break;
|
|
end;
|
|
|
|
TimeInBtn.Enabled := lSelected and (TimeVarEdit.Text = '');
|
|
EventInBtn.Enabled := lSelected and (EventVarEdit.Text = '');
|
|
GroupInBtn.Enabled := lSelected and (GroupVarEdit.Text = '');
|
|
TimeOutBtn.Enabled := (TimeVarEdit.Text <> '');
|
|
EventOutBtn.Enabled := (EventVarEdit.Text <> '');
|
|
GroupOutBtn.Enabled := (GroupVarEdit.Text <> '');
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.VarListDblClick(Sender: TObject);
|
|
var
|
|
index: Integer;
|
|
s: String;
|
|
begin
|
|
index := VarList.ItemIndex;
|
|
if index > -1 then
|
|
begin
|
|
s := VarList.Items[index];
|
|
if GroupVarEdit.Text = '' then
|
|
GroupVarEdit.Text := s
|
|
else if TimeVarEdit.Text = '' then
|
|
TimeVarEdit.Text := s
|
|
else if EventVarEdit.Text = '' then
|
|
EventVarEdit.Text := s;
|
|
VarList.Items.Delete(index);
|
|
UpdateBtnStates;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TKaplanMeierForm.VarListSelectionChange(Sender: TObject; User: boolean);
|
|
begin
|
|
UpdateBtnStates;
|
|
end;
|
|
|
|
|
|
end.
|
|
|