diff --git a/applications/lazstats/source/forms/analysis/statistical_process_control/basicspcunit.pas b/applications/lazstats/source/forms/analysis/statistical_process_control/basicspcunit.pas index 753fdbc5a..55d821c78 100644 --- a/applications/lazstats/source/forms/analysis/statistical_process_control/basicspcunit.pas +++ b/applications/lazstats/source/forms/analysis/statistical_process_control/basicspcunit.pas @@ -324,7 +324,7 @@ begin FChartFrame.SetXTitle(AXTitle); FChartFrame.SetYTitle(AYTitle); - ser := FChartFrame.PlotXY(ptSymbols, nil, Means, Groups, nil, ADataTitle, clBlack); + ser := FChartFrame.PlotXY(ptLinesAndSymbols, nil, Means, Groups, nil, ADataTitle, clBlack); if Length(Groups) > 0 then begin FChartFrame.Chart.BottomAxis.Marks.Source := ser.Source; diff --git a/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.lfm b/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.lfm index 491da1aa1..416d32ae1 100644 --- a/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.lfm +++ b/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.lfm @@ -1,18 +1,20 @@ inherited CUSUMChartForm: TCUSUMChartForm - Height = 424 + Height = 503 + Width = 1000 HelpType = htKeyword HelpKeyword = 'html/CUMSUMChart.htm' Caption = 'Cumulative Sum Control Chart' - ClientHeight = 424 + ClientHeight = 503 + ClientWidth = 1000 OnActivate = FormActivate ShowHint = True inherited SpecsPanel: TPanel - Height = 424 + Height = 503 Width = 432 - ClientHeight = 424 + ClientHeight = 503 ClientWidth = 432 inherited ButtonPanel: TPanel - Top = 382 + Top = 461 Width = 432 ClientWidth = 432 TabOrder = 5 @@ -33,8 +35,8 @@ inherited CUSUMChartForm: TCUSUMChartForm end end inherited VarList: TListBox - Height = 349 - Width = 188 + Height = 428 + Width = 196 end inherited GroupLabel: TLabel Left = 236 @@ -68,164 +70,23 @@ inherited CUSUMChartForm: TCUSUMChartForm AnchorSideLeft.Control = GroupInBtn Left = 205 end - object GroupBox1: TGroupBox[12] - AnchorSideLeft.Control = MeasEdit - AnchorSideTop.Control = GroupBox2 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = MeasEdit - AnchorSideRight.Side = asrBottom - Left = 236 - Height = 153 - Top = 239 - Width = 196 - Anchors = [akTop, akLeft, akRight] - AutoSize = True - BorderSpacing.Top = 16 - BorderSpacing.Bottom = 8 - Caption = 'V-Mask Specifications' - ClientHeight = 133 - ClientWidth = 192 - TabOrder = 4 - object Label4: TLabel - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = DeltaEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 6 - Width = 94 - BorderSpacing.Left = 12 - Caption = 'Delta (Effect Size):' - ParentColor = False - end - object Label5: TLabel - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = AlphaEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 33 - Width = 94 - BorderSpacing.Left = 12 - Caption = 'Alpha Probability:' - ParentColor = False - end - object Label6: TLabel - AnchorSideLeft.Control = GroupBox1 - AnchorSideTop.Control = BetaEdit - AnchorSideTop.Side = asrCenter - Left = 12 - Height = 15 - Top = 60 - Width = 86 - BorderSpacing.Left = 12 - Caption = 'Beta Probability:' - ParentColor = False - end - object DeltaEdit: TEdit - AnchorSideLeft.Control = AlphaEdit - AnchorSideTop.Control = GroupBox1 - AnchorSideRight.Control = GroupBox1 - AnchorSideRight.Side = asrBottom - Left = 114 - Height = 23 - Hint = 'Detection level for a shift in the process mean, '#13#10'expressed in data units (default), or'#13#10'as a multiple of the standard deviation of the '#13#10'data points (when "Normalized CUSUM" is checked).' - Top = 2 - Width = 70 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 2 - BorderSpacing.Right = 8 - Constraints.MinWidth = 64 - TabOrder = 0 - Text = 'DeltaEdit' - end - object AlphaEdit: TEdit - AnchorSideLeft.Control = Label5 - AnchorSideLeft.Side = asrBottom - AnchorSideTop.Control = DeltaEdit - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = GroupBox1 - AnchorSideRight.Side = asrBottom - Left = 114 - Height = 23 - Hint = 'Probability of concluding that a shift in the process has occurred, when in fact it did not. ' - Top = 29 - Width = 70 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Left = 8 - BorderSpacing.Top = 4 - BorderSpacing.Right = 8 - Constraints.MinWidth = 64 - TabOrder = 1 - Text = 'AlphaEdit' - end - object BetaEdit: TEdit - AnchorSideLeft.Control = AlphaEdit - AnchorSideTop.Control = AlphaEdit - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = GroupBox1 - AnchorSideRight.Side = asrBottom - Left = 114 - Height = 23 - Hint = 'Probability of not detecting that a shift in the process mean has, in fact, occurred' - Top = 56 - Width = 70 - Alignment = taRightJustify - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 4 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - Constraints.MinWidth = 64 - TabOrder = 2 - Text = 'BetaEdit' - end - object VMaskScrollbar: TScrollBar - AnchorSideLeft.Control = Label1 - AnchorSideTop.Control = Label1 - AnchorSideTop.Side = asrBottom - AnchorSideRight.Control = BetaEdit - AnchorSideRight.Side = asrBottom - Left = 12 - Height = 17 - Top = 104 - Width = 172 - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Top = 2 - BorderSpacing.Bottom = 12 - PageSize = 0 - TabOrder = 3 - end - object Label1: TLabel - AnchorSideLeft.Control = Label4 - AnchorSideTop.Control = BetaEdit - AnchorSideTop.Side = asrBottom - Left = 12 - Height = 15 - Top = 87 - Width = 100 - Caption = 'Position of V-Mask' - ParentColor = False - end - end - object GroupBox2: TGroupBox[13] - AnchorSideLeft.Control = GroupBox1 + object GroupBox2: TGroupBox[12] + AnchorSideLeft.Control = MeasInBtn AnchorSideTop.Control = GroupEdit AnchorSideTop.Side = asrBottom AnchorSideRight.Control = MeasEdit AnchorSideRight.Side = asrBottom - Left = 236 - Height = 74 - Top = 149 - Width = 196 + Left = 204 + Height = 134 + Top = 153 + Width = 228 Anchors = [akTop, akLeft, akRight] AutoSize = True - BorderSpacing.Top = 16 + BorderSpacing.Top = 20 BorderSpacing.Bottom = 8 - Caption = 'Option:' - ClientHeight = 54 - ClientWidth = 192 + Caption = 'Options:' + ClientHeight = 114 + ClientWidth = 224 TabOrder = 3 object TargetChk: TCheckBox AnchorSideLeft.Control = GroupBox2 @@ -241,63 +102,369 @@ inherited CUSUMChartForm: TCUSUMChartForm TabOrder = 0 end object TargetEdit: TEdit - AnchorSideLeft.Control = TargetChk - AnchorSideLeft.Side = asrBottom + AnchorSideLeft.Control = StdDevEdit AnchorSideTop.Control = GroupBox2 AnchorSideRight.Control = GroupBox2 AnchorSideRight.Side = asrBottom - Left = 128 + Left = 132 Height = 23 Top = 0 - Width = 64 + Width = 84 Alignment = taRightJustify Anchors = [akTop, akLeft, akRight] - BorderSpacing.Left = 8 BorderSpacing.Right = 8 BorderSpacing.Bottom = 4 Constraints.MinWidth = 64 TabOrder = 1 Text = 'TargetEdit' end - object ShowMeanDevChk: TCheckBox + object rbTabular: TRadioButton AnchorSideLeft.Control = TargetChk - AnchorSideTop.Control = TargetEdit + AnchorSideTop.Control = Bevel3 AnchorSideTop.Side = asrBottom Left = 12 Height = 19 - Top = 27 - Width = 169 + Top = 66 + Width = 103 BorderSpacing.Top = 4 - BorderSpacing.Bottom = 8 - Caption = 'Show mean group deviation' + Caption = 'Tabular CUSUM' + Checked = True + OnChange = rbTabularChange + TabOrder = 4 + TabStop = True + end + object Bevel3: TBevel + AnchorSideLeft.Control = GroupBox2 + AnchorSideTop.Control = StdDevEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = GroupBox2 + AnchorSideRight.Side = asrBottom + Left = 8 + Height = 4 + Top = 58 + Width = 208 + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 8 + BorderSpacing.Top = 8 + BorderSpacing.Right = 8 + Shape = bsTopLine + end + object rbVMask: TRadioButton + AnchorSideLeft.Control = rbTabular + AnchorSideTop.Control = rbTabular + AnchorSideTop.Side = asrBottom + Left = 12 + Height = 19 + Top = 89 + Width = 60 + BorderSpacing.Top = 4 + BorderSpacing.Bottom = 6 + Caption = 'V-Mask' + OnChange = rbTabularChange + TabOrder = 5 + end + object StdDevChk: TCheckBox + AnchorSideLeft.Control = GroupBox2 + AnchorSideTop.Control = StdDevEdit + AnchorSideTop.Side = asrCenter + Left = 12 + Height = 19 + Top = 29 + Width = 112 + BorderSpacing.Left = 12 + BorderSpacing.Right = 8 + Caption = 'Use StdDev Specs' TabOrder = 2 end + object StdDevEdit: TEdit + AnchorSideLeft.Control = StdDevChk + AnchorSideLeft.Side = asrBottom + AnchorSideTop.Control = TargetEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = GroupBox2 + AnchorSideRight.Side = asrBottom + Left = 132 + Height = 23 + Top = 27 + Width = 84 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 8 + BorderSpacing.Right = 8 + BorderSpacing.Bottom = 4 + Constraints.MinWidth = 64 + TabOrder = 3 + Text = 'StdDevEdit' + end + end + object Notebook: TNotebook[13] + AnchorSideLeft.Control = GroupBox2 + AnchorSideTop.Control = GroupBox2 + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = GroupBox2 + AnchorSideRight.Side = asrBottom + Left = 204 + Height = 153 + Top = 295 + Width = 228 + PageIndex = 0 + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Bottom = 8 + TabOrder = 4 + object TabularPage: TPage + object TabularGroup: TGroupBox + AnchorSideLeft.Control = TabularPage + AnchorSideTop.Control = TabularPage + AnchorSideRight.Control = TabularPage + AnchorSideRight.Side = asrBottom + Left = 0 + Height = 80 + Top = 0 + Width = 228 + Anchors = [akTop, akLeft, akRight] + AutoSize = True + Caption = 'Tabulated CUSUM Parameters' + ClientHeight = 60 + ClientWidth = 224 + TabOrder = 0 + object Label7: TLabel + AnchorSideTop.Control = kEdit + AnchorSideTop.Side = asrCenter + Left = 12 + Height = 15 + Top = 6 + Width = 96 + BorderSpacing.Left = 12 + Caption = 'k (in StdDev units)' + ParentColor = False + end + object kEdit: TEdit + AnchorSideLeft.Control = hEdit + AnchorSideTop.Control = TabularGroup + AnchorSideRight.Control = TabularGroup + AnchorSideRight.Side = asrBottom + Left = 117 + Height = 23 + Hint = 'Detection level for a shift in the process mean, '#13#10'expressed in data units (default), or'#13#10'as a multiple of the standard deviation of the '#13#10'data points (when "Normalized CUSUM" is checked).' + Top = 2 + Width = 99 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 2 + BorderSpacing.Right = 8 + Constraints.MinWidth = 64 + TabOrder = 0 + Text = 'kEdit' + end + object hEdit: TEdit + AnchorSideLeft.Control = Label8 + AnchorSideLeft.Side = asrBottom + AnchorSideTop.Control = kEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = TabularGroup + AnchorSideRight.Side = asrBottom + Left = 117 + Height = 23 + Hint = 'Probability of concluding that a shift in the process has occurred, when in fact it did not. ' + Top = 29 + Width = 99 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 8 + BorderSpacing.Top = 4 + BorderSpacing.Right = 8 + BorderSpacing.Bottom = 8 + Constraints.MinWidth = 64 + TabOrder = 1 + Text = 'hEdit' + end + object Label8: TLabel + AnchorSideTop.Control = hEdit + AnchorSideTop.Side = asrCenter + Left = 12 + Height = 15 + Top = 33 + Width = 97 + BorderSpacing.Left = 12 + Caption = 'h (in StdDev units)' + ParentColor = False + end + end + end + object VMaskPage: TPage + object VMaskGroup: TGroupBox + AnchorSideLeft.Control = VMaskPage + AnchorSideTop.Control = VMaskPage + AnchorSideRight.Control = VMaskPage + AnchorSideRight.Side = asrBottom + Left = 0 + Height = 153 + Top = 0 + Width = 228 + Anchors = [akTop, akLeft, akRight] + AutoSize = True + Caption = 'V-Mask Specifications' + ClientHeight = 133 + ClientWidth = 224 + TabOrder = 0 + object Label4: TLabel + AnchorSideLeft.Control = VMaskGroup + AnchorSideTop.Control = DeltaEdit + AnchorSideTop.Side = asrCenter + Left = 12 + Height = 15 + Top = 6 + Width = 94 + BorderSpacing.Left = 12 + Caption = 'Delta (Effect Size):' + ParentColor = False + end + object Label5: TLabel + AnchorSideLeft.Control = VMaskGroup + AnchorSideTop.Control = AlphaEdit + AnchorSideTop.Side = asrCenter + Left = 12 + Height = 15 + Top = 33 + Width = 94 + BorderSpacing.Left = 12 + Caption = 'Alpha Probability:' + ParentColor = False + end + object Label6: TLabel + AnchorSideLeft.Control = VMaskGroup + AnchorSideTop.Control = BetaEdit + AnchorSideTop.Side = asrCenter + Left = 12 + Height = 15 + Top = 60 + Width = 86 + BorderSpacing.Left = 12 + Caption = 'Beta Probability:' + ParentColor = False + end + object DeltaEdit: TEdit + AnchorSideLeft.Control = AlphaEdit + AnchorSideTop.Control = VMaskGroup + AnchorSideRight.Control = VMaskGroup + AnchorSideRight.Side = asrBottom + Left = 114 + Height = 23 + Hint = 'Detection level for a shift in the process mean, '#13#10'expressed in data units (default), or'#13#10'as a multiple of the standard deviation of the '#13#10'data points (when "Normalized CUSUM" is checked).' + Top = 2 + Width = 102 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 2 + BorderSpacing.Right = 8 + Constraints.MinWidth = 64 + TabOrder = 0 + Text = 'DeltaEdit' + end + object AlphaEdit: TEdit + AnchorSideLeft.Control = Label5 + AnchorSideLeft.Side = asrBottom + AnchorSideTop.Control = DeltaEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = VMaskGroup + AnchorSideRight.Side = asrBottom + Left = 114 + Height = 23 + Hint = 'Probability of concluding that a shift in the process has occurred, when in fact it did not. ' + Top = 29 + Width = 102 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 8 + BorderSpacing.Top = 4 + BorderSpacing.Right = 8 + Constraints.MinWidth = 64 + TabOrder = 1 + Text = 'AlphaEdit' + end + object BetaEdit: TEdit + AnchorSideLeft.Control = AlphaEdit + AnchorSideTop.Control = AlphaEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = VMaskGroup + AnchorSideRight.Side = asrBottom + Left = 114 + Height = 23 + Hint = 'Probability of not detecting that a shift in the process mean has, in fact, occurred' + Top = 56 + Width = 102 + Alignment = taRightJustify + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 4 + BorderSpacing.Right = 8 + BorderSpacing.Bottom = 8 + Constraints.MinWidth = 64 + TabOrder = 2 + Text = 'BetaEdit' + end + object VMaskScrollbar: TScrollBar + AnchorSideLeft.Control = Label1 + AnchorSideTop.Control = Label1 + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = BetaEdit + AnchorSideRight.Side = asrBottom + Left = 12 + Height = 17 + Top = 104 + Width = 204 + Anchors = [akTop, akLeft, akRight] + BorderSpacing.Top = 2 + BorderSpacing.Bottom = 12 + PageSize = 0 + TabOrder = 3 + end + object Label1: TLabel + AnchorSideLeft.Control = Label4 + AnchorSideTop.Control = BetaEdit + AnchorSideTop.Side = asrBottom + Left = 12 + Height = 15 + Top = 87 + Width = 100 + Caption = 'Position of V-Mask' + ParentColor = False + end + end + end end end inherited SpecsSplitter: TSplitter - Left = 435 - Height = 424 + Left = 443 + Height = 503 end inherited PageControl: TPageControl - Left = 443 - Height = 412 - Width = 478 + Left = 451 + Height = 491 + Width = 543 inherited ReportPage: TTabSheet - ClientHeight = 384 - ClientWidth = 470 + ClientHeight = 463 + ClientWidth = 535 inherited Panel1: TPanel - Height = 344 - Width = 458 - ClientHeight = 340 - ClientWidth = 454 + Height = 423 + Width = 523 + ClientHeight = 419 + ClientWidth = 519 inherited ReportMemo: TMemo - Height = 332 - Width = 446 + Height = 411 + Width = 511 end end inherited ReportToolBar: TToolBar - Width = 466 + Width = 531 end end end + inherited SaveDialog: TSaveDialog + Left = 600 + Top = 112 + end + inherited PrintDialog: TPrintDialog + Left = 600 + Top = 200 + end end diff --git a/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.pas b/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.pas index 6797a7622..e06e35a09 100644 --- a/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.pas +++ b/applications/lazstats/source/forms/analysis/statistical_process_control/cusumunit.pas @@ -6,7 +6,7 @@ interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls, ExtCtrls, - StdCtrls, Globals, BasicSPCUnit; + StdCtrls, Buttons, PrintersDlgs, Globals, BasicSPCUnit; type @@ -14,12 +14,24 @@ type TCUSUMChartForm = class(TBasicSPCForm) AlphaEdit: TEdit; + hEdit: TEdit; BetaEdit: TEdit; + Bevel3: TBevel; + kEdit: TEdit; + TabularGroup: TGroupBox; Label1: TLabel; + Label7: TLabel; + Label8: TLabel; + Notebook: TNotebook; + TabularPage: TPage; + StdDevChk: TCheckBox; + StdDevEdit: TEdit; + VMaskPage: TPage; + rbTabular: TRadioButton; + rbVMask: TRadioButton; VMaskScrollbar: TScrollBar; - ShowMeanDevChk: TCheckBox; DeltaEdit: TEdit; - GroupBox1: TGroupBox; + VMaskGroup: TGroupBox; GroupBox2: TGroupBox; Label4: TLabel; Label5: TLabel; @@ -27,13 +39,15 @@ type TargetChk: TCheckBox; TargetEdit: TEdit; procedure FormActivate(Sender: TObject); + procedure rbTabularChange(Sender: TObject); private + CUSums, CUSumsUpper, CUSumsLower: DblDyneVec; SEMean: Double; k, h: Double; protected procedure Compute; override; procedure PlotMeans(ATitle, AXTitle, AYTitle, ADataTitle, AGrandMeanTitle: String; - const Groups: StrDyneVec; const Means: DblDyneVec; + const Groups: StrDyneVec; const {%H-}Means: DblDyneVec; UCL, LCL, GrandMean, TargetSpec, LowerSpec, UpperSpec: double); override; procedure Reset; override; function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override; @@ -48,44 +62,53 @@ implementation {$R *.lfm} uses - Math, TAChartUtils, TASeries, - Utils, MainUnit, DataProcs; + Math, TAChartUtils, TATypes, TASeries, + Utils, MainUnit, ChartFrameUnit, DataProcs; { TCUSUMChartForm } procedure TCUSUMChartForm.Compute; var i, j, grpIndex, numGrps, grpSize, oldGrpSize, numValues: Integer; - X, Xsq, Xmin, Xmax, target, diff, grandMean, grandSum, grandSD: Double; - deltaSD, alpha, beta: double; - sizeError: Boolean; + X, Xsq, prevX: Double; + target, stdDev, diff, grandMean, grandSD, aveSD: Double; + delta, alpha, beta: double; grp: String; + individuals: Boolean; + UCL: Double = NaN; + LCL: Double = NaN; groups: StrDyneVec = nil; means: DblDyneVec = nil; - stdDev: DblDyneVec = nil; - cuSums: DblDyneVec = nil; + stdDevs: DblDyneVec = nil; count: IntDyneVec = nil; ColNoSelected: IntDyneVec = nil; lReport: TStrings; begin - SetLength(ColNoSelected, 2); - ColNoSelected[0] := GrpVar; - ColNoSelected[1] := MeasVar; + if GroupEdit.Text <> '' then + begin + SetLength(ColNoSelected, 2); + ColNoSelected[0] := GrpVar; + ColNoSelected[1] := MeasVar; + individuals := false; + groups := GetGroups; + end else + begin + SetLength(ColNoSelected, 1); + ColNoSelected[0] := MeasVar; + individuals := true; + SetLength(groups, NoCases) + end; - groups := GetGroups(); numGrps := Length(groups); grpSize := 0; oldGrpSize := 0; - SetLength(means, numGrps); SetLength(count, numGrps); - SetLength(stdDev, numGrps); - SetLength(cuSums, numGrps); - SEMean := 0.0; + SetLength(means, numGrps); + SetLength(stdDevs, numGrps); grandMean := 0.0; - grandSum := 0.0; - sizeError := false; + grandSD := 0.0; // Count "good" data points numValues := 0; @@ -93,117 +116,194 @@ begin if GoodRecord(i, Length(ColNoSelected), ColNoSelected) then inc(numValues); // calculate group ranges, grand mean, group sd's, semeans - for j := 0 to numGrps - 1 do // groups + if individuals then begin - Xmin := Infinity; - Xmax := -Infinity; + // x-bar chart of individual measurements, no groups + grpIndex := 0; + prevX := NaN; for i := 1 to NoCases do begin if not GoodRecord(i, Length(ColNoSelected), ColNoSelected) then continue; - grp := Trim(OS3MainFrm.DataGrid.Cells[GrpVar, i]); - grpIndex := IndexOfString(groups, grp); - if grpIndex = j then - begin - X := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[MeasVar, i])); - Xsq := X * X; - if X > Xmax then Xmax := X; - if X < Xmin then Xmin := X; - inc(count[grpIndex]); - means[grpIndex] := means[grpIndex] + X; - stddev[grpIndex] := stddev[grpIndex] + Xsq; - SEMean := SEMean + Xsq; - grandMean := grandMean + X; - end; - end; // next case - - grpSize := count[j]; - if j = 0 then oldgrpSize := grpSize; - if (oldGrpsize <> grpSize) or (grpSize < 2) then - begin - sizeError := true; - break; + X := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[MeasVar, i])); + Xsq := X*X; + count[i] := 1; + groups[grpIndex] := IntToStr(i); + means[grpIndex] := means[grpIndex] + X; + if not IsNaN(prevX) then + stdDevs[grpIndex-1] := abs(X - prevX); // assume std dev to be moving range; + // -1 --> skip empty 1st value + grandMean := grandMean + X; + grandSD := grandSD + Xsq; + inc(grpIndex); + prevX := X; end; - - stdDev[j] := stddev[j] - sqr(means[j]) / grpSize; - stddev[j] := stddev[j] / (grpSize - 1); - stddev[j] := sqrt(stddev[j]); - means[j] := means[j] / grpSize; - end; // next group - - if (grpSize < 2) or (grpSize > 25) or sizeError then + SetLength(stdDevs, numGrps - 1); // skip empty 1st value + end else begin - ErrorMsg('Group size error.'); - exit; + for j := 0 to numGrps - 1 do // groups + begin + for i := 1 to NoCases do + begin + if not GoodRecord(i, Length(ColNoSelected), ColNoSelected) then continue; + grp := Trim(OS3MainFrm.DataGrid.Cells[GrpVar, i]); + grpIndex := IndexOfString(groups, grp); + if grpIndex = j then + begin + X := StrToFloat(Trim(OS3MainFrm.DataGrid.Cells[MeasVar, i])); + Xsq := X * X; + inc(count[grpIndex]); + means[grpIndex] := means[grpIndex] + X; + stdDevs[grpIndex] := stdDevs[grpIndex] + Xsq; + grandMean := grandMean + X; + grandSD := grandSD + Xsq; + end; + end; // next case + + grpSize := count[j]; + if j = 0 then oldgrpSize := grpSize; + if (oldGrpSize <> grpSize) then + begin + ErrorMsg('All groups must have the same size.'); + exit; + end; + + stdDevs[j] := stdDevs[j] - sqr(means[j]) / grpSize; + stdDevs[j] := stdDevs[j] / (grpSize - 1); + stdDevs[j] := sqrt(stdDevs[j]); + means[j] := means[j] / grpSize; + end; // next group end; - // now get cumulative deviations of means from target + grandSD := grandSD - sqr(grandMean) / numValues; + grandSD := sqrt(grandSD / (numValues - 1)); + SEMean := grandSD / sqrt(numValues); + grandMean := grandMean/numValues; // mean of all observations + if individuals then + begin + aveSD := 0; + for i := 0 to High(stdDevs) do + aveSD := aveSD + stdDevs[i]; + aveSD := aveSD / Length(stdDevs) / 1.128; // 1.128 is the value of d2 fo n = 2. + end else + aveSD := grandSD / sqrt(grpSize); + if TargetChk.Checked then target := StrToFloat(TargetEdit.Text) else - target := means[numGrps-1]; - cusums[0] := means[0] - target; - grandSum := grandSum + (means[0] - target); + target := grandMean; //means[numGrps-1]; + + if StdDevChk.Checked then + stdDev := StrToFloat(StdDevEdit.Text) + else + stdDev := aveSD; + + if rbTabular.Checked then + begin + k := StrToFloat(kEdit.Text) * stdDev; + h := StrToFloat(hEdit.Text) * stdDev; + UCL := h; + LCL := -h; + end else + begin + if DeltaEdit.Text <> '' then + begin + delta := StrToFloat(DeltaEdit.Text) / stdDev; + // This is in multiples of std deviations + + // see : https://www.itl.nist.gov/div898/handbook/pmc/section3/pmc323.htm + alpha := StrToFloat(AlphaEdit.Text); + beta := StrToFloat(BetaEdit.Text); + + k := stdDev * delta / 2.0; + h := stdDev / delta * ln((1-beta) / alpha); + end; + end; + + // Now get cumulative deviations of means from target + diff := means[0] - target; + if rbVMask.Checked then + begin + SetLength(CUSums, numGrps); + FillChar(CUSums[0], numGrps*SizeOf(Double), 0); + CUSums[0] := diff; + end; + if rbTabular.Checked then + begin + SetLength(CUSumsUpper, numGrps); + FillChar(CUSumsUpper[0], numGrps*SizeOf(Double), 0); + CUSumsUpper[0] := 0; //Max(0, diff); + SetLength(CUSumsLower, numGrps); + FillChar(CUSumsLower[0], numGrps*SizeOf(Double), 0); + CUSumsLower[0] := 0; //Max(0, diff); + end; for j := 1 to numGrps-1 do begin diff := means[j] - target; - cusums[j] := cusums[j-1] + diff; - grandSum := grandSum + diff; - end; - - SEMean := SEMean - sqr(grandMean)/numValues; - SEMean := sqrt(SEMean/(numValues - 1)); - grandSD := SEMean; - SEMean := SEMean/sqrt(numValues); - grandMean := grandMean/numValues; // mean of all observations - grandSum := grandSum/numGrps; // mean of the group means - - if DeltaEdit.Text <> '' then - begin - deltaSD := StrToFloat(DeltaEdit.Text) / SEMean; - // This is in multiples of std deviations - - // see : https://www.itl.nist.gov/div898/handbook/pmc/section3/pmc323.htm - alpha := StrToFloat(AlphaEdit.Text); - beta := StrToFloat(BetaEdit.Text); - k := deltaSD * SEMean / 2.0; - h := SEMean / deltaSD * ln((1 - beta) / alpha); + if rbVMask.Checked then + CUSums[j] := CUSums[j-1] + diff; + if rbTabular.Checked then + begin + CUSumsUpper[j] := Max(0, diff - k + CUSumsUpper[j-1]); + CUSumsLower[j] := Min(0, diff + k + CUSumsLower[j-1]); + // wp: There's a lot of garbage in the internet on these formulas! + end; end; // Print results lReport := TStringList.Create; try lReport.Clear; - lReport.Add('CUSUM Chart Results'); - lReport.Add(''); - lReport.Add('Number of Values: %8d', [numValues]); - lReport.Add('Mean of group deviations: %8.3f', [grandSum]); - lReport.Add('Mean of all observations: %8.3f', [grandMean]); - lReport.Add('Std. Dev. of Observations: %8.3f', [grandSD]); - lReport.Add('Standard Error of Mean: %8.3f', [SEMean]); - lReport.Add('Target Specification: %8.3f', [target]); + lReport.Add ('CUSUM Chart Results'); + lReport.Add (''); + lReport.Add ('Number of Values: %8d', [numValues]); + lReport.Add ('Number of groups: %8d', [numGrps]); + lReport.Add ('Group size: %8d', [grpSize]); + lReport.Add (''); + lReport.Add ('Mean of all observations: %8.3f', [grandMean]); + lReport.Add ('Std. Dev. of observations: %8.3f', [grandSD]); + lReport.Add ('Standard error of Mean: %8.3f', [SEMean]); + lReport.Add ('Target specification: %8.3f', [target]); + lReport.Add ('Average group std dev: %8.3f', [aveSD]); + lReport.Add (''); - lReport.Add(''); - lReport.Add('Differences in data units'); - lReport.Add(''); - - lReport.Add('Group Size Mean Std.Dev. Mean-Dev Cum.Dev. of' ); - lReport.Add(' Mean from Target'); - lReport.Add('----- ---- -------- -------- -------- ----------------'); - for i := 0 to numGrps - 1 do + if rbTabular.Checked then begin - lReport.Add('%5s %4d %8.3f %8.3f %8.3f %10.3f', [ - groups[i], count[i], means[i], stddev[i], means[i]-target, cusums[i] - ]); + lReport.Add ('Tabular CUSUM parameters:'); + lReport.Add (' k: %8.3f (%s sigma)', [k, kEdit.Text]); + lReport.Add (' h: %8.3f (%s sigma)', [h, hEdit.Text]); + lReport.Add (''); + + lReport.Add ('Group Size Mean Mean Dev Cum. Deviation ' ); + lReport.Add (' Upper Lower '); + lReport.Add ('----- ---- -------- -------- ---------------------'); + for i := 0 to numGrps - 1 do + begin + lReport.Add('%5s %4d %8.3f %8.3f %9.3f %9.3f', [ + groups[i], count[i], means[i], means[i]-target, CUSumsUpper[i], CUSumsLower[i] + ]); + end; end; - if DeltaEdit.Text <> '' then + if rbVMask.Checked then begin - lReport.Add(''); - lReport.Add('V-Mask parameters:'); - lReport.Add(' Alpha (Type I error) %8.3f', [alpha]); - lReport.Add(' Beta (Type II error) %8.3f', [beta]); - lReport.Add(' k: %8.3f (%.2f sigma)', [k, k/SEMean]); - lReport.Add(' h: %8.3f (%.2f sigma)', [h, h/SEMean]); + if DeltaEdit.Text <> '' then + begin + lReport.Add('V-Mask parameters:'); + lReport.Add(' Alpha (Type I error) %8.3f', [alpha]); + lReport.Add(' Beta (Type II error) %8.3f', [beta]); + lReport.Add(' k: %8.3f (%.2f sigma)', [k, k/SEMean]); + lReport.Add(' h: %8.3f (%.2f sigma)', [h, h/SEMean]); + end; + lReport.Add (''); + lReport.Add ('Group Size Mean Mean Dev Cum.Dev. of' ); + lReport.Add (' Mean from Target'); + lReport.Add ('----- ---- -------- -------- ----------------'); + for i := 0 to numGrps - 1 do + begin + lReport.Add('%5s %4d %8.3f %8.3f %10.3f', [ + groups[i], count[i], means[i], means[i]-target, cusums[i] + ]); + end; end; ReportMemo.Lines.Assign(lReport); @@ -213,16 +313,14 @@ begin // Show graph VMaskScrollbar.Max := numGrps; - if not ShowMeanDevChk.Checked then - grandSum := NaN; PlotMeans( Format('Cumulative Sum Chart for "%s"', [GetFileName]), // chart title GroupEdit.Text, // x title 'CUSUM of ' + MeasEdit.Text + ' differences', // y title 'Data', // series title 'Mean', // mean label at right - groups, cuSums, - NaN, NaN, grandSum, + groups, nil, // y values will be applied in overridden method + UCL, LCL, NaN, NaN, NaN, NaN ); end; @@ -232,6 +330,8 @@ procedure TCUSUMChartForm.FormActivate(Sender: TObject); var w: Integer; begin + NoGroupsAllowed := true; + w := MaxValue([HelpBtn.Width, ResetBtn.Width, ComputeBtn.Width, CloseBtn.Width]); HelpBtn.Constraints.MinWidth := w; ResetBtn.Constraints.MinWidth := w; @@ -240,20 +340,36 @@ begin DisableAutoSizing; try - GroupBox1.Anchors := GroupBox1.Anchors - [akRight]; +// VMaskGroup.Anchors := VMaskGroup.Anchors - [akRight]; VarList.Constraints.MinWidth := VarListLabel.Width; SpecsPanel.Constraints.MinWidth := Max( CloseBtn.Left + CloseBtn.Width - HelpBtn.Left + HelpBtn.BorderSpacing.Around, GroupBox2.Width * 2 + VarList.BorderSpacing.Right + VarList.BorderSpacing.Left ); - Constraints.MinHeight := GroupBox1.Top + GroupBox1.Height + GroupBox1.BorderSpacing.Bottom + ButtonPanel.Height; - Height := 1; // enforce autosizing of height - GroupBox1.Anchors := GroupBox1.Anchors + [akRight]; + TabularGroup.Constraints.MinHeight := VMaskScrollbar.Top + VMaskScrollbar.Height + + VMaskScrollbar.BorderSpacing.Bottom + TabularGroup.Height - TabularGroup.ClientHeight; + Notebook.Constraints.MinHeight := TabularGroup.Constraints.MinHeight; + Notebook.Height := 1; // Enforce notebook autosizing + Constraints.MinHeight := Notebook.Top + Notebook.Height + Notebook.BorderSpacing.Bottom + ButtonPanel.Height; + if Height < Constraints.MinHeight then + Height := 1; // enforce height autosizing + // VMaskGroup.Anchors := VMaskGroup.Anchors + [akRight]; finally EnableAutoSizing; end; end; + +procedure TCUSUMChartForm.rbTabularChange(Sender: TObject); +begin + if (Sender = rbTabular) and rbTabular.Checked then + Notebook.PageIndex := 0 + else + if (Sender = rbVMask) and rbVMask.Checked then + Notebook.PageIndex := 1; +end; + + { Overridden to draw the V-Mark } procedure TCUSUMChartForm.PlotMeans(ATitle, AXTitle, AYTitle, ADataTitle, AGrandMeanTitle: String; const Groups: StrDyneVec; const Means: DblDyneVec; @@ -262,47 +378,65 @@ var ser: TLineSeries; xVM, yVM, x1, y1, x2, y2, x3, y3, x4, y4: Double; begin - inherited; - if DeltaEdit.Text = '' then - exit; + // Tabular CUSUM Chart: Plot the lower CUSums in addition to the upper CUSums. + if rbTabular.Checked then + begin + inherited PlotMeans(ATitle, AXTitle, AYTitle, 'Upper CUSums', AGrandMeanTitle, + Groups, CUSumsUpper, UCL, LCL, GrandMean, TargetSpec, LowerSpec, UpperSpec); - ser := TLineSeries.Create(FChartFrame.Chart); - FChartFrame.Chart.AddSeries(ser); - ser.SeriesColor := clBlue; - ser.Title := 'V-Mask'; + ser := TLineSeries(FChartFrame.PlotXY(ptLinesAndSymbols, nil, CUSUMsLower, Groups, nil, 'Lower CUSums', clBlack)); +// ser.Pointer.Style := psDiamond; + ser.Pointer.Brush.Color := clWhite; + end; - // Position of V mask point - xVM := VMaskScrollbar.Position; - yVM := Means[VMaskScrollbar.Position-1]; + // CUSUM Chart with V-Mask + if rbVMask.Checked then begin + inherited PlotMeans(ATitle, AXTitle, AYTitle, ADataTitle, AGrandMeanTitle, + Groups, CUSums, NaN, NaN, GrandMean, TargetSpec, LowerSpec, UpperSpec); - // Upper part of V mask - x2 := xVM; - y2 := yVM + h; - x1 := 1; // x values begin with 1 - y1 := y2 - k*(x1 - x2); + if (DeltaEdit.Text = '') then + exit; - // Lower part of V mask - x3 := xVM; - y3 := yVM - h; - x4 := 1; - y4 := y3 + k*(x4 - x3); + ser := TLineSeries.Create(FChartFrame.Chart); + FChartFrame.Chart.AddSeries(ser); + ser.SeriesColor := clBlue; + ser.Title := 'V-Mask'; - ser.AddXY(x1, y1); - ser.AddXY(x2, y2); - ser.AddXY(x2, NaN); // Do not draw the vertical line - ser.AddXY(x3, y3); - ser.AddXY(x4, y4); + // Position of V mask point + xVM := VMaskScrollbar.Position; + yVM := CUSums[VMaskScrollbar.Position-1]; + + // Upper part of V mask + x2 := xVM; + y2 := yVM + h; + x1 := 1; // x values begin with 1 + y1 := y2 - k*(x1 - x2); + + // Lower part of V mask + x3 := xVM; + y3 := yVM - h; + x4 := 1; + y4 := y3 + k*(x4 - x3); + + ser.AddXY(x1, y1); + ser.AddXY(x2, y2); +// ser.AddXY(x2, NaN); // Do not draw the vertical line + ser.AddXY(x3, y3); + ser.AddXY(x4, y4); + end; end; procedure TCUSUMChartForm.Reset; begin inherited; - ShowMeanDevChk.Checked := false; TargetEdit.Clear; + StdDevEdit.Clear; DeltaEdit.Clear; - AlphaEdit.Text := FormatFloat('0.00000', 0.0027); //DEFAULT_ALPHA_LEVEL); - BetaEdit.Text := FormatFloat('0.00000', 0.01); //DEFAULT_BETA_LEVEL); + kEdit.Text := FormatFloat('0.0', 0.5); + hEdit.Text := '4'; + AlphaEdit.Text := FormatFloat('0.00', DEFAULT_ALPHA_LEVEL); + BetaEdit.Text := FormatFloat('0.00', DEFAULT_BETA_LEVEL); VMaskScrollbar.Min := 2; VMaskScrollbar.Max := 1000; VMaskScrollbar.Position := VMaskScrollbar.Max; @@ -312,15 +446,52 @@ end; function TCUSUMChartForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; var x: Double; - n: Integer; begin Result := inherited; - if not Result then - exit; + if (not Result) then + begin + // This particular chart will handle individual data if GroupEdit is empty. + if GroupEdit.Visible and (GroupEdit.Text = '') then + Result := true + else + exit; + end; Result := false; - if (DeltaEdit.Text <> '') then + if TargetChk.Checked then + begin + if (TargetEdit.Text = '') then + begin + AMsg := 'Target not specified.'; + AControl := TargetEdit; + exit; + end; + if not TryStrToFloat(TargetEdit.Text, x) then + begin + AMsg := 'No valid number given for target specification.'; + AControl := TargetEdit; + exit; + end; + end; + + if StdDevChk.Checked then + begin + if (StdDevEdit.Text = '') then + begin + AMsg := 'Standard deviation expected, but not specified.'; + AControl := StdDevEdit; + exit; + end; + if not TryStrToFloat(StdDevEdit.Text, x) then + begin + AMsg := 'No valid number given for standard deviation.'; + AControl := StdDevEdit; + exit; + end; + end; + + if rbVMask.Checked and (DeltaEdit.Text <> '') then begin if not TryStrToFloat(DeltaEdit.Text, x) then begin @@ -356,18 +527,31 @@ begin end; end; - if TargetChk.Checked then + if rbTabular.Checked then begin - if (TargetEdit.Text = '') then + if (kEdit.Text = '') then begin - AMsg := 'Target not specified.'; - AControl := TargetEdit; + AMsg := 'k not specified.'; + AControl := kEdit; exit; end; - if not TryStrToFloat(TargetEdit.Text, x) then + if not TryStrToFloat(kEdit.Text, x) then begin - AMsg := 'No valid number given for target specification.'; - AControl := TargetEdit; + AMsg := 'No valid number given for k.'; + AControl := kEdit; + exit; + end; + + if (hEdit.Text = '') then + begin + AMsg := 'h not specified.'; + AControl := hEdit; + exit; + end; + if not TryStrToFloat(hEdit.Text, x) then + begin + AMsg := 'No valid number given for h.'; + AControl := hEdit; exit; end; end; diff --git a/applications/lazstats/source/forms/analysis/statistical_process_control/xbarchartunit.pas b/applications/lazstats/source/forms/analysis/statistical_process_control/xbarchartunit.pas index f5f299e99..fc017425a 100644 --- a/applications/lazstats/source/forms/analysis/statistical_process_control/xbarchartunit.pas +++ b/applications/lazstats/source/forms/analysis/statistical_process_control/xbarchartunit.pas @@ -61,7 +61,7 @@ implementation uses Math, - Utils, MainUnit, DataProcs; + Utils, MathUnit, MainUnit, DataProcs; {$R *.lfm} @@ -118,6 +118,7 @@ var grp: String; X, Xsq, prevX: Double; sigma, UCL, LCL, grandMean, grandSD, SEMean: Double; + //C4Value: Double; individualsChart: Boolean; lReport: TStrings; begin @@ -258,7 +259,8 @@ begin means[i] := means[i] / count[i]; end; end; - FAveStdDev := sqrt(FAveStdDev / (numGrps * grpSize)); +// C4Value := CalcC4(grpSize); + FAveStdDev := sqrt(FAveStdDev / (numGrps * grpSize)); // / C4Value; UCL := grandMean + sigma * FAveStdDev; LCL := grandMean - sigma * FAveStdDev; @@ -289,9 +291,9 @@ begin lReport.Add('Group size: %8d', [grpSize]); lReport.Add(''); lReport.Add('Grand Mean: %8.3f', [grandMean]); - lReport.Add('Standard Deviation: %8.3f', [grandSD]); lReport.Add('Standard Error of Mean: %8.3f', [SEMean]); - lReport.Add('Average Std Deviation: %8.3f', [FAveStdDev]); + lReport.Add('Grand Std Deviation: %8.3f', [grandSD]); + lReport.Add('Average Group Std Dev: %8.3f', [FAveStdDev]); lReport.Add('Upper Control Limit: %8.3f', [UCL]); lReport.Add('Lower Control Limit: %8.3f', [LCL]); lReport.Add('');