Recall

data

Recall

Definition

Recall (also called sensitivity or true positive rate) is the fraction of relevant instances that are successfully retrieved. Mathematically, recall = TP / (TP + FN), where TP is the number of true positives and FN is the number of false negatives. A recall of 1.0 means every positive instance was found; a recall of 0.0 means none were found.

In security and intelligence contexts, recall is often prioritized over precision. Missing a genuine threat (false negative) is typically more costly than investigating a false alarm (false positive). For the Prismatic Platform's OSINT tools and Perimeter security rating system, high recall in threat detection means fewer missed vulnerabilities and sanctions matches.

Recall is not a standalone metric -- it must always be interpreted alongside precision. A classifier that labels everything as positive achieves perfect recall (1.0) but zero precision (0.0). The interplay between these metrics, controlled by the classification threshold, defines the operational characteristics of any detection system.

Core Concepts

The Confusion Matrix

All binary classification metrics derive from the confusion matrix, a 2x2 table of outcomes:

Predicted PositivePredicted Negative
Actually PositiveTrue Positive (TP)False Negative (FN) -- Type II Error
Actually NegativeFalse Positive (FP) -- Type I ErrorTrue Negative (TN)

From this matrix, the fundamental metrics are derived:

MetricFormulaMeasuresAlias
RecallTP / (TP + FN)Detection completenessSensitivity, TPR, Hit Rate
PrecisionTP / (TP + FP)Detection accuracyPositive Predictive Value
SpecificityTN / (TN + FP)Negative identificationTrue Negative Rate, Selectivity
Accuracy(TP + TN) / (TP + TN + FP + FN)Overall correctness--
F1 Score2 * (Precision * Recall) / (Precision + Recall)Balanced measureHarmonic mean
False Positive RateFP / (FP + TN)False alarm rateFall-out, 1 - Specificity
False Negative RateFN / (FN + TP)Miss rate1 - Recall

Precision-Recall Tradeoff

The precision-recall tradeoff is fundamental to every detection system. Moving the classification threshold controls where on this tradeoff curve the system operates:

ThresholdRecallPrecisionBehavior
Very low (0.1)Very high (~0.98)Very low (~0.10)Almost everything flagged; analyst overload
Low (0.3)High (~0.90)Low (~0.30)Many false alarms but few misses
Medium (0.5)Moderate (~0.75)Moderate (~0.60)Balanced operation
High (0.7)Low (~0.50)High (~0.85)Few false alarms but many misses
Very high (0.9)Very low (~0.20)Very high (~0.95)Almost nothing flagged; many threats missed

The optimal threshold depends entirely on the cost asymmetry between false negatives and false positives in the specific domain.

F-Beta Score Family

The F1 score weights precision and recall equally. When the costs are asymmetric, the F-beta score allows explicit weighting:

MetricFormulaWeightingUse When
F0.5(1 + 0.25) * P * R / (0.25P + R)Precision 2x recallFalse positives expensive (spam detection)
F12 * P * R / (P + R)Equal weightNo cost preference
F2(1 + 4) * P * R / (4P + R)Recall 2x precisionFalse negatives expensive (threat detection)
F5(1 + 25) * P * R / (25P + R)Recall 5x precisionMissing anything is catastrophic (medical screening)

For Prismatic Platform's sanctions screening, F2 is the primary metric because missing a sanctioned entity has severe regulatory consequences.

ROC Curve vs Precision-Recall Curve

Two visualization approaches exist for evaluating classifier performance across all thresholds:

CharacteristicROC CurvePrecision-Recall Curve
X-axisFalse Positive RateRecall
Y-axisTrue Positive Rate (Recall)Precision
Random baselineDiagonal line (AUC = 0.5)Horizontal at prevalence
Perfect classifierTop-left corner (AUC = 1.0)Top-right corner (AUC = 1.0)
Imbalanced dataCan be misleadingly optimisticAccurately reflects difficulty
Best forBalanced datasetsImbalanced datasets (rare positives)
Summary metricAUC-ROCAUC-PR (Average Precision)

For security detection where positives are rare (1 sanctioned entity per 10,000 screened), the precision-recall curve is more informative than the ROC curve. A classifier with 99% ROC-AUC might have only 20% precision at 90% recall when the positive class is 0.01% of the data.

