本福特定律及Python实验

2021-07-30 09:14:24

昨天看了Netflix原创纪录片大数据时代 (第四集),上面介绍了一个本福特定律(Benford’s law),大概是说世界上看似是随机的分布实际上存在着某种规律。比如说全世界各地区的火山数量、美国地区的选票甚至是一个公司的财报数据都符合这种规律。上面还举例说明了,我们可以利用这个规律来验证数据是否经过了人为造假还可以判断一张图片是否经过修改,甚至还可以判断网络上一些人是否是机器人。这感觉像是魔法一样,一些个体的独立选择、甚至是自然界看似完全随机的一些事件在整体上面看起来竟然存在着某些规则,就像有一只上帝之手在虚空空操纵这一些似的。实在太令人难以置信了,所以我想要找一些数据用Python来验证一下是否确实有着本福特定律的存在。在验证之前,先大致介绍一下什么是本福特定律。

一、本福特定律(Benford’s law)

在我们的印象中,一大堆真实世界的数据,比如一座图书馆所有图书的页码数,以1为首位数字和以9为首位数字的书籍数量应该是一样多的,大概都占全部数据的\(\frac{1}{9}\)​​​。但是本福特定律告诉我们事实并不是这样的,该定律认为在一堆实际生活的出的数据中,以1为首位数字的数出现的概率约为总数的三成,接近直觉得出的期望值\(\frac{1}{9}\)的三倍。推广来说,越大的数,以它位首几位的数出现的概率就越低。但是要注意使用条件:

  1. 数据至上3000笔以上。
  2. 不能有人为操控

所以我们可以利用它的第二个条件来判断是否具有数据造假。

二、验证本福特定律

所用验证数据为2019年全国各市区城市人口数量,数据来源为中华人民共和国住房和城乡建设部 - 建设统计年鉴 (mohurd.gov.cn)。当然该数据只有676笔并没有3000笔以上, 不过我们可以先分析的试一下,看其是否满足本福特定律。这里使用Python的对数据进行分析。代码如下

#coding=utf-8
from collections import Counter

import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv("./全国各市区人口数量.CSV",encoding='GBK')
# 市区人口是以万人为单位,将其转化回去
data['市区人口'] *= 1e4
# 提取出首位数字,并统计首位数字出现的次数
occurrences_number = Counter([eval(str(i)[0]) for i in data['市区人口']])
# 通过key对词典进行排序
occurrences_number_sorted = dict(sorted(occurrences_number.items(), 
                                        key=lambda item: item[0])
                                )

# 解决中文乱码问题
plt.rcParams['font.sans-serif']=['SimHei']
# 调整图片尺寸
plt.figure(figsize=(8,6))
# 通过matplotlib绘图
plt.plot(list(occurrences_number_sorted.keys()),
         list(occurrences_number_sorted.values()),
         marker='.')
plt.title("市区人口数量首位数字统计")
plt.savefig("./市区人口数量首位数字统计.svg",dpi=300)

分析结果如图所示

市区人口数量首位数字统计

从分析结果上来看,虽然结果可能由于数据量不够的原因并不完全满足本福特定律,但是还是非常相似的。以1开头的数字数量远大于其他数字开始的数字数量,并且总体呈现递减的趋势。(虽然这个中间有部分隆起)

三、验证图片是否经过修改

在纪录片中说到可以通过本福特定律来确定一个图片是否经过了修改,这里来实验一下。 我这里是先将图片转化为灰度图片,然后统计图片的灰度值,看其是否满足本福特定律。所用图片是我自己在学校拍的一张风景照

学校的夕阳

因为图片本身比较大为了提高博客的加载速度,这里图片是经过压缩了的。使用Python对图片进行分析,代码如下

#coding=utf-8
'''
验证图片的灰度值是否满足本福特定律
'''
from collections import Counter
import cv2
import matplotlib.pyplot as plt
import numpy as np

# 以黑白的形式读取图片
img = cv2.imread("./20210621_193616.jpg",0)
# 将所有像素点的灰度值整合都一行中
data_array = img.reshape(img.size)
# 去除含有0的值
data_array = data_array[data_array != 0]

# 提取出首位数字,并统计首位数字出现的次数
occurrences_number = Counter([eval(str(i)[0]) for i in data_array])
# 通过key对词典进行排序
occurrences_number_sorted = dict(sorted(occurrences_number.items(), key=lambda item: item[0]))

# 解决中文乱码问题
plt.rcParams['font.sans-serif']=['SimHei']
# 调整图片尺寸
plt.figure(figsize=(8,6))
# 通过matplotlib绘图
plt.plot(list(occurrences_number_sorted.keys()),
         list(occurrences_number_sorted.values()),
         marker='.')
plt.title("图片灰度值首位数字统计")
plt.savefig("./图片灰度值首位数字统计.svg",dpi=300)

分析结果如图所示

图片灰度值首位数字统计

结果显示大致是符合本福特定律的,但如果我们将这个图片用PS修改一下它是否还符合呢?这个是PS后的图片(可以看看P了那些地方哈)

PS后的图片

将这个图片输入到上面的代码中,可以得到下图的分析数据

图片灰度值首位数字统计(PS后)

哈哈,好像差别并不是很大,可能本福特定律并不适合用来鉴别图片是否经过PS。(至上通过统计灰度数据是这样的)