DEV Community

Max Kleiner
Max Kleiner

Posted on

Confusion Matrix

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;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)