Technical Deep Dive

Per-Class vs Aggregated Recall

In multi-class classification, recall must be computed per class. Aggregation strategies have different properties:

Micro-averaging: Pools all TP and FN across classes before computing:

Recall_micro = sum(TP_i) / sum(TP_i + FN_i)

Weighted by class frequency -- dominated by the majority class.

Macro-averaging: Computes recall per class, then averages:

Recall_macro = mean(Recall_i for each class i)

Gives equal weight to every class regardless of frequency.

Weighted averaging: Like macro, but weighted by class support:

Recall_weighted = sum(support_i * Recall_i) / sum(support_i)
AveragingRare Class SensitivityMajority Class BiasUse When
MicroLowHighOverall system performance
MacroHighLowAll classes equally important
WeightedModerateModerateClass importance proportional to frequency

For sanctions screening with hundreds of sanction lists of varying sizes, macro-averaged recall ensures that recall on small lists (e.g., North Korea designations with ~50 entries) is weighted equally with large lists (e.g., EU consolidated list with ~10,000 entries).

Threshold Tuning Strategies

Choosing the optimal classification threshold is a decision problem, not a statistical one. Several strategies exist:

  1. Cost-minimization: Define cost_FN and cost_FP, then minimize total expected cost across the threshold range
  2. F-beta optimization: Choose threshold that maximizes F-beta for the desired beta
  3. Recall floor: Set minimum acceptable recall (e.g., 95%), then maximize precision subject to that constraint
  4. Youden's J statistic: Maximize J = Sensitivity + Specificity - 1 (equivalent to maximizing distance from ROC diagonal)
  5. Operating point from PR curve: Select the "knee" of the precision-recall curve where marginal recall gain starts requiring large precision sacrifice

Calibration and Recall

A classifier's predicted probabilities should be calibrated -- a prediction of 0.8 should mean the item is positive 80% of the time. Poorly calibrated classifiers can have misleading recall at specific thresholds because the predicted probabilities do not reflect true probabilities. Platt scaling or isotonic regression can recalibrate predictions post-hoc.

Recall in Information Retrieval

In search systems (Meilisearch, Elasticsearch), recall measures query completeness:

Recall@k = |relevant documents in top-k results| / |total relevant documents|

Unlike classification recall, search recall is measured at a specific cutoff (k). Recall@10 measures how many relevant documents appear in the first 10 results. Typo tolerance, stemming, and synonym expansion improve search recall by matching more query variations.

Advanced Topics

Recall Degradation Over Time

Model drift causes recall degradation when the data distribution shifts. Common causes in OSINT/security:

  • New naming conventions for sanctioned entities
  • Transliteration changes (Russian/Chinese entities in Latin script)
  • Corporate restructuring creating new entity relationships
  • New sanction list formats requiring parser updates

Continuous evaluation against a labeled holdout set detects recall degradation before it impacts operations. The Prismatic Platform's statistical detection system monitors per-detector recall weekly.

Class Imbalance and Recall

Extreme class imbalance (1:10,000 positive:negative ratio) makes high recall difficult because even a tiny FN rate translates to many missed positives. Techniques to improve recall under imbalance:

TechniqueHow It Helps RecallTradeoff
Oversampling (SMOTE)Creates synthetic positives for trainingCan cause overfitting
UndersamplingReduces negatives to balance ratioLoses information
Cost-sensitive learningPenalizes FN more heavily during trainingMay reduce precision
Ensemble methodsCombines multiple weak classifiersIncreased computation
Threshold adjustmentLowers decision boundaryIncreases false positives
Feature engineeringBetter features improve separabilityDomain expertise required

Recall in Multi-Stage Pipelines

The Prismatic Platform uses multi-stage detection pipelines where recall compounds multiplicatively:

Pipeline_Recall = Stage1_Recall * Stage2_Recall * Stage3_Recall

If each stage has 90% recall, the pipeline recall is only 72.9%. This means every filtering stage must have extremely high recall, or the pipeline will miss a significant fraction of positives. The platform addresses this by making early stages high-recall / low-precision (cast a wide net) and later stages high-precision (refine the results).

Usage in Prismatic Platform

