Magic Bookof Algorithms DE

StatisticsSTA-01

Benford's Law

Real numbers start with a 1 far more often than one would think. People who make them up do not know that.

Running time
O(n)
Formula
P(d) = log10(1 + 1/d)
Leading 1
30.1 % instead of 11.1 %
Test statistic
chi-square, limit 15.51

01What it is about

One would expect every digit to stand at the front of a number equally often, each in about 11 percent of the cases. For numbers that have grown — invoice totals, populations, river lengths, share prices — that is not what happens. There the 1 leads in a good 30 percent of the cases and the 9 in less than 5. That is Benford's law, and it is not a curiosity: it is one of the few rules that tell something about how a plain list of numbers came about.

Simon Newcomb noticed it first, in 1881, on worn paper: the front pages of the logarithm tables in the library were used far more heavily than the back ones. Frank Benford measured it in 1938 across 20 data sets with more than 20,000 numbers, from molecular weights to house numbers.

The law becomes useful because nobody can invent what they do not know. People who make up amounts spread them evenly — and give themselves away.

130.1 %
217.6 %
312.5 %
49.7 %
57.9 %
66.7 %
75.8 %
85.1 %
94.6 %
The expected distribution of the leading digit, computed with the same formula as the code below.

02How it works

The formula

The expected frequency of the leading digit d is

P(d) = log10(1 + 1/d)

That gives 30.1 % for the 1, 17.6 % for the 2 and so on down to 4.6 % for the 9. The nine values add up to exactly 1, because the sum of the logarithms telescopes into log10(10/1).

Why a logarithm of all things

The real reason is called scale invariance. A distribution of amounts should not depend on whether the books are kept in euros or in dollars. Multiplying every number by a fixed factor shifts their leading digits — and there is exactly one distribution that survives that unchanged: the logarithmic one above.

Put differently: draw the numbers on a logarithmic axis and they lie evenly spread on it. On that axis the stretch from 1 to 2 is much longer than the one from 9 to 10 — by exactly the factor the formula gives.

A second effect comes on top: products of several independent factors land on Benford all by themselves. Multiplying means adding on the logarithmic axis, and sums of many random quantities spread wider and wider — the wider they spread, the better the law holds. That is why invoice totals (amount times price times markup) fit so well, while body heights never do.

Counting and comparing

The rest is bookkeeping. LeadingDigit shifts a number by powers of ten until it lies between 1 and 10, Count tallies the nine digits. What is missing is a measure for whether a deviation can still be chance.

Chi-square

That is what the chi-square goodness-of-fit test provides: every deviation between counted and expected is squared (so that a few large ones weigh more than many small ones) and divided by the expected value (so that rare digits are not judged too harshly).

X² = sum of (observed − expected)² / expected

With nine digits the test has eight degrees of freedom. The limits there:

  • Eight degrees of freedom, the digits 1 to 9: 15.51 at 95 %, 20.09 at 99 %.
  • Nine degrees of freedom, the digits 0 to 9: 16.92 at 95 %, 21.67 at 99 %.

A value below 15.51 is unremarkable for nine digits: pure chance produces that much deviation in 19 out of 20 cases. One caveat: chi-square grows with the number of values. With 100,000 records even a tiny, harmless lopsidedness sets it off.

03Try it

04Implementation

MagicBook.Algorithms/Statistics/Benford.csC#
// Numbers that grew on their own - invoice totals, populations, share prices - do not
// start with all nine digits equally often. The 1 leads in about 30 percent of the
// cases, the 9 in less than 5. The reason is that such numbers are spread evenly on a
// logarithmic scale, and on that scale the stretch from 1 to 2 is far longer than the
// one from 9 to 10. Anyone who makes numbers up does not know that.
public static class Benford
{
    // How often the digit is expected to lead. This is the whole law.
    public static double Share(int digit)
    {
        ArgumentOutOfRangeException.ThrowIfLessThan(digit, 1);
        ArgumentOutOfRangeException.ThrowIfGreaterThan(digit, 9);

        return Math.Log10(1 + 1.0 / digit);
    }

    // The nine expected shares in a row, ready to be compared with a count.
    public static double[] Shares() => [.. Enumerable.Range(1, 9).Select(Share)];

    // The leading digit of a number. Size and sign do not matter: 0.00734 and
    // -73400 both start with a 7.
    public static int LeadingDigit(double number)
    {
        var value = Math.Abs(number);

        if (value == 0 || double.IsNaN(value) || double.IsInfinity(value))
            throw new ArgumentOutOfRangeException(nameof(number), "This number has no leading digit.");

        while (value >= 10)
            value /= 10;

        while (value < 1)
            value *= 10;

        return (int)value;
    }

    // How often each digit from 1 to 9 leads. The first entry belongs to the 1.
    // Zeros carry no leading digit and are left out.
    public static int[] Count(IEnumerable<double> numbers)
    {
        var counts = new int[9];

        foreach (var number in numbers)
        {
            if (number != 0)
                counts[LeadingDigit(number) - 1]++;
        }

        return counts;
    }

    // Chi-square measures how far a count is from what was expected: every deviation
    // is squared, so that a few large ones weigh more than many small ones, and then
    // divided by the expected value, so that rare digits are not judged too harshly.
    // Zero means a perfect fit, the larger the value the more suspicious the data.
    public static double ChiSquare(int[] counts, double[] shares)
    {
        if (counts.Length != shares.Length)
            throw new ArgumentException("There has to be one expected share per count.", nameof(shares));

        var total = counts.Sum();
        var sum = 0.0;

        for (var i = 0; i < counts.Length; i++)
        {
            var expected = total * shares[i];
            var difference = counts[i] - expected;

            sum += difference * difference / expected;
        }

        return sum;
    }
}

05Numbers nobody made up

