How to build a Full 2x2 Matrix Grid Layout as a Confusion Matrix: Displays the matrix sequence with True Positives (TP) and False Negatives (FN) in Row 1, and False Positives (FP) and True Negatives (TN) in Row 2.
Script Features
4 Real-Time Trackbars (TTrackBar): Independently adjust True Positives (TP), False Negatives (FN), False Positives (FP), and True Negatives (TN) from 0 to 20.
Instant Recalculation: Moving any slider dynamically updates the Accuracy, Precision, Recall, Specificity, and F1-Score in the log window.
Live Heatmap Canvas: Re-draws the 2x2 grid on TImage.Canvas with cell shading proportional to counts.
https://raw.githack.com/maxkleiner/maXbox5/main/assets/machinelearning55.htm
The Code
The Code
type
TMatrix2x2 = array[0..1] of array[0..1] of Integer;
TTStringArray = array[0..1] of string;
var
Form1: TForm;
ImgCanvas: TImage;
MemoLog: TMemo;
BtnCalc: TButton;
EdtTP, EdtFN, EdtFP, EdtTN: TEdit;
LblTP, LblFN, LblFP, LblTN: TLabel;
ClassNames: TTStringArray;
procedure CalculateAndDrawMatrix(Sender: TObject);
var
CM: TMatrix2x2;
r, c, TotalSamples, Correct: Integer;
Acc, Prec, rRec, F1, Spec: Double;
W, H, CellW, CellH, OffX, OffY, rVal, MaxVal: Integer;
ColorVal: Byte;
begin
// Class Names
ClassNames[0]:= 'Known';
ClassNames[1]:= 'Don''t Know';
// Populate 2x2 Confusion Matrix Counts based on Person A's 9-Person Scenario:
// TP (Known -> Known) = 3
// FN (Known -> Don't Know) = 2
// FP (Don't Know -> Known) = 1
// TN (Don't Know -> Don't Know) = 3
CM[0][0]:= 3; // TP
CM[0][1]:= 2; // FN
CM[1][0]:= 1; // FP
CM[1][1]:= 3; // TN
TotalSamples:= CM[0][0] + CM[0][1] + CM[1][0] + CM[1][1]; // 9
Correct:= CM[0][0] + CM[1][1]; // 6
Acc:= (Correct * 100.0) / TotalSamples; // 66.67%
// Metrics for "Known" (Positive class)
if (CM[0][0] + CM[1][0]) > 0 then Prec:= (CM[0][0] * 100.0) / (CM[0][0] + CM[1][0]) else Prec:= 0.0; // 3/4 = 75.0%
if (CM[0][0] + CM[0][1]) > 0 then rRec:= (CM[0][0] * 100.0) / (CM[0][0] + CM[0][1]) else rRec:= 0.0; // 3/5 = 60.0%
if (CM[1][1] + CM[1][0]) > 0 then Spec:= (CM[1][1] * 100.0) / (CM[1][1] + CM[1][0]) else Spec:= 0.0; // 3/4 = 75.0%
if (Prec + rRec) > 0 then F1:= (2.0 * Prec * rRec) / (Prec + rRec) else F1:= 0.0; // 66.67%
// Memo Log Output
MemoLog.Clear;
MemoLog.Lines.Add('=== PERSON CONFUSION MATRIX ===');
MemoLog.Lines.Add('Total People Evaluated : '+ IntToStr(TotalSamples));
MemoLog.Lines.Add('Overall Accuracy : '+ FormatFloat('0.00%', Acc));
MemoLog.Lines.Add('');
MemoLog.Lines.Add('ACTUAL \ PREDICTED | Known | Don''t Know | Total');
MemoLog.Lines.Add('----------------------------');
MemoLog.Lines.Add(Format('%-16s | %2d | %2d | %2d',[ClassNames[0],CM[0][0],CM[0][1],CM[0][0]+CM[0][1]]));
MemoLog.Lines.Add(Format('%-16s | %2d | %2d | %2d',[ClassNames[1],CM[1][0],CM[1][1],CM[1][0]+CM[1][1]]));
MemoLog.Lines.Add('----------------------------');
MemoLog.Lines.Add(Format('%-16s | %2d | %2d | %2d',['Total Predicted',CM[0][0]+CM[1][0],CM[0][1]+CM[1][1],TotalSamples]));
MemoLog.Lines.Add('');
MemoLog.Lines.Add('--- KEY EVALUATION METRICS ---');
MemoLog.Lines.Add('Precision (Known) : ' + FormatFloat('0.00%', Prec) + ' (3/4)');
MemoLog.Lines.Add('Recall / Sensitivity: ' + FormatFloat('0.00%', rRec) + ' (3/5)');
MemoLog.Lines.Add('Specificity : ' + FormatFloat('0.00%', Spec) + ' (3/4)');
MemoLog.Lines.Add('F1-Score : ' + FormatFloat('0.00%', F1));
// Render Canvas
W:= ImgCanvas.Width;
H:= ImgCanvas.Height;
ImgCanvas.Canvas.Brush.Color:= $DCF8FF //$3F85CD; //$ADDEFF; clWhite;
ImgCanvas.Canvas.FillRect(Rect(0, 0, W, H));
MaxVal:= 3;
OffX:= 120; OffY:= 110; CellW:= 100; CellH:= 100;
// Title and Axis Labels
ImgCanvas.Canvas.Font.Name:= 'Arial';
ImgCanvas.Canvas.Font.Size:= 14;
ImgCanvas.Canvas.Font.Style2:= fsBold2;
ImgCanvas.Canvas.Font.Color:= clBlack;
ImgCanvas.Canvas.TextOut(OffX - 2, 35, 'Predicted Status');
ImgCanvas.Canvas.TextOut(10, OffY + 80, 'Actual');
// Column Headers
ImgCanvas.Canvas.Font.Size:= 9;
ImgCanvas.Canvas.TextOut(OffX - 1, OffY - 30, ClassNames[0]);
ImgCanvas.Canvas.TextOut(OffX + CellW - 1, OffY - 30, ClassNames[1]);
ImgCanvas.Canvas.TextOut(OffX , OffY + 220, 'Sample Size: 9');
// Cells
for r:= 0 to 1 do begin
ImgCanvas.Canvas.TextOut(10, OffY + r * CellH + 40, ClassNames[r]);
for c:= 0 to 1 do begin
rVal:= CM[r][ c];
// Color intensity based on count
ColorVal:= 255 - Round((rVal / MaxVal) * 160);
// Diagonal cells (TP, TN) green/teal, Off-diagonal (FN, FP) orange/amber
if r = c then
ImgCanvas.Canvas.Brush.Color:= RGB(ColorVal, 255, ColorVal)
else
ImgCanvas.Canvas.Brush.Color:= RGB(255, 230, ColorVal);
ImgCanvas.Canvas.Pen.Color:= clNavy;
ImgCanvas.Canvas.Pen.Width:= 2;
ImgCanvas.Canvas.Rectangle(OffX+ c * CellW, OffY+ r * CellH,
OffX+ (c+ 1) * CellW, OffY+ (r+ 1) * CellH);
// Value & Label text inside cell
ImgCanvas.Canvas.Font.Size:= 16;
ImgCanvas.Canvas.Font.Style2:= fsBold2;
ImgCanvas.Canvas.Font.Color:= clBlack;
ImgCanvas.Canvas.Brush.Style:= bsClear;
ImgCanvas.Canvas.TextOut(OffX + c * CellW + 42, OffY + r * CellH + 28, IntToStr(rVal));
ImgCanvas.Canvas.Font.Size:= 8;
ImgCanvas.Canvas.Font.Style2:= fsNormal;
if (r=0) and (c=0) then ImgCanvas.Canvas.TextOut(OffX + c * CellW + 15, OffY + r * CellH + 65, '(True Pos)')
else if (r=0) and (c=1) then ImgCanvas.Canvas.TextOut(OffX+ c * CellW+ 10, OffY+ r * CellH+ 65, '(False Neg)')
else if (r=1) and (c=0) then ImgCanvas.Canvas.TextOut(OffX+ c * CellW+ 10, OffY+ r * CellH+ 65, '(False Pos)')
else if (r=1) and (c=1) then ImgCanvas.Canvas.TextOut(OffX+ c * CellW+ 15, OffY+ r * CellH+ 65, '(True Neg)');
ImgCanvas.Canvas.Brush.Style:= bsSolid;
ImgCanvas.Canvas.Font.Size:= 9;
ImgCanvas.Canvas.Font.Style2:= fsBold2;
end;
end;
end;

Top comments (0)