The sanctions screening system prioritizes recall -- missing a sanctioned entity in due diligence has severe regulatory consequences. The SDN list matcher uses fuzzy matching with a low threshold to maximize recall, then ranks results by confidence for human review.

The Perimeter EASM system optimizes for recall in asset discovery: finding all exposed assets is more important than perfect classification of each asset's risk level. The statistical detection system reports recall alongside precision for each anomaly detector.

The OSINT adapter pipeline implements a three-stage architecture:

  1. Discovery stage (target recall: 99%+): Cast wide net across all 157 adapters
  2. Correlation stage (target recall: 95%): Cross-reference findings to confirm signals
  3. Assessment stage (target precision: 90%): Analyst-assisted evaluation of confirmed signals

The DD pipeline's entity matching system uses recall-focused fuzzy matching for company name comparison. Czech business names often have multiple valid representations (with/without legal form suffix, abbreviated vs. full name, Czech vs. English), requiring high-recall matching that catches all variants at the cost of some false matches that human analysts resolve.

Code Examples

Comprehensive Classification Metrics

defmodule PrismaticStats.ClassificationMetrics do
  @moduledoc """
  Calculates recall and related classification metrics.
  Used in OSINT detection, sanctions screening evaluation,
  and Perimeter EASM asset discovery assessment.
  Supports per-class, micro, macro, and weighted averaging.
  """

  @type confusion :: %{
    true_positives: non_neg_integer(),
    false_positives: non_neg_integer(),
    true_negatives: non_neg_integer(),
    false_negatives: non_neg_integer()
  }

  @type metrics :: %{
    recall: float(),
    precision: float(),
    f1: float(),
    f_beta: float(),
    specificity: float(),
    accuracy: float()
  }

  @doc """
  Calculates recall (sensitivity / true positive rate).

  ## Examples

      iex> cm = %{true_positives: 90, false_negatives: 10, false_positives: 20, true_negatives: 880}
      iex> ClassificationMetrics.recall(cm)
      0.9

  """
  @spec recall(confusion()) :: float()
  def recall(%{true_positives: tp, false_negatives: fn_count}) do
    denominator = tp + fn_count
    if denominator == 0, do: 0.0, else: tp / denominator
  end

  @doc """
  Calculates precision (positive predictive value).
  """
  @spec precision(confusion()) :: float()
  def precision(%{true_positives: tp, false_positives: fp}) do
    denominator = tp + fp
    if denominator == 0, do: 0.0, else: tp / denominator
  end

  @doc """
  Calculates F1 score (harmonic mean of precision and recall).
  """
  @spec f1_score(confusion()) :: float()
  def f1_score(cm) do
    f_beta_score(cm, 1.0)
  end

  @doc """
  Calculates F-beta score with configurable beta.
  Beta > 1 weights recall more; beta < 1 weights precision more.

  ## Examples

      iex> cm = %{true_positives: 90, false_negatives: 10, false_positives: 20, true_negatives: 880}
      iex> ClassificationMetrics.f_beta_score(cm, 2.0) |> Float.round(3)
      0.878

  """
  @spec f_beta_score(confusion(), float()) :: float()
  def f_beta_score(cm, beta) do
    p = precision(cm)
    r = recall(cm)
    beta_sq = beta * beta

    if p + r == 0 do
      0.0
    else
      (1 + beta_sq) * p * r / (beta_sq * p + r)
    end
  end

  @doc """
  Calculates all standard metrics from a confusion matrix.
  """
  @spec all_metrics(confusion(), float()) :: metrics()
  def all_metrics(cm, beta \\ 1.0) do
    %{
      recall: recall(cm),
      precision: precision(cm),
      f1: f1_score(cm),
      f_beta: f_beta_score(cm, beta),
      specificity: specificity(cm),
      accuracy: accuracy(cm)
    }
  end

  @doc """
  Calculates specificity (true negative rate).
  """
  @spec specificity(confusion()) :: float()
  def specificity(%{true_negatives: tn, false_positives: fp}) do
    denominator = tn + fp
    if denominator == 0, do: 0.0, else: tn / denominator
  end

  @doc """
  Calculates accuracy.
  """
  @spec accuracy(confusion()) :: float()
  def accuracy(%{true_positives: tp, true_negatives: tn, false_positives: fp, false_negatives: fn_count}) do
    total = tp + tn + fp + fn_count
    if total == 0, do: 0.0, else: (tp + tn) / total
  end

  @doc """
  Builds a confusion matrix from scored items at a given threshold.
  Items are tuples of {score, actually_positive?}.
  """
  @spec evaluate_at_threshold(list({float(), boolean()}), float()) :: confusion()
  def evaluate_at_threshold(scored_items, threshold) do
    Enum.reduce(scored_items, %{true_positives: 0, false_positives: 0, true_negatives: 0, false_negatives: 0}, fn
      {score, true}, acc when score >= threshold -> %{acc | true_positives: acc.true_positives + 1}
      {score, false}, acc when score >= threshold -> %{acc | false_positives: acc.false_positives + 1}
      {score, true}, acc when score < threshold -> %{acc | false_negatives: acc.false_negatives + 1}
      {score, false}, acc when score < threshold -> %{acc | true_negatives: acc.true_negatives + 1}
    end)
  end

  @doc """
  Computes precision-recall curve by evaluating at multiple thresholds.
  Returns list of {threshold, precision, recall} tuples.
  """
  @spec precision_recall_curve(list({float(), boolean()}), non_neg_integer()) ::
          list({float(), float(), float()})
  def precision_recall_curve(scored_items, steps \\ 100) do
    thresholds = for i <- 0..steps, do: i / steps

    Enum.map(thresholds, fn threshold ->
      cm = evaluate_at_threshold(scored_items, threshold)
      {threshold, precision(cm), recall(cm)}
    end)
  end