MagicBook.Console/Examples/BenfordExample.csC#
// Two series that nobody made up: the powers of two and the Fibonacci numbers.
// Both grow by multiplication, and that is exactly the kind of number the law
// is about.
var powers = new List<double>();
var fibonacci = new List<double>();

var power = 1.0;
double previous = 1, current = 1;

for (var i = 0; i < 500; i++)
{
    powers.Add(power);
    power *= 2;

    fibonacci.Add(previous);
    (previous, current) = (current, previous + current);
}

var powerCounts = Benford.Count(powers);
var fibonacciCounts = Benford.Count(fibonacci);

Console.WriteLine("digit  expected  powers of two  fibonacci");

for (var digit = 1; digit <= 9; digit++)
{
    Console.WriteLine(
        $"{digit,5}  {Benford.Share(digit),7:0.0%}  " +
        $"{powerCounts[digit - 1] / 500.0,12:0.0%}  {fibonacciCounts[digit - 1] / 500.0,9:0.0%}");
}

// Chi-square says in one number how far a count is from the expectation.
// Below 15.51 the deviation is what chance alone would produce in 19 of 20 cases.
Console.WriteLine();
Console.WriteLine($"chi-square, powers of two: {Benford.ChiSquare(powerCounts, Benford.Shares()),6:0.00}");
Console.WriteLine($"chi-square, fibonacci:     {Benford.ChiSquare(fibonacciCounts, Benford.Shares()),6:0.00}");
Console output
digit  expected  powers of two  fibonacci
    1    30.1%         30.2%      30.2%
    2    17.6%         17.6%      17.6%
    3    12.5%         12.4%      12.6%
    4     9.7%          9.8%       9.4%
    5     7.9%          7.8%       8.0%
    6     6.7%          6.8%       6.6%
    7     5.8%          5.6%       5.8%
    8     5.1%          5.2%       5.4%
    9     4.6%          4.6%       4.4%

chi-square, powers of two:   0.07
chi-square, fibonacci:       0.17

06A cash book that does not add up

MagicBook.Console/Examples/BenfordCashBookExample.csC#
const int entries = 500;

var random = new Pcg(20240921);

// An honest cash book of a wholesaler. Every total is a chain of factors:
// so many boxes, so many pieces in a box, a price per piece and a markup.
// Nobody picks the total, it falls out of the multiplication - and products
// of several independent factors land on Benford all by themselves.
int[] perBox = [6, 12, 24, 48];
var honest = new List<double>();

for (var entry = 0; entry < entries; entry++)
{
    var boxes = 1 + random.Next(12);
    var pieces = perBox[random.Next(perBox.Length)];
    var price = 0.20 + random.NextDouble() * 7.80;
    var markup = 1.0 + random.NextDouble() * 0.6;

    honest.Add(Math.Round(boxes * pieces * price * markup, 2));
}

// The other cash book was made up. A person writes down amounts that look
// harmless: nothing too small, nothing too large, and rather 47 or 63 than a
// round 50, because round numbers feel conspicuous.
int[] favourites = [3, 4, 6, 7, 8];
var invented = new List<double>();

for (var entry = 0; entry < entries; entry++)
{
    var euros = (2 + random.Next(8)) * 10 + favourites[random.Next(favourites.Length)];

    invented.Add(euros + (random.Next(2) == 0 ? 0 : 0.5));
}

// The leading digit should follow Benford, the euro digit should be spread
// evenly: each of the ten digits about a tenth of the time.
var evenly = Enumerable.Repeat(0.1, 10).ToArray();

Console.WriteLine("cash book   leading digit   euro digit");

foreach (var (name, book) in new[] { ("honest", honest), ("invented", invented) })
{
    Console.WriteLine(
        $"{name,-9}   {Benford.ChiSquare(Benford.Count(book), Benford.Shares()),13:0.0}   " +
        $"{Benford.ChiSquare(EuroDigits(book), evenly),10:0.0}");
}

Console.WriteLine();
Console.WriteLine("chance alone stays below 15.5 and 16.9 in 19 out of 20 cases");

// How often each digit stands directly in front of the decimal point.
static int[] EuroDigits(IEnumerable<double> amounts)
{
    var counts = new int[10];

    foreach (var amount in amounts)
        counts[(int)amount % 10]++;

    return counts;
}
Console output
cash book   leading digit   euro digit
honest                6.5          7.0
invented            366.6        504.6

chance alone stays below 15.5 and 16.9 in 19 out of 20 cases

07Good to know

  • Where the tax office uses it. In a digital tax audit (data access under § 147 (6) of the German fiscal code) digit analysis runs as a pre-check: Benford on the leading digits, chi-square on the last digits before the decimal point, where a genuinely even spread is expected. The second example runs exactly those two tests. People who invent amounts have favourite digits without knowing it, and they avoid round numbers because round numbers feel conspicuous.
  • An indication, not a proof. According to German federal and regional tax court rulings, digit analysis alone cannot shake the presumption of proper bookkeeping (§ 158 of the fiscal code). It moves the focus of an audit, nothing more. The Düsseldorf tax court calls it an admissible indication that on its own does not support rejecting the accounts.
  • Where the law does not hold. Assigned numbers (postcodes, account numbers, telephone numbers), numbers from a narrow range (body heights, shoe sizes), amounts with hard limits (grants up to 500 €) and prices with the .99 convention. For election results the method is contested as well, because vote counts rarely span enough orders of magnitude.
  • Known cases. Mark Nigrini established the method in auditing. Among the data it has been used on are the books of Enron and the deficit figures Greece reported to Brussels.
  • It does not work in reverse. Data that fits Benford is not automatically genuine — anyone who knows the law can shape their forgeries to match it. That is why forensic accountants also look at the last digits, at duplicates and at round amounts.