Executive Summary
Benford's Law read on 4,941 values from Amount
The short answer
These 4,941 transaction amounts deviate from Benford's Law. The mean absolute deviation of 0.0234 falls in the nonconformity band, and the chi-square test yields p < 0.001, signaling a first-digit pattern that departs from the expected log10(1 + 1/d) curve more than chance would explain.
The detail
All 9 digits carry the deviation. The MAD of 0.0234 is the aggregate distance from the Benford curve across all digits; Nigrini's conformity bands classify this as nonconformity. The chi-square p-value < 0.001 confirms the deviation is statistically detectable across the 4,941 values analyzed. The deviations table (in the full report) shows which individual digits are elevated or suppressed.
What this can't tell you
A Benford deviation is a screening signal—it points to where an auditor should look—but does not establish the reason. Legitimate business rules, rounding, or systematic numbering can produce deviations identical to those from data quality issues. Investigate the flagged digits in context: compare them against your transaction generation process, pricing rules, and historical patterns.
Analysis Overview
Benford's Law screening of 4,941 values from Amount.
The short answer
The first-digit distribution of these 4,941 transaction amounts deviates from Benford's Law. Organically generated numbers—transactions, invoices, expenses—tend to start with small digits in a predictable pattern (about 30.1% with digit 1, only 4.6% with digit 9). This dataset's first-digit pattern diverges from that curve enough to warrant investigation, though deviation is a screening signal, not proof of fabrication.
The detail
The chi-square goodness-of-fit test yields p < 0.001, well below the threshold for random variation. The mean absolute deviation (MAD) is 0.0234, which places the dataset in Nigrini's nonconformity band—the threshold band that flags datasets as deviating rather than conforming. All nine digits (1 through 9) carry the deviation; the deviations table specifies which digits are over- or under-represented.
What this can't tell you
The analysis confirms the digit pattern does not match Benford's curve; it does not identify the source of the deviation. Benford deviation can arise from legitimate causes—rounding conventions, currency conversions, invoice numbering systems, or business rules that naturally skew transaction sizes—as well as from data entry errors or intentional misstatement. The next step is domain inspection: compare the flagged digits against your transaction workflows and controls.
Data Quality
How the amounts were cleaned before digit extraction.
The short answer
Of 5,000 rows loaded, 4,941 values were usable for digit screening. The main exclusion was 59 zero values, which carry no first digit; all other data quality checks left the sample intact.
The detail
Zero-value rows (59 total) were dropped because a zero has no first digit to extract. No blank or non-numeric entries were present, and no negative values required absolute-value conversion. A total of 279 values below 1 were retained because the digit is defined as the first significant digit — so 0.0042 counts as a 4 just as 4,200 does. This preserves small fractional amounts that may carry real information. The final count of 4,941 values exceeds the 100-value minimum required for Benford screening to be valid.
What this can't tell you
The preprocessing captures what was dropped and why, but does not reveal whether any of the 59 zeros represent missing or suppressed data rather than genuine zero amounts. A transaction-level export showing the reason codes for zero values would clarify whether they are structural (e.g., refunds netted to zero) or data-entry gaps.
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 bars break sharply from the downward Benford slope. Digit 1 is over-represented at 36.6% versus 30.1% expected, while digits 2 through 6 fall consistently short of the curve, and digits 7, 8, 9 spike above expectation.
The detail
Benford expects a smooth decline from digit 1 (30.1%) to digit 9 (4.6%). The observed distribution peaks at digit 1 (36.6%), drops steeply to digit 3 (9.2% observed vs. 12.5% expected — the largest single shortfall), and then does not resume the expected decline. Instead, digits 7 (7.14% vs. 5.8%), 8 (5.95% vs. 5.12%), and 9 (6.46% vs. 4.58%) all exceed their Benford values. This non-monotonic pattern — a bulge at the low end and an unexpected rise at the high end — is the signature of the deviation.
What this can't tell you
The chart shows where the digit pattern diverges but not why. Over-representation of digit 1 can reflect naturally small amounts, contractual minimums, or rounding practices; over-representation of 7, 8, 9 can point to round numbers or specific pricing conventions. Only transaction context would reveal the cause.
Per-Digit Deviations
Observed vs expected share per digit with standardized residuals.
| Digit | Observed | Expected | Std Residual | Flag |
|---|---|---|---|---|
| 1 | 0.3659 | 0.301 | 9.94 | deviates high |
| 2 | 0.1484 | 0.1761 | -5.12 | deviates low |
| 3 | 0.0917 | 0.1249 | -7.07 | deviates low |
| 4 | 0.0844 | 0.0969 | -2.97 | deviates low |
| 5 | 0.064 | 0.0792 | -3.96 | deviates low |
| 6 | 0.0502 | 0.0669 | -4.71 | deviates low |
| 7 | 0.0714 | 0.058 | 4.05 | deviates high |
| 8 | 0.0595 | 0.0512 | 2.66 | deviates high |
| 9 | 0.0646 | 0.0458 | 6.33 | deviates high |
The short answer
Digit 1 stands out as the largest deviation: it appears 9.94 standard errors above its Benford expectation. Digits 2 through 6 all fall below expectation, with digit 3 the deepest shortfall at −7.07 standard errors. Digits 7, 8, 9 also over-appear.
The detail
All 9 digits flag beyond the ±1.96 threshold. Digit 1: observed 36.6%, expected 30.1%, standardized residual +9.94 (deviates high). Digit 3: observed 9.2%, expected 12.5%, standardized residual −7.07 (deviates low). Digit 2: −5.12. Digit 4: −2.97. Digit 5: −3.96. Digit 6: −4.71. Digit 7: +4.05. Digit 8: +2.66. Digit 9: +6.33. The pattern shows a systematic excess of 1 and 7–9, and a systematic deficit of 2–6.
What this can't tell you
A standardized residual measures distance from expectation in the context of sample size, but does not explain the cause. Over-representation of digit 1 is consistent with many legitimate patterns (small natural amounts, contractual floors, rounding down). Over-representation of 7–9 can indicate round numbers or specific pricing tiers. Audit procedures on the underlying transactions would be needed to rule out or confirm any particular mechanism.
Data Hygiene
What was dropped, what was kept, and why.
| Reason | Rows Affected | Action |
|---|---|---|
| Blank or non-numeric entries | 0 | dropped — no digits to read |
| Zero values | 59 | dropped — a zero has no first digit |
| Negative values | 0 | kept — absolute value used |
| Values below 1 | 279 | kept — first significant digit used |
| Values analyzed | 4941 | screened against Benford's Law |
The short answer
The dataset is clean at the preprocessing stage: 59 zero values were dropped (no first digit), and 279 sub-1 values were kept (first significant digit rule). No blanks, non-numeric entries, or negative values required correction.
The detail
Of 5,000 loaded rows: 0 blank or non-numeric entries (dropped — no digits to read); 59 zero values (dropped — a zero has no first digit); 0 negative values (kept via absolute value, but none were present); 279 values below 1 (kept — first significant digit definition applies, e.g., 0.0042 → digit 4). Final count: 4,941 values screened. The only material exclusion was the 59 zeros; all other adjustments were either absent or preserving of valid data.
What this can't tell you
The data-hygiene table does not distinguish between zero values that represent genuine zero amounts (e.g., refunds or reversals) and those that may be missing or suppressed. A breakdown by transaction type or source would clarify whether the 59 zeros are expected or anomalous. Consider requesting a transaction-level export that shows the reason or category for each zero value.
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 | |
| 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 | yes |
The short answer
The analysis used a chi-square goodness-of-fit test (p < 0.001) and the mean absolute deviation (0.0234) against Nigrini's published conformity bands. The MAD falls in the nonconformity band (≥0.015), confirming the digit pattern departs from Benford's Law. A second-digit test (p < 0.001) corroborates the finding.
The detail
Chi-square test: 8 degrees of freedom, p < 0.001, rejecting the hypothesis that the first-digit distribution follows log₁₀(1 + 1/d). Mean absolute deviation: 0.0234, compared against Nigrini's thresholds — close conformity ≤0.006, acceptable ≤0.012, marginal ≤0.015, nonconformity ≥0.015. This data lands in nonconformity. Individual digits flagged when standardized residual > 1.96. First-two-digit test (90 combinations, 10–99): chi-square p < 0.001, first-two-digit MAD 0.0048, agreeing that the digit pattern departs from Benford. Applicability: amounts span 5.8 orders of magnitude across 2,504 distinct values — wide enough for Benford to apply.
What this can't tell you
Benford's Law does not apply to assigned identifiers, capped or floored ranges, or single-price lists; this dataset passes the applicability check. The tests confirm deviation but do not identify its cause. A qualitative review of the transactions themselves would be needed to distinguish legitimate patterns (e.g., contractual minimums, round pricing) from data anomalies.
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
)
}