Executive Summary
Benford's Law read on 1,500 values from Transaction Amount
The short answer
The vendor invoice amounts conform to Benford's Law. Across 1,500 transactions, the first-digit distribution shows close conformity with a MAD of 0.0049 and a chi-square p-value of 0.874—no digits deviate beyond ordinary sampling variation. This pattern is consistent with organic, unmanipulated transaction data.
The detail
Mean absolute deviation of 0.0049 places the dataset squarely in Nigrini's close-conformity band, and the chi-square goodness-of-fit test yields p = 0.874, well above any threshold for concern. Zero individual digits are flagged; all standardized residuals remain within ±1.96 standard errors of their Benford expectations. The verdict is straightforward: conforms.
What this can't tell you
Benford conformity is a screening test, not a proof of integrity. It detects systematic digit-pattern manipulation but cannot uncover fraud schemes that happen to fall within the natural distribution, targeted falsification of individual invoices, or collusive schemes. A passing Benford screen is necessary but not sufficient for audit clearance.
Analysis Overview
Benford's Law screening of 1,500 values from Transaction Amount.
The short answer
The vendor invoice amounts follow the natural pattern predicted by Benford's Law, with no evidence of systematic manipulation or fabrication. Digit 1 appears 29.3 percent of the time (expected 30.1 percent) and digit 9 appears 5.2 percent (expected 4.6 percent)—minor variations consistent with random sampling from a genuine transaction pool.
The detail
Benford's Law screening compares observed first-digit distributions against the log₁₀(1 + 1/d) curve, which models how organically generated amounts naturally arise. The mean absolute deviation (MAD) of 0.0049 falls within Nigrini's close-conformity band. Chi-square goodness-of-fit yields p = 0.874, indicating no systematic departure from expectation. All 1,500 analyzed values track the expected downward slope from 30.1 percent at digit 1 to 4.6 percent at digit 9. This is the signature of unmanipulated transaction data.
What this can't tell you
Benford conformity rules out certain broad manipulation patterns (e.g., wholesale round-number entry) but cannot detect small-scale targeted fraud, collusion-based schemes, or invoices falsified within the natural digit distribution. This screen is a negative test: it identifies where to look, not what was done.
Data Quality
How the amounts were cleaned before digit extraction.
The short answer
Of 1,535 rows loaded, 1,500 entered the screen. Fifteen blank or non-numeric entries were removed, 20 zeros were dropped (a zero has no first digit), 30 negative values were kept via absolute value, and 511 values below 1 were retained using the first-significant-digit definition.
The detail
Data quality rules applied before digit extraction: 15 blank or non-numeric entries removed because they carry no digits to read. 20 zero values dropped—zeros have no first significant digit. 30 negative values (refunds, reversals) kept by taking absolute value; a negative amount still has a leading digit. 511 values below 1 kept because the first significant digit is defined as the leftmost non-zero digit (so 0.0042 counts as digit 4, just as 4,200 does). This treatment preserves the full range of the data while respecting the mathematical definition of first significant digit. The final 1,500 values exceed the 100-value minimum required for Benford screening.
What this can't tell you
Preprocessing choices affect which patterns emerge. Absolute-value treatment of negatives assumes refunds and reversals belong in the same digit pool as purchases; if reversals follow distinct business logic, separate screening might be warranted.
First Digits: Observed vs Expected
Share of values starting with each digit 1-9, against Benford's Law.
The short answer
The observed first-digit distribution tracks the Benford slope closely across all nine digits. Digit 2 is furthest above its expectation (18.5 percent observed vs. 17.6 percent expected), and digit 1 is furthest below (29.3 percent vs. 30.1 percent expected)—both gaps are small and consistent with normal sampling variation.
The detail
Benford's Law predicts a downward slope: digit 1 at 30.1 percent, digit 2 at 17.6 percent, digit 3 at 12.49 percent, declining to digit 9 at 4.58 percent. Observed shares: digit 1 is 29.3 percent, digit 2 is 18.5 percent, digit 3 is 11.93 percent, digit 4 is 9.87 percent, digit 5 is 7.4 percent, digit 6 is 6.4 percent, digit 7 is 6.07 percent, digit 8 is 5.4 percent, digit 9 is 5.2 percent. The observed bars follow the expected curve without systematic bulges or dips. The largest individual deviation is digit 2 at +0.9 percentage points; all others are smaller. This consistency across the full range of digits is the hallmark of naturally distributed amounts.
What this can't tell you
A smooth digit distribution does not rule out systematic bias within specific vendors, time periods, or transaction types. Benford screening is a population-level test; subgroup anomalies may be hidden in the aggregate.
Per-Digit Deviations
Observed vs expected share per digit with standardized residuals.
| Digit | Observed | Expected | Std Residual | Flag |
|---|---|---|---|---|
| 1 | 0.2927 | 0.301 | -0.71 | |
| 2 | 0.1847 | 0.1761 | 0.87 | |
| 3 | 0.1193 | 0.1249 | -0.66 | |
| 4 | 0.0987 | 0.0969 | 0.23 | |
| 5 | 0.074 | 0.0792 | -0.74 | |
| 6 | 0.064 | 0.0669 | -0.46 | |
| 7 | 0.0607 | 0.058 | 0.44 | |
| 8 | 0.054 | 0.0512 | 0.5 | |
| 9 | 0.052 | 0.0458 | 1.16 |
The short answer
No digit's observed share deviates more than ordinary sampling wobble from its Benford expectation. Every standardized residual falls between −0.71 and +1.16, well short of the ±1.96 threshold used to flag anomalies.
The detail
Standardized residuals measure how many standard errors each digit's observed share sits from its Benford expectation, given 1,500 values. Flagging threshold: |residual| > 1.96. Observed residuals: digit 1 is −0.71, digit 2 is +0.87, digit 3 is −0.66, digit 4 is +0.23, digit 5 is −0.74, digit 6 is −0.46, digit 7 is +0.44, digit 8 is +0.50, digit 9 is +1.16. None crosses 1.96 in either direction. The largest, digit 9 at +1.16, is still 0.80 points below the flag threshold. This means the observed distribution is consistent with random sampling from a Benford-distributed population; no digit stands out as over- or under-represented.
What this can't tell you
Absence of per-digit flags does not rule out correlated deviations across multiple digits or concentration of anomalies in a small subset of amounts. Standardized residuals assume independence; they do not detect patterns like clustering of high amounts or temporal trends.
Data Hygiene
What was dropped, what was kept, and why.
| Reason | Rows Affected | Action |
|---|---|---|
| Blank or non-numeric entries | 15 | dropped — no digits to read |
| Zero values | 20 | dropped — a zero has no first digit |
| Negative values | 30 | kept — absolute value used |
| Values below 1 | 511 | kept — first significant digit used |
| Values analyzed | 1500 | screened against Benford's Law |
The short answer
Of 1,535 loaded invoice records, 35 were dropped before screening (15 blank or non-numeric, 20 exactly zero), leaving 1,500 values. Negative values (30 rows) were kept via absolute value; sub-unit amounts (511 rows) were kept using the first-significant-digit rule. The final analyzed set is clean and defensible.
The detail
Starting population: 1,535 rows. Blanks and non-numeric entries: 15 dropped (no digits to extract). Zeros: 20 dropped (zero has no first digit). Negatives: 30 kept, converted to absolute value. Values below 1: 511 kept, first significant digit extracted (e.g., 0.047 → digit 4). Final analyzed count: 1,500. This preprocessing follows forensic-accounting convention and preserves all information-bearing entries.
What this can't tell you
The preprocessing is transparent but does not validate the source data itself. If the 1,535 loaded rows already contain falsified, duplicate, or corrupted entries, no cleaning step will reveal that. Benford screening assumes the input data are genuine transaction records, not fabricated or massaged before loading.
Method and Thresholds
Nigrini's conformity bands, the tests run, and when Benford applies.
| Band | Mad Cutoff | Meaning | This Data |
|---|---|---|---|
| Close conformity | 0.006 | MAD below 0.006 — observed digits track the Benford curve closely | yes |
| Acceptable conformity | 0.012 | MAD from 0.006 up to 0.012 — ordinary sampling wobble, no cause for review | |
| Marginal conformity | 0.015 | MAD from 0.012 up to 0.015 — borderline; worth a look if other signals agree | |
| Nonconformity | 0.015 | MAD of 0.015 or more — the digit pattern departs from Benford; investigate |
The short answer
The analysis used Nigrini's mean absolute deviation (MAD) thresholds and a chi-square goodness-of-fit test. The MAD of 0.0049 falls in the close conformity band (below 0.006), and the chi-square p-value of 0.874 indicates the observed digit distribution is well within sampling variation of Benford's expectation.
The detail
First-digit test: chi-square goodness-of-fit against Benford's Law with 8 degrees of freedom, p = 0.874. MAD of 0.0049 read against Nigrini's conformity thresholds: close conformity below 0.006 (this data qualifies), acceptable conformity 0.006–0.012, marginal 0.012–0.015, nonconformity 0.015 and above. Individual digits flagged when standardized residual exceeds 1.96 (none do). First-two-digit test also ran on the same 1,500 values: chi-square across 90 combinations (10–99) gives p = 0.575, first-two-digit MAD of 0.0020, confirming no additional departure beyond first-digit sampling noise. Applicability: the amounts span 6.0 orders of magnitude across 1,499 distinct values—sufficiently wide-ranging that Benford's Law is a valid expectation. Caveat: data that is naturally assigned (identifiers), capped, floored, or single-price does not follow Benford and should not be screened this way.
What this can't tell you
Statistical conformity does not establish absence of fraud. Benford deviation is a screening signal; conformity clears the test but does not rule out isolated fabrications or collusion that preserves overall digit distribution.
Methodology
Statistical methodology and diagnostics for Benford's Law Screening
Statistical Method
Standard-library analysis: screen a column of amounts against Benford's Law, the forensic-accounting standard for spotting numbers that may have been fabricated or manipulated. The observed first-digit distribution is compared with the expected log10(1 + 1/d) curve using a chi-square goodness-of-fit test, the mean absolute deviation (MAD) read against Nigrini's conformity bands, per-digit standardized residuals flagging which digits deviate most, and a first-two-digit test when at least 1,000 values are available. Deviation is reported as a screening signal to investigate — never as proof of fraud — and a computed applicability check warns when the data's range is too narrow for Benford's Law to apply naturally.
- Amounts are organically generated and span several orders of magnitude
- Values are numeric or cleanly convertible to numeric
- Observations are independent records, not aggregates of each other
- Benford deviation is a screening signal, not proof of fraud or manipulation
- Assigned numbers, capped or floored ranges, and single-price data violate Benford naturally — the module warns when the data spans under 2 orders of magnitude
- Chi-square grows oversensitive on very large samples; read the MAD band alongside the p-value
- The first-two-digit test needs at least 1,000 values and is skipped below that
Analysis Code
Complete R source code for this analysis
Benford's Law Screening — Do These Numbers Look Fabricated?
Screens a numeric amount column against Benford's Law: the observed first-significant-digit distribution versus the expected log10(1 + 1/d) curve, a chi-square goodness-of-fit test, the mean absolute deviation (MAD) read against Nigrini's conformity bands, per-digit standardized residuals flagging which digits deviate most, and an optional first-two-digit test when there is enough data.
Why This Method?
Organic amounts that grow multiplicatively — transactions, expenses, invoice totals — start with 1 about 30 percent of the time and with 9 under 5 percent of the time. Invented numbers rarely follow that curve, which makes the first-digit test a fast, standard screen in forensic accounting and audit. Deviation is a SCREENING signal that says "look closer here", never proof of manipulation.
What This Analysis Covers
- Observed versus expected share for each first digit 1-9
- Chi-square goodness-of-fit against the Benford expectation
- MAD with Nigrini's conformity bands (close / acceptable / marginal /
nonconformity)
- Per-digit standardized residuals with deviates-high / deviates-low flags
- First-two-digit test when at least 1,000 usable values are present
- A computed applicability check: narrow-range data violates Benford
naturally and is warned about, not accused
Standard Library
Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {value}. All narrative is derived from the user's own column name and computed values.
suppressPackageStartupMessages(library(DT))
suppressPackageStartupMessages(library(htmlwidgets))
suppressPackageStartupMessages(library(arrow))
suppressPackageStartupMessages(library(knitr))
suppressPackageStartupMessages(library(rmarkdown))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(stringr))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(Matrix))
suppressPackageStartupMessages(library(cluster))
suppressPackageStartupMessages(library(data.table))Core Analysis Pipeline
Step 1: Locate the mapped amount column
initial_rows <- nrow(df)
if (!("value" %in% names(df))) {
stop("column_mapping must map the value column — the numeric amounts to screen.")
}
hv <- humanize_semantic("value", col_map)Step 2: Data hygiene — every drop is counted and reported.
Blanks and non-numeric entries are dropped; zeros are dropped (a zero has no first digit); negatives are KEPT via absolute value (a refund of -420 still leads with a 4); values below 1 are KEPT because the digit is defined as the first SIGNIFICANT digit (0.0042 counts as a 4).
v_raw <- df$value
ch <- as.character(v_raw)
blank_mask <- is.na(v_raw) | is.na(ch) | trimws(ch) == ""
conv <- suppressWarnings(as.numeric(ch))
nonnum_mask <- !blank_mask & is.na(conv)
n_blank <- as.integer(sum(blank_mask) + sum(nonnum_mask))
n_nonblank <- sum(!blank_mask)
if (n_nonblank > 0 && sum(!blank_mask & !is.na(conv)) < 0.95 * n_nonblank) {
stop(paste0(hv, " does not look numeric: fewer than 95 percent of its ",
"non-blank entries could be read as numbers. Benford ",
"screening needs a numeric amount column such as ",
"transaction amounts, expenses, or invoice totals."))
}
x0 <- conv[!blank_mask & !nonnum_mask]
n_zero <- as.integer(sum(x0 == 0))
n_neg <- as.integer(sum(x0 < 0))
x <- abs(x0[x0 != 0])
n_sub1 <- as.integer(sum(x < 1))
n <- length(x)
final_rows <- n
rows_removed <- initial_rows - final_rowsStep 3: Volume guard — Benford needs volume to mean anything.
if (n < 100) {
stop(sprintf(paste0(
"Benford screening needs at least 100 usable values to be meaningful, ",
"but %s has only %d after removing blank and zero entries. First-digit ",
"proportions are far too noisy below that volume for the conformity ",
"thresholds to carry any signal — gather more records and re-run."),
hv, n))
}Step 4: First significant digits, extracted robustly.
sprintf("%.6e", x) yields the normalized mantissa, so the first (and second) significant digits are positional characters — immune to the floating-point trap where floor(log10(1000)) evaluates to 2.
e_str <- sprintf("%.6e", x)
d1 <- as.integer(substr(e_str, 1, 1))
d2 <- as.integer(substr(e_str, 3, 3))Step 5: First-digit test — chi-square, MAD, Nigrini bands, residuals
digits <- 1:9
benford_p <- log10(1 + 1 / digits)
obs_counts <- tabulate(d1, nbins = 9)
obs_p <- obs_counts / n
chi <- suppressWarnings(chisq.test(obs_counts, p = benford_p, rescale.p = TRUE))
chi_stat <- as.numeric(chi$statistic)
chi_p <- as.numeric(chi$p.value)
mad_val <- mean(abs(obs_p - benford_p))Nigrini's first-digit conformity bands (Nigrini 2012): MAD < 0.006 close; < 0.012 acceptable; < 0.015 marginal; else nonconformity
band <- if (mad_val < 0.006) "close conformity" else
if (mad_val < 0.012) "acceptable conformity" else
if (mad_val < 0.015) "marginal conformity" else "nonconformity"
verdict <- if (band %in% c("close conformity", "acceptable conformity")) {
"conforms"
} else {
"deviates(investigate)"
}
se <- sqrt(benford_p * (1 - benford_p) / n)
z <- (obs_p - benford_p) / se
flag <- ifelse(z > 1.96, "deviates high", ifelse(z < -1.96, "deviates low", ""))
n_flagged <- as.integer(sum(flag != ""))Step 6: Applicability — computed against the data, not assumed.
Benford's Law only arises in amounts spanning several orders of magnitude. Assigned numbers, capped or floored ranges, and single-price data violate it NATURALLY — deviation there is not suspicious.
span_oom <- log10(max(x) / min(x))
n_distinct <- length(unique(x))
range_warning <- is.finite(span_oom) && span_oom < 2
applicability_notes <- character(0)
if (range_warning) {
applicability_notes <- c(applicability_notes, paste0(
"Warning: the amounts in ", hv, " span only ", sprintf("%.1f", span_oom),
" orders of magnitude(from ", fmt_num(min(x)), " to ", fmt_num(max(x)),
"), and Benford's Law may not apply to numbers confined to such a ",
"narrow band — assigned numbers, capped or floored amounts, and ",
"single-price data all depart from Benford naturally. Treat the ",
"conformity reading with caution: the deviation here can be a ",
"property of the data's range, not of anyone's behavior."))
} else if (n_distinct < 50) {
applicability_notes <- c(applicability_notes, paste0(
"Warning: ", hv, " holds only ", n_distinct, " distinct amounts, ",
"which suggests a fixed price list or assigned values rather than ",
"organically generated amounts — a situation where Benford's Law may ",
"not apply. Treat the conformity reading with caution."))
}Step 7: First-two-digit test — only with volume (n >= 1000).
ftd_ran <- n >= 1000
ftd_chi_p <- NA_real_
ftd_mad <- NA_real_
if (ftd_ran) {
ftd <- 10L * d1 + d2
ftd_counts <- tabulate(ftd, nbins = 99)[10:99]
ftd_exp_p <- log10(1 + 1 / (10:99))
ftd_chi <- suppressWarnings(chisq.test(ftd_counts, p = ftd_exp_p, rescale.p = TRUE))
ftd_chi_p <- as.numeric(ftd_chi$p.value)
ftd_mad <- mean(abs(ftd_counts / n - ftd_exp_p))
ftd_note <- paste0(
"The first-two-digit test ran on the same ", format(n, big.mark = ","),
" values(it requires at least 1,000): chi-square across the 90 ",
"two-digit combinations 10 through 99 gives ", fmt_pp(ftd_chi_p),
", with a first-two-digit MAD of ", sprintf("%.4f", ftd_mad), ". ",
if (!is.na(ftd_chi_p) && ftd_chi_p < 0.05) {
"It agrees with the first-digit read that the digit pattern departs from the Benford expectation."
} else {
"It finds no additional departure beyond first-digit sampling noise."
})
} else {
ftd_note <- paste0(
"The first-two-digit test was skipped: it requires at least 1,000 ",
"usable values and this data has ", format(n, big.mark = ","),
" — the 90 two-digit proportions are too noisy below that volume to ",
"read against an expectation.")
}Step 9: Metrics + computed answer
metrics <- list(
`Values Analyzed` = as.integer(n),
`MAD` = round(mad_val, 4),
`Conformity` = band,
`Chi-Square p` = fmt_p(chi_p),
`Digits Flagged` = n_flagged,
`Verdict` = verdict
)
flagged_digits <- digits[flag != ""]
flag_txt <- if (n_flagged > 0) {
paste0(n_flagged, " digit", .s(n_flagged), " (",
paste(flagged_digits, collapse = ", "), ") deviate",
if (n_flagged == 1) "s" else "",
" beyond the 1.96 standardized-residual line. ")
} else {
"No individual digit deviates beyond the 1.96 standardized-residual line. "
}
json_output <- list(
answer = paste0(
"Benford's Law screening of ", format(n, big.mark = ","),
" values from ", hv, ": mean absolute deviation ",
sprintf("%.4f", mad_val), " — ", band,
" on Nigrini's thresholds — with a chi-square goodness-of-fit ",
fmt_pp(chi_p), ". Verdict: ", verdict, ". ", flag_txt,
if (length(applicability_notes) > 0)
paste0(paste(applicability_notes, collapse = " "), " ") else "",
"Benford deviation is a screening signal that says where to look ",
"closer; it is not proof of fabrication or manipulation."
),
cards = lapply(
c("tldr", "overview", "preprocessing", "digit_distribution",
"deviations", "data_hygiene", "methods"),
function(cid) list(id = cid, metrics = metrics)
)
)
list(
initial_rows = initial_rows, final_rows = final_rows,
rows_removed = rows_removed,
hv = hv, n = n,
n_blank = n_blank, n_zero = n_zero, n_neg = n_neg, n_sub1 = n_sub1,
obs_p = obs_p, benford_p = benford_p, obs_counts = obs_counts,
chi_stat = chi_stat, chi_p = chi_p,
mad_val = mad_val, band = band, verdict = verdict,
z = z, flag = flag, n_flagged = n_flagged,
flagged_digits = flagged_digits,
span_oom = span_oom, n_distinct = n_distinct,
range_warning = range_warning,
applicability_notes = applicability_notes,
ftd_ran = ftd_ran, ftd_chi_p = ftd_chi_p, ftd_mad = ftd_mad,
ftd_note = ftd_note,
digit_distribution_df = digit_distribution_df,
deviations_df = deviations_df,
data_hygiene_df = data_hygiene_df,
conformity_bands_df = conformity_bands_df,
metrics = metrics, json_output = json_output
)
}