Dev.to AI 🤖 Ai 👁 0 📖 3 min read

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. Scr

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;
📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.