Applying Benford's Law to Payment-Amount Anomaly Detection
Organic payment amounts obey a distribution most people find counterintuitive: across a large, unconstrained batch of transactions, the leading digit is a 1 about 30% of the time and a 9 under 5% of the time. Fabricated amounts do not — a fraudster inventing invoice values, or a manual-entry process producing round numbers, flattens that curve. Benford's Law turns that regularity into a batch-level anomaly signal, and it is the round-amount check inside the fused scorer described under fraud-pattern detection signals, part of the broader exception routing and fraud-pattern detection practice. This guide builds the detector: observed first-digit frequencies over a batch, a chi-square deviation from Benford's expectation, and a single batch-level flag — with a hard warning against the most common misuse, applying it to one transaction.
The critical property, and the one that separates a sound implementation from a harmful one, is that Benford is a statement about distributions, not individual values. A single $5,000.00 wire tells you nothing — round amounts are perfectly normal one at a time. It is only when a batch of thousands of amounts collectively deviates from the expected digit curve that you have evidence of fabrication or systemic manipulation. Unlike the per-entry duplicate-debit signal, this signal scores a batch and never a row, and treating it otherwise produces confident nonsense.
Concept Spec: The Expected Distribution and a Chi-Square Test
Benford's Law gives the probability that the leading digit of a value equals each of 1 through 9:
This yields , , descending to . To test whether a batch of amounts conforms, count the observed occurrences of each leading digit and compare against the expected counts with Pearson's chi-square statistic:
With nine digit categories there are eight degrees of freedom, so the conventional 0.05-significance critical value is : a batch whose statistic exceeds it deviates from Benford's expectation more than sampling noise alone would explain. Computing the first digit of each amount and tallying is one pass, time, and the histogram is nine buckets, space — so the test is trivially cheap relative to the batch it screens. The cost discipline is entirely in when the test is valid, which is a matter of sample size and the nature of the amounts, not compute.
Full Annotated Implementation
The function below extracts the leading digit of each Decimal amount, builds the nine-bucket histogram, computes the chi-square statistic against Benford's expectation, and returns a single batch-level verdict. It refuses to run below a minimum sample size rather than emit a meaningless statistic, and it operates on Decimal so leading-digit extraction is exact.
from __future__ import annotations
import math
from dataclasses import dataclass
from decimal import Decimal
from typing import Iterable
# Benford expected probabilities for leading digits 1..9.
BENFORD_P: dict[int, float] = {
d: math.log10(1 + 1 / d) for d in range(1, 10)
}
# Chi-square critical value, 8 degrees of freedom, alpha = 0.05.
CHI2_CRITICAL_8DF = 15.51
@dataclass(frozen=True, slots=True)
class BenfordResult:
sample_size: int
chi_square: float
anomalous: bool
observed: dict[int, int]
reason: str
def leading_digit(amount: Decimal) -> int | None:
"""Return the first significant digit (1-9) of a positive amount.
Zero and negative amounts have no Benford leading digit and are excluded
from the sample rather than coerced.
"""
a = amount.copy_abs()
if a == 0:
return None
digits = a.as_tuple().digits # exact, no float rounding
for d in digits: # first non-zero significant digit
if d != 0:
return d
return None
def benford_batch_check(
amounts: Iterable[Decimal],
min_sample: int = 300,
) -> BenfordResult:
"""Batch-level Benford conformance test.
Returns a single verdict for the WHOLE batch. This is never valid for one
transaction: Benford describes the distribution of a large set of
unconstrained amounts, so `min_sample` guards against a statistic computed
on too few points to be meaningful.
"""
observed: dict[int, int] = {d: 0 for d in range(1, 10)}
n = 0
for amount in amounts:
d = leading_digit(amount)
if d is not None:
observed[d] += 1
n += 1
if n < min_sample:
return BenfordResult(
sample_size=n, chi_square=0.0, anomalous=False,
observed=observed,
reason=f"sample {n} < min {min_sample}: test not applicable",
)
chi_square = 0.0
for d in range(1, 10):
expected = n * BENFORD_P[d]
chi_square += (observed[d] - expected) ** 2 / expected
anomalous = chi_square > CHI2_CRITICAL_8DF
reason = (
f"chi2 {chi_square:.2f} > {CHI2_CRITICAL_8DF}"
if anomalous else f"chi2 {chi_square:.2f} within expectation"
)
return BenfordResult(n, chi_square, anomalous, observed, reason)
Calibration
Two calibration choices decide whether the test is trustworthy or noise:
- Minimum sample size. Below a few hundred amounts, sampling variance alone pushes the chi-square around wildly and the test flags healthy batches. A
min_sampleof 300 is a practical floor; 1,000+ is comfortable. Never lower it to squeeze a verdict out of a small batch — return "not applicable" instead, as the implementation does. A statistic on 40 amounts is not a weak signal, it is a wrong one. - Which amount field. Test the field that is free to vary across many orders of magnitude: the transaction amount as originated. Do not test a field that is bounded or administered — a fee column that is always $25 or $35, a fixed subscription price, or a set of amounts all in the same narrow range — because those legitimately violate Benford and will flag every time. The right input is a broad population of unconstrained payment amounts; the wrong input guarantees a false positive.
A useful refinement once the batch test is stable is to segment: run the check per originator or per file rather than across the whole day, so a single fabricated batch is not diluted by millions of clean amounts. Chi-square on the whole day can wash out a small poisoned batch; per-file granularity surfaces it.
Before and After
Two same-size batches of 1,200 wire amounts, one organic and one seeded with fabricated round values:
| Batch | leading-digit shape | (8 df) | verdict |
|---|---|---|---|
| A — organic settlement | ~30% ones, smooth Benford decay | 9.4 | within expectation → post batch |
| B — 40% seeded round amounts | spike at 1 and 5, deficit at 7–9 | 63.1 | anomalous → flag batch for review |
| C — 90 amounts (small file) | irrelevant | — | not applicable (sample < 300) |
Batch A conforms and clears; the reconciliation continues normally. Batch B is dominated by injected $1,000, $5,000, and $50,000 fabrications, so its first-digit histogram spikes at 1 and 5 and starves the high digits, driving the chi-square far past 15.51 — the batch is flagged for an analyst to inspect the originator, not any single wire. Batch C is too small to test at all; forcing a statistic on it would have produced a coin-flip verdict, so the detector correctly abstains. Note what the flag on B does not do: it does not accuse any individual $5,000 wire, because a round wire is normal one at a time.
Failure Modes & Guardrails
- Per-transaction misuse. The single most damaging error is scoring one payment with Benford. The law says nothing about an individual value — a lone $5,000.00 wire has a leading digit of 5 and that is unremarkable. Wiring the batch statistic into a per-transaction gate produces confident false accusations and will not survive model review. Keep the output batch-level: it flags a population for inspection, and it feeds the fused scorer as a batch-context feature, never as a per-row score.
- Small samples. A chi-square on too few points is dominated by variance, so healthy small files flag and the desk learns to ignore Benford entirely. The
min_sampleguard exists precisely to return "not applicable" rather than a spurious anomaly; do not defeat it to get a number. - Bounded or administered amounts. Benford only holds for values free to span multiple orders of magnitude. Fee schedules, capped micro-payments, fixed subscription tiers, and any amount clustered in a narrow band legitimately violate the distribution and will flag on every run. Applying the test to such a field is a guaranteed false positive; restrict it to broad, unconstrained originated amounts and exclude administered fields at the source.
Frequently Asked Questions
Can I flag a single transaction with Benford's Law?
No, and this is the one rule that matters most. Benford describes the distribution of leading digits across a large set of amounts; it makes no claim about any individual value. A single round $5,000 wire is perfectly normal. The detector returns a batch-level verdict that flags a whole population — an originator, a file — for inspection, and it must never be reduced to a per-transaction score.
How many amounts do I need for a valid test?
A few hundred at minimum; a thousand or more is comfortable. Below roughly 300, sampling variance alone swings the chi-square enough to flag healthy batches, so the implementation returns "not applicable" rather than a misleading statistic. Raising the floor is safe; lowering it to force a verdict on a small file is not.
Which amount field should I test?
The originated transaction amount — a field free to range over many orders of magnitude. Avoid bounded or administered fields such as fixed fees, capped payments, or subscription tiers, because those legitimately break Benford and will flag on every batch. Testing the wrong field is the most common source of false positives after per-transaction misuse.
What does an anomalous chi-square actually mean?
It means the batch's leading-digit distribution deviates from Benford's expectation more than random sampling would explain — evidence worth a human look, not proof of fraud. Legitimate causes exist (a batch dominated by one product priced at a round number, for instance), which is exactly why the output routes a population to review rather than auto-rejecting anything.
Related
- Fraud-Pattern Detection Signals in Payment Reconciliation — the parent guide where this batch-level signal fuses with duplicate, velocity, and structuring checks.
- Detecting Duplicate ACH Debits with Idempotency Hashing — a per-entry deterministic signal that contrasts with this batch-level statistic.
- Velocity Checks for Wire-Transfer Anomaly Detection — the stateful per-key sibling signal that catches bursts.
- Reducing False Positives in Amount Tolerance Rules — the amount-matching counterpart, where Decimal-exact amount handling also matters.