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+
analysistoolboxinstalled (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:
P(H | E) = P(E | H) × P(H) / P(E)
Where:
P(H)= your prior: what you believed about hypothesis H before seeing evidence EP(E | H)= the likelihood: how probable is this evidence if H is true?P(E | ¬H)= the likelihood of the evidence if H is falseP(H | E)= the posterior: your updated belief after seeing evidence E
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
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:
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.
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:
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.
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:
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
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
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
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%}")