本福特定律剖析
本福特定律是数据分析中一个很有意思的经验法则。它指出,在任何自然生成的大量数字集合里,大约会有 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 的数字,只是想看看会有什么结果。
从所有这些数据集可以很清楚地看到,随机或人工生成的数字所产生的结果与本福特定律的曲线完全不符。只要是我自己输入数字,所产生的结果曲线与理想曲线就完全不匹配。
结论
要记住,这只是一个经验法则,并非适用于所有数据的绝对事实。当数据集太小、没有跨越数量级或者不是自然生成时,本福特定律将不适用。它不是检测编造数字的万能解决方案。
话虽如此,如果你计算了某个数据集的数字比例,而它与本福特定律不匹配,那么深入研究该数据以了解情况可能是个好主意。
不符合本福特定律的财务记录是一个严重的警示信号,应该认真对待。如果某种因素(或某个人)改变了该数据集,那么在计算首位数字的比例时,就会在数据中显示为异常值。如果有人完全编造了一个数据集,那么它将完全不遵循本福特定律,从上面的图表中可以清楚地看到这一点。
我觉得本福特定律在数据分析中或多或少类似于钟形曲线。如果你有一组数据,特别是像考试成绩或实验结果这样的数据,那么你应该会看到一个漂亮的钟形曲线,两端的数值较小,中间有一个单峰。
有两个或更多峰值的钟形曲线表明,有一些外部因素影响了结果,导致出现了差异,你应该深入研究,找出到底发生了什么。
如果你对这个感兴趣,那么你可能会想了解齐普夫定律,它研究的是数据集中分布的比例频率。


