本福特定律:检测自然数据和欺诈数据的分析工具

本福特定律剖析

本福特定律是数据分析中一个很有意思的经验法则。它指出,在任何自然生成的大量数字集合里,大约会有 30% 的数字是以 1 开头的。从数字的频率分布来看,以 2 开头的数字出现概率大概是 17%,而以 9 开头的数字出现概率仅为 5%。

理解“自然生成”这个概念很关键。本质上,本福特定律关注的是数字生成后跨越数量级的趋势。这意味着,我公司账目、发票、账单或者其他任何财务记录里的数字,都会遵循本福特定律。在这些系统中,既有很小的数字,也有很大的数字,所以用对数尺度来比较它们时,会发现它们聚集在一起。

这就表明,任何由记录数据生成的值组成的数据集,而不是随机生成的值的集合,都可以用本福特定律来进行测试。实际上,该定律在金融系统中被用于检测欺诈行为,因为通过它就有可能判断这些数字是自然生成的,还是受到了某种因素的影响。

如果你还记得 2001 年的安然丑闻,当时这家公司因为广泛的会计欺诈而倒闭。通过把本福特定律应用到他们的财务记录上,揭示了欺诈行为,这些记录显示,它们不符合自然发生的数据应有的正确数字分布。

在本文中,我们会探讨如何计算本福特定律,然后把该定律应用到一系列数据集上,看看能不能发现什么问题。同时,在 Drupal 开发、Drupal 模块开发以及 Drupal 升级等相关的数据处理场景中,也可以尝试运用本福特定律来检测数据的真实性和自然性,特别是在 Drupal 11 的环境下,对于财务数据、业务指标等数据,可以借助本福特定律进行更有效的分析。

本福特定律实际上就是对 1 到 9 每个数字出现比例的计算。可以用下面这个函数来进行计算。

function benford($num) {
  return log10(1 + (1 / $num));
}

为了把计算得到的值和我们的数据集进行比较,我们只需要提取每个数字在数值开头出现的次数,然后用这个次数除以数字的总数即可。

咱们从一个 CSV 文件里提取一些数据,这里我们使用 PHP 来操作。

首先,打开 CSV 文件。

$file = 'data.csv';
$fh = fopen($file, "r");

接着,遍历数据,统计 1 到 9 之间每个数字在我们的数据中出现的次数。

// 创建一个包含1到9的键的数组。
$numbers = array_flip(range(1, 9));
$total = 0;

while (($data = fgetcsv($fh, 0, ',')) !== FALSE) {
  // 从CSV中提取值。
  $number = $data[0];

  // 获取第一个数字。
  $digit = substr(trim($number), 0, 1);

  if(isset($numbers[$digit])) {
    // 如果数字存在,则对其进行计数。
    $numbers[$digit]++;
    $total++;
  }
}

这样就能得到一个数组,数组的键是 1 到 9 的数字,值是每个数字出现的次数,同时还能得到处理的数字总数。

现在,我们只需要计算数据集和本福特定律所对应的数字比例。

$data = [];

$minValue = 1;
$maxValue = 0;

foreach($numbers as $digit => $count) {
  $numberRatio = number_format($count/$total, 3);
  $benfordRatio = number_format(benford($digit), 3);

  $min = min($numberRatio, $benfordRatio);
  $max = max($numberRatio, $benfordRatio);

  if ($min < $minValue) {
    $minValue = $min;
  }
  if ($max > $maxValue) {
    $maxValue = $max;
  }

  $data[$digit] = [
    $numberRatio,
    $benfordRatio,
  ];
}

现在,可以把结果以 CSV 文件的形式输出。

echo "#,data,benford" . PHP_EOL;
foreach ($data as $digit => $item) {
  echo $digit . ',' . $item[0] . ',' . $item[1] . PHP_EOL;
}

为了测试这个方法,我从自己的银行账户下载了几年的交易记录,然后用上面的代码生成了下面这个表格。

#,data,benford
1,0.301,0.301
2,0.175,0.176
3,0.171,0.125
4,0.080,0.097
5,0.102,0.079
6,0.054,0.067
7,0.060,0.058
8,0.066,0.051
9,0.041,0.046

看起来这些数据符合本福特定律,但要是根据这些数据创建一个图表,就能更清楚地看到它们之间的关系。

所以,咱们来创建一个图表,以图形的形式展示这两组数字之间的关系。下面的代码会把上面的数组作为输入,然后生成一个包含图表的图像文件。

$height = 400;
$width = 700;

// 创建图像。
$image = imagecreatetruecolor($width, $height);

// 将线条粗细设置为5像素。
imagesetthickness($image, 5);

// 设置背景颜色。
$backgroundColour = imagecolorallocate($image, 255, 255, 255);
imagefilledrectangle($image, 0, 0, $width, $height, $backgroundColour);