end

Multi-Class Recall with Averaging

defmodule PrismaticStats.MultiClassRecall do
  @moduledoc """
  Computes per-class and aggregated recall for multi-class classification.
  Supports micro, macro, and weighted averaging strategies.
  Used in entity type classification and threat categorization.
  """

  alias PrismaticStats.ClassificationMetrics

  @type per_class_results :: %{String.t() => ClassificationMetrics.confusion()}

  @doc """
  Computes macro-averaged recall (equal weight to all classes).
  """
  @spec macro_recall(per_class_results()) :: float()
  def macro_recall(per_class) do
    recalls = Enum.map(per_class, fn {_class, cm} -> ClassificationMetrics.recall(cm) end)

    case recalls do
      [] -> 0.0
      recalls -> Enum.sum(recalls) / length(recalls)
    end
  end

  @doc """
  Computes micro-averaged recall (weighted by class frequency).
  """
  @spec micro_recall(per_class_results()) :: float()
  def micro_recall(per_class) do
    totals =
      Enum.reduce(per_class, %{tp: 0, fn: 0}, fn {_class, cm}, acc ->
        %{tp: acc.tp + cm.true_positives, fn: acc.fn + cm.false_negatives}
      end)

    denominator = totals.tp + totals.fn
    if denominator == 0, do: 0.0, else: totals.tp / denominator
  end

  @doc """
  Computes weighted-average recall (weighted by class support).
  """
  @spec weighted_recall(per_class_results()) :: float()
  def weighted_recall(per_class) do
    {weighted_sum, total_support} =
      Enum.reduce(per_class, {0.0, 0}, fn {_class, cm}, {sum, total} ->
        support = cm.true_positives + cm.false_negatives
        recall = ClassificationMetrics.recall(cm)
        {sum + support * recall, total + support}
      end)

    if total_support == 0, do: 0.0, else: weighted_sum / total_support
  end
end

Sanctions Screening Evaluator

