ANALYSIS TOOL BOX
intermediate20 min

Bayesian Updating: From Prior to Posterior

Use ProbabilityOfHypothesisGivenData to update your beliefs with evidence — the core technique in intelligence analysis.

bayesprobabilityintelligence-analysis

Analysts are constantly asked to form judgments under uncertainty: Is this supplier a fraud risk? Is this pattern of behavior consistent with insider threat? Is this market showing early signs of a shift?

Bayesian reasoning gives you a principled way to update those judgments as new evidence arrives. Instead of asking "what do I believe?" you ask "what did I believe before, and how much should this new evidence change that belief?"

This tutorial walks through the mechanics using ProbabilityOfHypothesisGivenData and a realistic intelligence analysis scenario.

Prerequisites

  • Python 3.9+
  • analysistoolbox installed (pip install analysistoolbox)
  • Comfort with basic probability (P(A), P(B|A))

The Bayesian framework

Bayes' theorem in plain English:

Posterior probability = (Prior probability × Likelihood of the evidence) / (Total probability of the evidence)

Or symbolically:

python
P(H | E) = P(E | H) × P(H) / P(E)

Where:

  • P(H) = your prior: what you believed about hypothesis H before seeing evidence E
  • P(E | H) = the likelihood: how probable is this evidence if H is true?
  • P(E | ¬H) = the likelihood of the evidence if H is false
  • P(H | E) = the posterior: your updated belief after seeing evidence E
Note

In intelligence analysis, this framework underlies techniques like Analysis of Competing Hypotheses (ACH). ACH asks analysts to evaluate evidence against multiple hypotheses — Bayesian updating is the formal mathematical version of that intuition.

The scenario

You are an analyst at a financial institution. A transaction flagged by your alert system has the following characteristics: large round-dollar amount, early-morning timing, a counterparty in a high-risk jurisdiction.

Your hypotheses:

  • H: This transaction is fraudulent
  • ¬H: This transaction is legitimate

Prior belief: Based on historical base rates, roughly 2% of transactions that trigger this alert are actually fraudulent. So P(H) = 0.02.

Evidence 1: The transaction is a large round-dollar amount. In your historical data, 70% of fraudulent transactions use round amounts, but only 20% of legitimate transactions do.

Step 1: First update

python
from analysistoolbox.probability import ProbabilityOfHypothesisGivenData

posterior_1 = ProbabilityOfHypothesisGivenData(
    prior_probability_of_hypothesis=0.02,
    probability_of_data_given_hypothesis=0.70,
    probability_of_data_given_not_hypothesis=0.20,
    print_results=True,
)

Output:

python
Prior probability of hypothesis:    0.0200
P(evidence | hypothesis):           0.7000
P(evidence | not hypothesis):       0.2000
Posterior probability of hypothesis: 0.0667

The round-dollar amount raised our fraud probability from 2% to about 6.7%. Meaningful, but still low.

Step 2: Chain updates as new evidence arrives

Bayesian updating is sequential — the posterior from one update becomes the prior for the next. Each piece of evidence shifts the probability, compounding the effect.

Evidence 2: The transaction occurred at 2:47 AM. In your data, 80% of fraudulent transactions occur outside business hours, but only 15% of legitimate transactions do.

python
posterior_2 = ProbabilityOfHypothesisGivenData(
    prior_probability_of_hypothesis=posterior_1,
    probability_of_data_given_hypothesis=0.80,
    probability_of_data_given_not_hypothesis=0.15,
    print_results=True,
)

Output:

python
Prior probability of hypothesis:    0.0667
P(evidence | hypothesis):           0.8000
P(evidence | not hypothesis):       0.1500
Posterior probability of hypothesis: 0.2752

Now at 27.5%. A single overnight transaction more than quadrupled our suspicion.

Evidence 3: The counterparty is in a jurisdiction on your high-risk watchlist. In your data, 60% of fraudulent transactions involve high-risk jurisdictions, and 8% of legitimate transactions do.

python
posterior_3 = ProbabilityOfHypothesisGivenData(
    prior_probability_of_hypothesis=posterior_2,
    probability_of_data_given_hypothesis=0.60,
    probability_of_data_given_not_hypothesis=0.08,
    print_results=True,
)

print(f"\nFinal fraud probability: {posterior_3:.1%}")

Output:

python
Prior probability of hypothesis:    0.2752
P(evidence | hypothesis):           0.6000
P(evidence | not hypothesis):       0.0800
Posterior probability of hypothesis: 0.7399

Final fraud probability: 74.0%

Three pieces of evidence moved the probability from 2% to 74%.

Step 3: Visualize the update chain

python
import matplotlib.pyplot as plt

steps = ['Base rate', 'Round amount', 'After hours', 'High-risk jurisdiction']
probabilities = [0.02, posterior_1, posterior_2, posterior_3]

fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(steps, [p * 100 for p in probabilities], 'o-', color='#2563EB', linewidth=2, markersize=8)
ax.axhline(50, color='#DC2626', linestyle='--', alpha=0.5, label='50% threshold')
ax.set_ylabel('Fraud Probability (%)')
ax.set_title('Bayesian Updating: Fraud Probability After Each Evidence Update')
ax.set_ylim(0, 100)
ax.legend()
plt.tight_layout()
plt.show()

Interpreting the results

Teaching Note

Three things make this analysis defensible: (1) the prior was grounded in historical base rates, not intuition; (2) the likelihoods came from data, not guesses; (3) the updating process is mathematically transparent — anyone can trace exactly how the probability changed and why. This is the epistemic discipline that separates structured analysis from gut feel. If a stakeholder challenges your conclusion, you can show them exactly which piece of evidence drove the probability and what assumptions you'd need to change for the conclusion to flip.

When to escalate? That's a policy decision, not an analytical one. The analyst's job is to produce a well-calibrated probability — the institution's compliance team sets the threshold at which escalation is required (commonly 50% or 70%). Keep these roles distinct.

What if a piece of evidence is exculpatory? Bayesian updating works in both directions. If an additional check shows the counterparty has an existing 5-year relationship with the institution, you'd use a likelihood where P(evidence | fraud) is very low (fraudsters rarely have long legitimate relationships), driving the posterior back down.

Full code

python
from analysistoolbox.probability import ProbabilityOfHypothesisGivenData

# Prior: 2% base rate from historical alert data
prior = 0.02

# Evidence 1: round-dollar amount
p1 = ProbabilityOfHypothesisGivenData(
    prior_probability_of_hypothesis=prior,
    probability_of_data_given_hypothesis=0.70,
    probability_of_data_given_not_hypothesis=0.20,
)

# Evidence 2: after-hours transaction
p2 = ProbabilityOfHypothesisGivenData(
    prior_probability_of_hypothesis=p1,
    probability_of_data_given_hypothesis=0.80,
    probability_of_data_given_not_hypothesis=0.15,
)

# Evidence 3: high-risk jurisdiction counterparty
p3 = ProbabilityOfHypothesisGivenData(
    prior_probability_of_hypothesis=p2,
    probability_of_data_given_hypothesis=0.60,
    probability_of_data_given_not_hypothesis=0.08,
)

print(f"Prior:                  {prior:.1%}")
print(f"After round-dollar:     {p1:.1%}")
print(f"After after-hours:      {p2:.1%}")
print(f"After high-risk country:{p3:.1%}")

Functions used in this tutorial