// 设置线条颜色。
$lineColour = imagecolorallocate($image, 0, 0, 255);
$baseLineColour = imagecolorallocate($image, 128, 128, 128);
$benfordLineColour = imagecolorallocate($image, 0, 0, 128);

// 调整高度和宽度。
$height -= 10;
$width -= 10;

// 绘制图表的背景。
imageline($image, 10, 10, 10, $height, $baseLineColour);
imageline($image, 10, $height, $width, $height, $baseLineColour);

$height = $height - 100;

// 计算图表中每个点之间的校正距离。
$barWidth = ($width) / (count($data) - 2) - 1;

// 在图表中绘制线条。
for ($i = 1; $i < count($data) -1; $i++) {
  // 数据集线条。
  $value = $data[$i][0] ?? 0;
  $nextValue = $data[$i + 1][0] ?? 0;

  $x1 = floor(($i - 1) * $barWidth) + 10;
  $x2 = floor($x1 + $barWidth - 1);
  $y1 = $height - round($height * (($value - $minValue) / ($maxValue - $minValue))) + 10;
  $y2 = $height - round($height * (($nextValue - $minValue) / ($maxValue - $minValue))) + 10;
  imageline($image, $x1, $y1, $x2, $y2, $lineColour);

  // 绘制本福特定律线条。
  $value = $data[$i][1] ?? 0;
  $nextValue = $data[$i + 1][1] ?? 0;

  $x1 = floor(($i - 1) * $barWidth) + 10;
  $x2 = floor($x1 + $barWidth - 1);
  $y1 = $height - round($height * (($value - $minValue) / ($maxValue - $minValue))) + 10;
  $y2 = $height - round($height * (($nextValue - $minValue) / ($maxValue - $minValue))) + 10;
  imageline($image, $x1, $y1, $x2, $y2, $benfordLineColour);
}

// 输出图像。
imagepng($image, 'benfords_law_' . str_replace('.csv', '', $file) . '.png');

使用从我的银行账户获取的值,生成的结果完全符合本福特定律。

当你测试那些本应遵循该规则的数据,并且数据确实高度符合时,其实会让人感到安心。

受到这次成功的鼓舞,我决定用这个图表来研究其他一些数据集。为此,我找到了一些开放数据集,可以从中挖掘相关信息。

我下载了一个不明飞行物目击事件的数据库,并且分析了每次遭遇的持续时间(以分钟为单位)。虽说这个数据集只有几千条记录,但本福特定律在数据中还是有所体现。

还有大量汽车的发动机扭矩(磅 - 英尺)数据。因为以 2 开头的发动机扭矩值数量较多,开始时会有一个小峰值,但除此之外,它遵循本福特定律的一般规则。

我很好奇一些人工数据集在应用本福特定律时会有什么结果,所以我编造了几个数据集来看看会发生什么。

我在键盘上输入了几百行随机数字,然后用上面的代码进行处理。很明显,这些数据根本不遵循本福特定律,但这非常清楚地表明,该定律可以用来识别任何非自然生成的数据。在 Drupal 开发过程中,对于一些模拟数据的生成和验证,也可以借助本福特定律来判断其是否符合自然数据的特征。

我还使用一段简单的代码生成了一些随机数,依次排列从 1 到 1000000 的数字,只是想看看会有什么结果。

从所有这些数据集可以很清楚地看到,随机或人工生成的数字所产生的结果与本福特定律的曲线完全不符。只要是我自己输入数字,所产生的结果曲线与理想曲线就完全不匹配。

结论

要记住,这只是一个经验法则,并非适用于所有数据的绝对事实。当数据集太小、没有跨越数量级或者不是自然生成时,本福特定律将不适用。它不是检测编造数字的万能解决方案。

话虽如此,如果你计算了某个数据集的数字比例,而它与本福特定律不匹配,那么深入研究该数据以了解情况可能是个好主意。

不符合本福特定律的财务记录是一个严重的警示信号,应该认真对待。如果某种因素(或某个人)改变了该数据集,那么在计算首位数字的比例时,就会在数据中显示为异常值。如果有人完全编造了一个数据集,那么它将完全不遵循本福特定律,从上面的图表中可以清楚地看到这一点。

我觉得本福特定律在数据分析中或多或少类似于钟形曲线。如果你有一组数据,特别是像考试成绩或实验结果这样的数据,那么你应该会看到一个漂亮的钟形曲线,两端的数值较小,中间有一个单峰。

有两个或更多峰值的钟形曲线表明,有一些外部因素影响了结果,导致出现了差异,你应该深入研究,找出到底发生了什么。

如果你对这个感兴趣,那么你可能会想了解齐普夫定律,它研究的是数据集中分布的比例频率。