defmodule PrismaticCompliance.ScreeningEvaluator do
  @moduledoc """
  Evaluates sanctions screening recall against known test sets.
  Prioritizes recall (F2 metric) because missing a sanctioned
  entity has severe regulatory consequences.
  """

  alias PrismaticStats.ClassificationMetrics

  require Logger

  @recall_floor 0.95
  @f2_floor 0.85

  @doc """
  Evaluates screening system against a labeled test set.
  Returns metrics and compliance status.
  """
  @spec evaluate(list({String.t(), boolean()}), list({String.t(), float()})) :: map()
  def evaluate(labeled_entities, screening_results) do
    scored_items = build_scored_items(labeled_entities, screening_results)
    cm = ClassificationMetrics.evaluate_at_threshold(scored_items, 0.5)
    metrics = ClassificationMetrics.all_metrics(cm, 2.0)

    compliance = %{
      recall_compliant: metrics.recall >= @recall_floor,
      f2_compliant: metrics.f_beta >= @f2_floor,
      missed_entities: cm.false_negatives
    }

    unless compliance.recall_compliant do
      Logger.warning("Sanctions screening recall below floor: #{Float.round(metrics.recall, 3)} < #{@recall_floor}",
        recall: metrics.recall,
        missed: cm.false_negatives
      )
    end

    %{metrics: metrics, confusion_matrix: cm, compliance: compliance}
  end

  @spec build_scored_items(list({String.t(), boolean()}), list({String.t(), float()})) ::
          list({float(), boolean()})
  defp build_scored_items(labeled, results) do
    result_map = Map.new(results)

    Enum.map(labeled, fn {entity_id, is_sanctioned} ->
      score = Map.get(result_map, entity_id, 0.0)
      {score, is_sanctioned}
    end)
  end
end

Common Pitfalls

PitfallProblemSolution
Reporting recall without precisionPerfect recall is trivial (classify everything as positive)Always report both metrics together
Using accuracy on imbalanced data99.9% accuracy possible by predicting all-negativeUse recall, precision, F-beta, or AUC-PR instead
Micro-averaging hiding rare class failures99% micro recall with 0% recall on critical rare classUse macro-averaging or per-class reporting
Static threshold in shifting dataOptimal threshold changes as data distribution driftsRe-evaluate threshold periodically on fresh labeled data
Ignoring pipeline recall compounding90% * 90% * 90% = 72.9% pipeline recallMake early stages very high recall, refine later
Evaluating on training dataOverfitting gives misleadingly high recallAlways evaluate on held-out test set
Using ROC-AUC with extreme imbalanceROC-AUC looks good even when PR performance is poorUse AUC-PR for imbalanced datasets
Ignoring per-class recallAggregate recall masks poor performance on specific classesReport per-class recall especially for critical classes
No temporal split in evaluationRandom train/test split leaks future informationUse temporal split (train on past, test on future)
Threshold tuned to test setOptimistic recall estimate that does not generalizeTune threshold on validation set, evaluate on separate test set

Best Practices

  1. Prioritize recall for security-critical detection -- missing threats is worse than investigating false alarms. Use F2 or higher beta values for scoring.

  2. Report recall alongside precision -- recall alone is meaningless (classifying everything as positive gives perfect recall).

  3. Use threshold tuning -- adjust classification thresholds based on the specific cost asymmetry of false negatives vs. false positives in the operational context.

  4. Measure per-class recall -- aggregate recall can mask poor detection of rare but important classes. Sanctions screening must report per-list recall.

  5. Track recall over time -- model drift causes recall degradation that must be detected through continuous evaluation against labeled test sets.

  6. Use precision-recall curves, not ROC curves, for imbalanced data -- PR curves provide a more honest assessment when positives are rare.

  7. Design pipelines for recall preservation -- early stages should be high-recall filters; precision refinement happens in later stages.

  8. Evaluate with temporal splits -- random splits leak future information. Train on historical data, evaluate on recent data.

  9. Set recall floors for compliance-critical systems -- sanctions screening should have a documented minimum recall (e.g., 95%) with automated alerting when it drops below.

  10. Regularly refresh evaluation datasets -- new entity names, transliterations, and corporate structures require updated test sets to maintain meaningful recall measurement.

  • Statistical Detection -- anomaly detection systems where recall is critical
  • Threshold -- the decision boundary that controls recall-precision tradeoff
  • Signal -- the raw intelligence data recall measures detection completeness for
  • R-squared -- model quality metric complementary to recall (for regression)
  • Precision -- the complementary metric measuring detection accuracy
  • F1 Score -- harmonic mean balancing precision and recall
  • Confusion Matrix -- the fundamental table from which recall is derived
  • ROC Curve -- visualization of classifier performance across thresholds
  • Specificity -- true negative rate, the counterpart to recall
  • Accuracy -- overall correctness metric, misleading on imbalanced data
  • AUC -- area under ROC or PR curve, threshold-independent performance measure
  • Statistics -- mathematical foundations underlying recall computation

See Also


Connect & Contribute

Created by Tomas Korcak (korczis) | Open Source under GHL